oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 rdb "amadan.net/rastrillo/rastrillo/db"
12 "amadan.net/rastrillo/rastrillo/migrate"
13)
14
15// TestSigninErrorNeverFallsThroughSilently is the reason signinError
16// exists. Every outcome on this page means one operational thing, so an
17// unrecognised code must still tell the person what to do.
18func TestSigninErrorNeverFallsThroughSilently(t *testing.T) {
19 for _, code := range []string{"1", "keymail", "expired", "rate", "address", "wat", "0"} {
20 msg, _ := signinError(code)
21 if msg == "" {
22 t.Errorf("err=%s produced no message", code)
23 continue
24 }
25 // Every error path has to leave a way forward.
26 low := strings.ToLower(msg)
27 if !strings.Contains(low, "new link") && !strings.Contains(low, "new one") &&
28 !strings.Contains(low, "a link instead") && !strings.Contains(low, "try again") &&
29 !strings.Contains(low, "email address") {
30 t.Errorf("err=%s says %q, with no next step", code, msg)
31 }
32 }
33 if msg, _ := signinError(""); msg != "" {
34 t.Errorf("bare page gave %q, want no message", msg)
35 }
36}
37
38// The sent state replaces the form (spec §9.2), rather than a notice
39// above a still-primary "Email me a link" that invites a second send.
40func TestSigninSentReplacesTheForm(t *testing.T) {
41 rec := httptest.NewRecorder()
42 New(Config{}).Router().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/signin?sent=1", nil))
43 body := rec.Body.String()
44 if !strings.Contains(body, "<h1>Check your email</h1>") || !strings.Contains(body, "<title>Check your email · Sheets</title>") {
45 t.Fatal("the sent state is not the Check your email page")
46 }
47 if strings.Contains(body, `name="address"`) || strings.Contains(body, "Email me a link") {
48 t.Fatal("the sent state still shows the form")
49 }
50 if !strings.Contains(body, `<a href="/signin">Use a different address</a>`) {
51 t.Fatal("the sent state has no way back to the form")
52 }
53}
54
55// TestSigninPageDoesNotEchoQuery: signinError selects from a fixed set
56// rather than echoing, so there is no path from the URL into the HTML.
57func TestSigninPageDoesNotEchoQuery(t *testing.T) {
58 rec := httptest.NewRecorder()
59 req := httptest.NewRequest(http.MethodGet, "/signin?err=%3Cscript%3Ealert(1)%3C/script%3E", nil)
60 New(Config{}).Router().ServeHTTP(rec, req)
61 if rec.Code != http.StatusOK {
62 t.Fatalf("GET /signin = %d", rec.Code)
63 }
64 if strings.Contains(rec.Body.String(), "<script>") {
65 t.Error("query value reached the page")
66 }
67 if !strings.Contains(rec.Body.String(), "couldn’t sign you in") {
68 t.Error("unknown err code did not get the fallback message")
69 }
70}
71
72// testAuth builds a real *auth.Auth over a temp database, so route
73// mounting is proven against the actual handlers rather than a stub.
74func testAuth(t *testing.T) *App {
75 t.Helper()
76 d, err := rdb.Open(filepath.Join(t.TempDir(), "test.db"), quietLogger())
77 if err != nil {
78 t.Fatalf("open db: %v", err)
79 }
80 t.Cleanup(func() { d.Close() })
81 if _, err := migrate.Apply(context.Background(), d, BootSchema); err != nil {
82 t.Fatalf("migrate: %v", err)
83 }
84 app := New(Config{DB: d.G, Admit: Admits([]string{"paul@example.com"})})
85 a, err := NewAuth(d.Writer(), AuthEnv{
86 Origin: "https://sheets.example.com",
87 InstanceKey: "test-instance-key",
88 Owners: []string{"paul@example.com"},
89 Mailer: nil, // auth falls back to a logging sender; no mail is sent in this test
90 }, quietLogger(), app)
91 if err != nil {
92 t.Fatalf("NewAuth: %v", err)
93 }
94 t.Cleanup(func() { CloseSigninMail(a) })
95 app.UseAuth(a)
96 return app
97}
98
99// TestAuthRoutesAreMounted proves auth's handlers are reachable — Verify
100// on POST as well as GET, or the confirm door's button gets a 405. It
101// exists because Router mounts them behind `if a.auth != nil`, and every
102// other test in this package passes nil — so without this, the whole
103// sign-in surface could be unmounted and the suite would stay green.
104func TestAuthRoutesAreMounted(t *testing.T) {
105 app := testAuth(t)
106 for _, tc := range []struct{ method, path string }{
107 {http.MethodPost, "/signin"},
108 {http.MethodGet, "/auth/callback"},
109 {http.MethodGet, "/auth/verify"},
110 {http.MethodPost, "/auth/verify"},
111 {http.MethodPost, "/signout"},
112 } {
113 rec := httptest.NewRecorder()
114 app.Router().ServeHTTP(rec, httptest.NewRequest(tc.method, tc.path, nil))
115 if rec.Code == http.StatusNotFound || rec.Code == http.StatusMethodNotAllowed {
116 t.Errorf("%s %s = %d, handler not mounted", tc.method, tc.path, rec.Code)
117 }
118 }
119}
120
121// TestAuthRoutesAbsentWithoutAuth is the control for the test above: it
122// pins that those codes really do mean "not mounted", so the assertion
123// there is measuring what it claims to.
124func TestAuthRoutesAbsentWithoutAuth(t *testing.T) {
125 app := New(Config{})
126 for _, tc := range []struct{ method, path string }{
127 {http.MethodPost, "/signin"},
128 {http.MethodGet, "/auth/verify"},
129 } {
130 rec := httptest.NewRecorder()
131 app.Router().ServeHTTP(rec, httptest.NewRequest(tc.method, tc.path, nil))
132 if rec.Code != http.StatusNotFound && rec.Code != http.StatusMethodNotAllowed {
133 t.Errorf("%s %s = %d with nil auth, want 404/405", tc.method, tc.path, rec.Code)
134 }
135 }
136}
137
138// TestSigninCarriesForce: when auth redirects a failed keymail sign-in
139// to ?force=1&err=keymail, the page must post force back, or Begin
140// bounces the user to keymail again and the loop has no exit.
141func TestSigninCarriesForce(t *testing.T) {
142 body := func(url string) string {
143 rec := httptest.NewRecorder()
144 New(Config{}).Router().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, url, nil))
145 return rec.Body.String()
146 }
147 with := body("/signin?force=1&err=keymail")
148 if !strings.Contains(with, `name="force"`) {
149 t.Error("force=1 page does not carry the force field")
150 }
151 if without := body("/signin"); strings.Contains(without, `name="force"`) {
152 t.Error("ordinary page carries a force field it was not given")
153 }
154}
155