package calls import ( "bytes" "log/slog" "net/http" "net/http/httptest" "strings" "testing" "time" ) // A panic inside the router is the 500 page with a reference and the // viewer's chrome for a browser, and plain text for a fetch (spec §3.3). func TestAPanicIsTheErrorPageWithAReference(t *testing.T) { f := newHomeRouterFixture(t) boom := f.app.recoverToErrorPage(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("boom") })) r := httptest.NewRequest("GET", homeTestOrigin+"/m/x", nil) r.Header.Set("Accept", "text/html") w := httptest.NewRecorder() boom.ServeHTTP(w, r) body := w.Body.String() if w.Code != 500 || !strings.Contains(body, "Something went wrong · Calls") || !strings.Contains(body, "Reference:") || !strings.Contains(body, "
") || !strings.Contains(body, `href="/m/x">Try again`) { t.Fatalf("panic page: %d\n%s", w.Code, body) } r = httptest.NewRequest("GET", homeTestOrigin+"/m/x/info", nil) w = httptest.NewRecorder() boom.ServeHTTP(w, r) if w.Code != 500 || !strings.HasPrefix(w.Header().Get("Content-Type"), "text/plain") { t.Fatalf("panic for a fetch: %d %q", w.Code, w.Header().Get("Content-Type")) } } // Astra on #24, finding 3 (Memoria #21's shape): the test above builds // recoverToErrorPage by hand, so taking it out of the router left it // green. This one panics inside a REAL route — the return cookie's clock, // read by /signin/local?return_to= — through the production router, // signed in and out, and checks the 500 page with a reference, the // viewer's own chrome, and that the panic's value reaches neither the // page nor the log. func TestARoutePanicIsTheErrorPageThroughTheRouter(t *testing.T) { const sentinel = "secret-sentinel-title" for _, signed := range []bool{true, false} { f := newHomeRouterFixture(t) if signed { f.local(t) } var logs bytes.Buffer f.app.logger = slog.New(slog.NewTextHandler(&logs, nil)) f.app.store.clk.set(func() time.Time { panic(sentinel) }) r := httptest.NewRequest("GET", homeTestOrigin+"/signin/local?return_to=%2Fsettings", nil) r.Header.Set("Accept", "text/html") for _, c := range f.cookies { r.AddCookie(c) } w := httptest.NewRecorder() func() { defer func() { if v := recover(); v != nil { t.Fatalf("signed in=%v: the panic escaped the router: %v", signed, v) } }() f.handler.ServeHTTP(w, r) }() b := w.Body.String() if w.Code != http.StatusInternalServerError || !strings.Contains(b, "Something went wrong · Calls") || !strings.Contains(b, "rst-error-ref") { t.Errorf("signed in=%v: %d, want the 500 page with a reference:\n%s", signed, w.Code, b) } if strings.Contains(b, "estilo-account") != signed { t.Errorf("signed in=%v: the 500 page's chrome is not the viewer's", signed) } if strings.Contains(b, sentinel) || strings.Contains(logs.String(), sentinel) { t.Errorf("signed in=%v: the panic's value reached the page or the log", signed) } if !strings.Contains(logs.String(), "ref=") { t.Errorf("signed in=%v: no reference in the log: %s", signed, logs.String()) } } }