Address review round 3: id limit, private temp files, canonical ids

- add() refuses to create an id that load() would reject; the CLI reports it
- save() creates the temp file with the real file's mode (and O_EXCL), so a
  private list is never briefly world-readable; one stat call; symlink
  resolution inlined; comment states the rename trade-offs
- CLI rejects ids with leading zeros and id 0
- Tests for trimming, the bare 'todo' default, and the id limit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
christianandClaude Opus 5.5 committed 2026-10-21 00:41:37 +02:00
1 parent 089fc94e16
commit f09d20cb84
4 files changed
+55 -21

No files matched your search

+26
View File
@@ -25,6 +25,30 @@ test("ls with no file prints the empty message and writes nothing", () => {
assert.equal(existsSync(file), false);
});
test("no command lists the todos", () => {
const file = tempFile();
run(file, "add", "a");
const res = run(file);
assert.equal(res.status, 0);
assert.equal(res.stdout.trim(), "[ ] 1. a");
});
test("add trims surrounding whitespace from the text", () => {
const file = tempFile();
run(file, "add", " buy", "milk ");
assert.deepEqual(JSON.parse(readFileSync(file, "utf8")), [{ id: 1, text: "buy milk", done: false }]);
});
test("add fails readably when no ids are left, without saving", () => {
const file = tempFile();
const before = JSON.stringify([{ id: Number.MAX_SAFE_INTEGER - 1, text: "a", done: false }]);
writeFileSync(file, before);
const res = run(file, "add", "b");
assert.equal(res.status, 1);
assert.match(res.stderr, /^No ids left/);
assert.equal(readFileSync(file, "utf8"), before);
});
test("add with no text fails without saving", () => {
const file = tempFile();
const res = run(file, "add", " ");
@@ -42,6 +66,8 @@ for (const [label, args, message] of [
["padded id", [" 1 "], /Invalid id: 1 /],
["empty id", [""], /Invalid id: \n/],
["unsafe id", ["99999999999999999999"], /Invalid id: 99999999999999999999/],
["leading-zero id", ["01"], /Invalid id: 01/],
["zero id", ["0"], /Invalid id: 0/],
["unknown id", ["99"], /No todo with id 99/],
]) {
for (const cmd of ["done", "rm"]) {