| 1 | package memoria |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "net/http/cookiejar" |
| 6 | "net/http/httptest" |
| 7 | "net/url" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | ) |
| 11 | |
| 12 | // cookieBrowser carries cookies between requests the way a browser does, |
| 13 | // deletions included, so a test can visit pages in an order and see what |
| 14 | // each visit spent. |
| 15 | type cookieBrowser struct { |
| 16 | t *testing.T |
| 17 | h http.Handler |
| 18 | jar *cookiejar.Jar |
| 19 | } |
| 20 | |
| 21 | var cookieBase = &url.URL{Scheme: "http", Host: "example.com", Path: "/"} |
| 22 | |
| 23 | func newCookieBrowser(t *testing.T, h http.Handler) *cookieBrowser { |
| 24 | jar, err := cookiejar.New(nil) |
| 25 | if err != nil { |
| 26 | t.Fatal(err) |
| 27 | } |
| 28 | return &cookieBrowser{t: t, h: h, jar: jar} |
| 29 | } |
| 30 | |
| 31 | func (b *cookieBrowser) keep(rec *httptest.ResponseRecorder) { |
| 32 | b.jar.SetCookies(cookieBase, rec.Result().Cookies()) |
| 33 | } |
| 34 | |
| 35 | func (b *cookieBrowser) visit(path string) string { |
| 36 | b.t.Helper() |
| 37 | req := httptest.NewRequest(http.MethodGet, path, nil) |
| 38 | for _, c := range b.jar.Cookies(cookieBase) { |
| 39 | req.AddCookie(c) |
| 40 | } |
| 41 | rec := httptest.NewRecorder() |
| 42 | b.h.ServeHTTP(rec, req) |
| 43 | if rec.Code != http.StatusOK { |
| 44 | b.t.Fatalf("GET %s answered %d, want 200", path, rec.Code) |
| 45 | } |
| 46 | b.keep(rec) |
| 47 | return rec.Body.String() |
| 48 | } |
| 49 | |
| 50 | // With both notices pending, each page shows its own once and leaves the |
| 51 | // other alone. |
| 52 | // |
| 53 | // Astra, 2026-09-29: clearing the wrong cookie passed make ci. Taking the |
| 54 | // index's notice then erased a pending upload refusal and left the delete |
| 55 | // notice to be said again. Both orders are run because that mutation fails |
| 56 | // differently in each. |
| 57 | // |
| 58 | // MUTATION-TESTED 2026-09-29, reverted: the clearing cookie in |
| 59 | // notice.take named string(addNotice) instead of string(n) → "/add no |
| 60 | // longer shows its notice after a visit to /" and "/ showed its notice a |
| 61 | // second time (/ first)". |
| 62 | func TestEachPageSpendsOnlyItsOwnNotice(t *testing.T) { |
| 63 | const uploaded, deleted = "scan.png is not text", "The delete did not happen" |
| 64 | pages := map[string]string{"/": deleted, "/add": uploaded} |
| 65 | for _, order := range [][2]string{{"/", "/add"}, {"/add", "/"}} { |
| 66 | h, _ := appWithAWorkingIndex(t) |
| 67 | b := newCookieBrowser(t, h) |
| 68 | seed := httptest.NewRecorder() |
| 69 | addNotice.set(seed, "danger", uploaded) |
| 70 | indexNotice.set(seed, "danger", deleted) |
| 71 | b.keep(seed) |
| 72 | |
| 73 | first, second := order[0], order[1] |
| 74 | if page := b.visit(first); !strings.Contains(page, pages[first]) { |
| 75 | t.Fatalf("%s did not show its notice with both pending", first) |
| 76 | } |
| 77 | if page := b.visit(second); !strings.Contains(page, pages[second]) { |
| 78 | t.Errorf("%s no longer shows its notice after a visit to %s", second, first) |
| 79 | } |
| 80 | for path, said := range pages { |
| 81 | if strings.Contains(b.visit(path), said) { |
| 82 | t.Errorf("%s showed its notice a second time (%s first); it is one-shot", path, first) |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | |