| 1 | package memoriatest |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | |
| 7 | memoria "amadan.net/oficina/memoria/internal/memoria" |
| 8 | "amadan.net/oficina/memoria/internal/memoria/notion" |
| 9 | ) |
| 10 | |
| 11 | // TestAFailureAfterImportKeepsWhatWeHold. |
| 12 | // |
| 13 | // A page imports fine, and on a LATER run it 404s — deleted or unshared |
| 14 | // in the meantime. Recording that failure used to write document_id=0 and |
| 15 | // an empty ingested edit time, which erased memoria's own record that it |
| 16 | // holds the document. Two consequences, both wrong: "Imported" dropped on |
| 17 | // the status page for a document that is still there, and the next run |
| 18 | // had to fetch and re-ingest a page that had never actually changed. |
| 19 | // |
| 20 | // The document itself was never at risk — memoria does not delete on |
| 21 | // absence — which is exactly why this was easy to miss. |
| 22 | // |
| 23 | // Found by a code review. |
| 24 | // |
| 25 | // MUTATION-TESTED 2026-09-05: passing 0, "", "" to MarkPage from |
| 26 | // markGone (the pre-fix behaviour) makes this fail. Reverted. |
| 27 | func TestAFailureAfterImportKeepsWhatWeHold(t *testing.T) { |
| 28 | src := newSource() |
| 29 | src.add("root", "Handbook", "", "Expenses are reimbursed within thirty days.") |
| 30 | src.add("kid", "Onboarding", "root", "Week one covers travel booking.", "Handbook") |
| 31 | |
| 32 | h, app, cid, _ := connectorApp(t, src) |
| 33 | sync(t, h, app, cid) |
| 34 | |
| 35 | _, importedBefore, err := app.Store().CountPages(context.Background(), cid) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | if importedBefore != 2 { |
| 40 | t.Fatalf("imported %d before the failure, want 2 — nothing to lose otherwise", importedBefore) |
| 41 | } |
| 42 | |
| 43 | // The page goes away upstream. |
| 44 | src.failWith["kid"] = ¬ion.Error{Code: notion.CodeTraversalFailed, Status: 404} |
| 45 | sync(t, h, app, cid) |
| 46 | |
| 47 | run := latest(t, app, cid) |
| 48 | if run.State != memoria.RunDone { |
| 49 | t.Fatalf("run state %q, want done", run.State) |
| 50 | } |
| 51 | if run.FailedPages != 1 { |
| 52 | t.Errorf("failed_pages %d, want 1", run.FailedPages) |
| 53 | } |
| 54 | |
| 55 | // The document is still held, and memoria still KNOWS it holds it. |
| 56 | if hits, _ := searchAsReader(app.Store(), context.Background(), "travel booking", 10); len(hits) == 0 { |
| 57 | t.Error("the document was removed when its page 404'd; memoria does not delete on absence") |
| 58 | } |
| 59 | _, importedAfter, err := app.Store().CountPages(context.Background(), cid) |
| 60 | if err != nil { |
| 61 | t.Fatal(err) |
| 62 | } |
| 63 | if importedAfter != importedBefore { |
| 64 | t.Errorf("imported dropped %d→%d because a fetch failed; the record that memoria holds "+ |
| 65 | "the document was erased, though the document is still there", |
| 66 | importedBefore, importedAfter) |
| 67 | } |
| 68 | } |
| 69 | |