// Package calendartest tests the app from the outside: real HTTP // requests against the real router, wired exactly as // cmd/calendar/main.go wires it. // // The TDD loop: write a failing test here, make it pass in // internal/calendar, repeat. package calendartest import ( "net/http" "net/http/httptest" "net/url" "testing" calendar "amadan.net/oficina/calendar/internal/calendar" ) // testOrigin is the origin newApp wires and post presents as its // same-origin evidence — the pair a browser form submission would be. const testOrigin = calendar.TestOrigin // newApp builds the whole app per test over a fresh temp database. func newApp(t *testing.T) *calendar.App { t.Helper() return calendar.NewForTest(t, t.TempDir()) } // signedIn builds an app and a session cookie for address, by driving // the real sign-in flow. func signedIn(t *testing.T, address string) (*calendar.App, *http.Cookie) { t.Helper() app := newApp(t) return app, app.SignInForTest(t, address) } func get(t *testing.T, h http.Handler, target string) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, target, nil)) return rec } // getAs is get with a session attached. func getAs(t *testing.T, h http.Handler, session *http.Cookie, target string) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() h.ServeHTTP(rec, calendar.RequestForTest(session, http.MethodGet, target, nil)) return rec } // follow is getAs plus one redirect hop. // // "/" answers 303 to the viewer's own today, because Today and Day are // one page rather than two subtly different ones. A test that wants the // page rather than the redirect says so by using this. func follow(t *testing.T, h http.Handler, session *http.Cookie, target string) *httptest.ResponseRecorder { t.Helper() rec := getAs(t, h, session, target) if rec.Code != http.StatusSeeOther && rec.Code != http.StatusFound { return rec } loc := rec.Header().Get("Location") if loc == "" { t.Fatalf("GET %s: %d with no Location", target, rec.Code) } return getAs(t, h, session, loc) } func post(t *testing.T, h http.Handler, target string, form url.Values) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() h.ServeHTTP(rec, calendar.RequestForTest(nil, http.MethodPost, target, form)) return rec } // postAs is post with a session attached. func postAs(t *testing.T, h http.Handler, session *http.Cookie, target string, form url.Values) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() h.ServeHTTP(rec, calendar.RequestForTest(session, http.MethodPost, target, form)) return rec } // openLink is opening an emailed link: a GET that must spend nothing and // answer with the confirm door. It returns the door's form action and the // token it carries, read off the page rather than the link, so a door // that dropped or mangled the token fails here. func openLink(t *testing.T, h http.Handler, link string) (action, token string) { t.Helper() rec := get(t, h, link) if rec.Code != http.StatusOK || len(rec.Result().Cookies()) != 0 { t.Fatalf("GET %s = %d with %d cookies, want 200 and the confirm door, minting nothing", link, rec.Code, len(rec.Result().Cookies())) } action, token, ok := calendar.ConfirmFormForTest(rec.Body.String()) if !ok { t.Fatalf("GET %s: no confirm form on the page:\n%s", link, rec.Body.String()) } return action, token } // postLink is pressing the confirm door's Sign in button: the token, // POSTed back to the door's action with a browser's same-origin // evidence. This is the request that spends the link. func postLink(t *testing.T, h http.Handler, action, token string) *httptest.ResponseRecorder { t.Helper() r := calendar.RequestForTest(nil, http.MethodPost, action, url.Values{"token": {token}}) r.Header.Set("Sec-Fetch-Site", "same-origin") rec := httptest.NewRecorder() h.ServeHTTP(rec, r) return rec } // serve runs one request through h. func serve(h http.Handler, r *http.Request) *httptest.ResponseRecorder { rec := httptest.NewRecorder() h.ServeHTTP(rec, r) return rec }