tasks: confirm-read tests for 429, 401 and a failed call, and the fake's view 404 is 3014 (#1565)
Row 71. Test 18 gains three steps after the 404/503 step: the child's task
list answers 404 and its confirm read answers 429, 401 or status 0 (the
fake throws fetch failed). Each tick refuses with tracker-not-found and
the child stays. The fake answers a missing kanban view with code 3014,
the live v2.7.0 answer in the tick-62 record.
Built by Researcher's Haiku subagent (decision 129). Approved by Filbert
(28171) and Dewey (28172) on manifest 9ac3a56e. Sage's gate on e635ef1b:
every node suite and scripts/test-*.sh green.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -138,7 +138,8 @@ export class FakeVikunja {
|
|||||||
for (const id of gone) this.#projects.delete(id);
|
for (const id of gone) this.#projects.delete(id);
|
||||||
}
|
}
|
||||||
// Drops a view, its buckets and the tasks' places in them. The project still reads, and a read
|
// Drops a view, its buckets and the tasks' places in them. The project still reads, and a read
|
||||||
// of the view answers 404 with code 13001.
|
// of the view answers 404 with code 3014, as tasks.woltje.com v2.7.0 does (Sage's live probe,
|
||||||
|
// agents/sage/work/vikunja-setup/2026-10-11_tick-62.json, deletedViewProbe).
|
||||||
deleteView(view) {
|
deleteView(view) {
|
||||||
for (const [id, b] of this.#buckets) if (b.view === view) this.#buckets.delete(id);
|
for (const [id, b] of this.#buckets) if (b.view === view) this.#buckets.delete(id);
|
||||||
for (const t of this.#tasks.values()) t.buckets.delete(view);
|
for (const t of this.#tasks.values()) t.buckets.delete(view);
|
||||||
@@ -473,14 +474,14 @@ export class FakeVikunja {
|
|||||||
_buckets(user, [p, k], q) {
|
_buckets(user, [p, k], q) {
|
||||||
const r = this.#projectFor(user, p);
|
const r = this.#projectFor(user, p);
|
||||||
if (r.error) return r.error;
|
if (r.error) return r.error;
|
||||||
if (this.#views.get(k)?.project !== p) return { status: 404, body: { title: 'Not Found', status: 404, detail: 'This project view does not exist.', code: 13001 } };
|
if (this.#views.get(k)?.project !== p) return { status: 404, body: { title: 'Not Found', status: 404, detail: 'This project view does not exist.', code: 3014 } };
|
||||||
return { status: 200, body: page([...this.#buckets.values()].filter((b) => b.view === k).map((b) => this.#bucketJson(b)), q) };
|
return { status: 200, body: page([...this.#buckets.values()].filter((b) => b.view === k).map((b) => this.#bucketJson(b)), q) };
|
||||||
}
|
}
|
||||||
_board(user, [p, k], q) {
|
_board(user, [p, k], q) {
|
||||||
const r = this.#projectFor(user, p);
|
const r = this.#projectFor(user, p);
|
||||||
if (r.error) return r.error;
|
if (r.error) return r.error;
|
||||||
const v = this.#views.get(k);
|
const v = this.#views.get(k);
|
||||||
if (v?.project !== p) return { status: 404, body: { title: 'Not Found', status: 404, detail: 'This project view does not exist.', code: 13001 } };
|
if (v?.project !== p) return { status: 404, body: { title: 'Not Found', status: 404, detail: 'This project view does not exist.', code: 3014 } };
|
||||||
const f = q.get('filter') ?? '';
|
const f = q.get('filter') ?? '';
|
||||||
if (f && f !== 'done = false') return { status: 400, body: bad('unsupported filter in the fake') };
|
if (f && f !== 'done = false') return { status: 400, body: bad('unsupported filter in the fake') };
|
||||||
const per = Math.min(Number(q.get('per_page')) || 50, 50),
|
const per = Math.min(Number(q.get('per_page')) || 50, 50),
|
||||||
|
|||||||
@@ -430,8 +430,8 @@ test('a child whose kanban view is deleted still refuses the tick, and the recon
|
|||||||
const rootTask = await add(w, w.project, 'root');
|
const rootTask = await add(w, w.project, 'root');
|
||||||
await ready(w);
|
await ready(w);
|
||||||
const rows = () => w.store.get('SELECT (SELECT count(*) FROM events) + (SELECT count(*) FROM task_snapshots) n').n;
|
const rows = () => w.store.get('SELECT (SELECT count(*) FROM events) + (SELECT count(*) FROM task_snapshots) n').n;
|
||||||
// The board answers 404 with code 13001, and the project still reads, so the child hasn't left
|
// The board answers 404 with code 3014 (the live v2.7.0 answer, tick-62 record), and the project
|
||||||
// the tree. The tick reads the project, fails, and writes nothing, not even the root's edit.
|
// still reads, so the child hasn't left the tree. The tick reads the project, fails, and writes nothing, not even the root's edit.
|
||||||
w.fake.deleteView(w.fake.kanban(child).id);
|
w.fake.deleteView(w.fake.kanban(child).id);
|
||||||
await w.ui('PATCH', `/tasks/${rootTask}`, { title: 'root renamed' });
|
await w.ui('PATCH', `/tasks/${rootTask}`, { title: 'root renamed' });
|
||||||
await sleep(20);
|
await sleep(20);
|
||||||
@@ -459,6 +459,17 @@ test('a child whose kanban view is deleted still refuses the tick, and the recon
|
|||||||
w.fake.fault('GET', new RegExp(`^/projects/${child}/tasks$`), 404);
|
w.fake.fault('GET', new RegExp(`^/projects/${child}/tasks$`), 404);
|
||||||
w.fake.fault('GET', new RegExp(`^/projects/${child}$`), 503);
|
w.fake.fault('GET', new RegExp(`^/projects/${child}$`), 503);
|
||||||
await assert.rejects(w.adapter.tick('demo'), { code: 'tracker-not-found' });
|
await assert.rejects(w.adapter.tick('demo'), { code: 'tracker-not-found' });
|
||||||
|
// The confirm read answers another 4xx: the tick still refuses, and the child stays.
|
||||||
|
w.fake.fault('GET', new RegExp(`^/projects/${child}/tasks$`), 404);
|
||||||
|
w.fake.fault('GET', new RegExp(`^/projects/${child}$`), 429);
|
||||||
|
await assert.rejects(w.adapter.tick('demo'), { code: 'tracker-not-found' });
|
||||||
|
w.fake.fault('GET', new RegExp(`^/projects/${child}/tasks$`), 404);
|
||||||
|
w.fake.fault('GET', new RegExp(`^/projects/${child}$`), 401);
|
||||||
|
await assert.rejects(w.adapter.tick('demo'), { code: 'tracker-not-found' });
|
||||||
|
// The confirm read's call fails (status 0, which the client returns for a thrown fetch): the tick refuses.
|
||||||
|
w.fake.fault('GET', new RegExp(`^/projects/${child}/tasks$`), 404);
|
||||||
|
w.fake.fault('GET', new RegExp(`^/projects/${child}$`), 0);
|
||||||
|
await assert.rejects(w.adapter.tick('demo'), { code: 'tracker-not-found' });
|
||||||
await w.ui('PATCH', `/tasks/${task}`, { title: 'child renamed' });
|
await w.ui('PATCH', `/tasks/${task}`, { title: 'child renamed' });
|
||||||
await sleep(20);
|
await sleep(20);
|
||||||
await w.adapter.tick('demo');
|
await w.adapter.tick('demo');
|
||||||
|
|||||||
Reference in New Issue
Block a user