diff --git a/intake/src/github.ts b/intake/src/github.ts index d065c20..a1e0eb9 100644 --- a/intake/src/github.ts +++ b/intake/src/github.ts @@ -11,9 +11,12 @@ export function github(repo: string, token: string): Issues { }, body: init.body ? JSON.stringify(init.body) : undefined, }); + if (res.status === 404 || res.status === 410) throw new Gone(); if (!res.ok) throw new Error(`GitHub ${init.method ?? 'GET'} ${path}: ${res.status} ${await res.text()}`); return res.json() as Promise; } + // an issue deleted by hand answers 410 (404 if the repo can't see it any more) + class Gone extends Error {} type Issue = { number: number; body: string | null }; return { async find(text) { @@ -23,7 +26,9 @@ export function github(repo: string, token: string): Issues { async create(title, body, labels) { return (await call('/issues', { method: 'POST', body: { title, body, labels } })).number; }, - async read(n) { return (await call(`/issues/${n}`)).body ?? ''; }, + async read(n) { + try { return (await call(`/issues/${n}`)).body ?? ''; } catch (e) { if (e instanceof Gone) return null; throw e; } + }, async edit(n, body) { await call(`/issues/${n}`, { method: 'PATCH', body: { body } }); }, }; } diff --git a/intake/src/run.ts b/intake/src/run.ts index cc61208..69d3ddc 100644 --- a/intake/src/run.ts +++ b/intake/src/run.ts @@ -20,7 +20,8 @@ export interface Issues { /** An issue whose text holds `text`, among the newest ones. */ find(text: string): Promise; create(title: string, body: string, labels: string[]): Promise; - read(n: number): Promise; + /** The issue's text, or null if it was deleted. */ + read(n: number): Promise; edit(n: number, body: string): Promise; } export type FileName = 'shot.jpg' | 'ui.jpg' | 'save.json'; @@ -63,7 +64,8 @@ export async function run(store: Store, issues: Issues, files: Files, now = Date // a week on: the issue keeps the note, and says the picture and the save are gone for (const old of await store.expired(now - KEEP_MS)) { - if (old.issue) await issues.edit(old.issue, expired(await issues.read(old.issue))); + const text = old.issue ? await issues.read(old.issue) : null; + if (old.issue && text !== null) await issues.edit(old.issue, expired(text)); await store.remove(old.id); done.expired++; } diff --git a/test/intake.test.ts b/test/intake.test.ts index 54f0950..548260f 100644 --- a/test/intake.test.ts +++ b/test/intake.test.ts @@ -29,7 +29,7 @@ function services(reports: StoredReport[], old: { id: string; issue?: number }[] const issues: Issues = { async find(text) { return [...existing].find(([, b]) => b.includes(text))?.[0] ?? null; }, async create(t, b, labels) { made.push({ title: t, body: b, labels }); existing.set(100 + made.length, b); return 100 + made.length; }, - async read(n) { return existing.get(n) ?? ''; }, + async read(n) { return existing.get(n) ?? null; }, async edit(n, b) { edits.set(n, b); }, }; const put: string[] = [], log: string[] = []; @@ -100,6 +100,13 @@ describe('the intake', () => { expect(s.edits.get(3)).toBe('_Picture deleted after 7 days._'); expect(s.removed).toEqual(['old', 'lost']); }); + + it('a week on, still deletes the report when its issue was deleted by hand', async () => { + const s = services([], [{ id: 'gone', issue: 2 }, { id: 'old', issue: 3 }]); + expect((await run(s.store, s.issues, s.files, NOW)).expired).toBe(2); + expect([...s.edits.keys()]).toEqual([3]); + expect(s.removed).toEqual(['gone', 'old']); + }); }); describe("a player's words in an issue", () => {