package memoriatest import ( "context" "io" "log/slog" "net/http" "path/filepath" "strconv" "strings" "testing" "amadan.net/rastrillo/rastrillo/db" "amadan.net/oficina/memoria/internal/memoria" "amadan.net/oficina/memoria/internal/memoria/provider" ) // noLayoutHere is every declared GET route that renders no page layout // under this harness, and why. A route missing from here that renders no // footer fails the walk. var noLayoutHere = map[string]string{ // Each reason is VERIFIED, not assumed — a review found two of the // first set were guesses stated as facts: "/native/*: not wired by // AppForTest" (it is wired) and "/auth/*: sign-in redirect" (with Home // configured, four of them render the footer on failure). "GET /search": "the results fragment search.js swaps into a page; renders the results define alone, no layout", "GET /api/native/session": "JSON for the native client", "GET /native/start": "wired by AppForTest, but 404 without a pending native request id; with one it redirects (303)", "GET /native/authorize": "wired by AppForTest, but 404 without a pending id; with one it serves the native library's standalone consent template, which has no layout footer", "GET /auth/local/complete": "a redirect on every GET path — the Home navigation guard either passes and redirects, or redirects; it renders nothing (read in home/connect navigation.go)", // The sign-in pages carry no footer at all: the count and the egress // claim are said only to somebody signed in (layout.html; shared chrome // §9.2 retires the claim there). memoria.TestHomePagesStateTheModelSwitch // asserts the absence with Home configured. "GET /signed-out": "a sign-in page: no footer by design (layout.html's .Frame.Signed)", "GET /signin/local": "Home-only, and a sign-in page: no footer by design — memoria.TestHomePagesStateTheModelSwitch", // AppForTest is signed in, and a signed-in GET /signin skips the form. "GET /signin": "signed in under this harness, so it answers 303 to the index (localSignin's shortcut) and renders nothing", "GET /auth/landed": "a redirect: where a verified link lands, and on to the remembered page (return.go)", // These render the footer when Home is configured, which this harness // does not do. They are checked there, by name, including their // failure pages at 400. "GET /account/home": "Home-only — memoria.TestHomePagesStateTheModelSwitch", "GET /account/home/confirm": "Home-only — memoria.TestHomePagesStateTheModelSwitch", "GET /auth/start": "Home-only; its failure page renders the footer — memoria.TestHomePagesStateTheModelSwitch", "GET /auth/finish": "Home-only; its failure page renders the footer — memoria.TestHomePagesStateTheModelSwitch", "GET /auth/callback": "no sign-in wired here; with Home its LocalGuard failure renders the footer — memoria.TestHomePagesStateTheModelSwitch", "GET /auth/verify": "no sign-in wired here; with Home its LocalGuard failure renders the footer — memoria.TestHomePagesStateTheModelSwitch", } // countingModel is a stand-in model that counts calls and points at any // "address: " line, as a compliant model would. type countingModel struct{ calls int } func (m *countingModel) ExtractAddresses(_ context.Context, passages []string) ([]provider.Pointer, error) { m.calls++ var out []provider.Pointer for i, p := range passages { if j := strings.Index(p, "address: "); j >= 0 { line := p[j+len("address: "):] if k := strings.IndexByte(line, '\n'); k >= 0 { line = line[:k] } out = append(out, provider.Pointer{Passage: i, Text: line}) } } return out, nil } // appWithModel is newApp with the model layer switched on by a stand-in, // and a document stored so every page has something to show. func appWithModel(t *testing.T, m memoria.Extractor) (http.Handler, int64) { t.Helper() logger := slog.New(slog.NewTextHandler(io.Discard, nil)) d, err := db.Open(filepath.Join(t.TempDir(), "app.db"), logger) if err != nil { t.Fatal(err) } t.Cleanup(func() { d.Close() }) mux, a, err := memoria.AppForTest(d, testOrigin, testDEK(t), logger) if err != nil { t.Fatal(err) } if m != nil { a.UseFakeModel(m) } id, err := a.Store().Ingest(context.Background(), memoria.IngestSpec{ Source: "upload", Key: "office", Title: "Tito office", Body: "## Contact\n\nOffice address: 12 Main Street, Dublin 2", }) if err != nil { t.Fatal(err) } return mux, id } // footers requests every declared GET route and returns the footer of // each page that has one, keyed by route. Walking declaredChiRoutes rather // than a hand-written list is the point: the first build of §8.9 updated // the README and missed the footer on every page, and a list of pages // written by the same hand misses a page the same way. func footers(t *testing.T, h http.Handler, docID int64) map[string]string { t.Helper() out := map[string]string{} for route := range declaredChiRoutes { method, path, _ := strings.Cut(route, " ") if method != "GET" { continue } path = strings.ReplaceAll(path, "{id}", strconv.FormatInt(docID, 10)) rec := get(t, h, path) body := rec.Body.String() i := strings.Index(body, "