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 "fmt"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9)
10
11// A viewer is not offered an edit affordance the server would refuse.
12//
13// handleTable admits anyone who may READ — sheetFromURL takes any role —
14// while handleTableWrite 404s a viewer or a commenter. So the page told
15// every reader "every cell here is editable", gave them a link per cell
16// that opened a form, and answered the submit with a 404. Offering an
17// affordance and then refusing it is worse than not offering it.
18//
19// The claim, the anchors and the ?edit= form are all gated now, and the
20// gate is a coordinate no cell can equal rather than a flag the template
21// is trusted to honour — the same shape the anonymous link view uses, so
22// the read-only view cannot grow an edit form by somebody adding a
23// branch to the template.
24//
25// The last assertion is the one that keeps this honest: an owner must
26// still get everything, or a gate that is simply stuck closed would pass
27// every check above.
28func TestTheTableViewOffersAViewerNothingToEdit(t *testing.T) {
29 app, cookies := signIn(t)
30 s, _ := newSheet(t, app, cookies, "Read only")
31 guestOn(t, app, s.ID, "ada@elsewhere.example", SheetViewer)
32 guestCookies := signInAs(t, app, "ada@elsewhere.example")
33
34 path := fmt.Sprintf("/sheets/%d/table", s.ID)
35 rec := do(t, app, as(httptest.NewRequest(http.MethodGet, path, nil), guestCookies))
36 if rec.Code != http.StatusOK {
37 t.Fatalf("a viewer cannot open the table view: %d", rec.Code)
38 }
39 body := rec.Body.String()
40
41 if strings.Contains(body, "editable") {
42 t.Error("the table view tells a viewer the cells are editable, and " +
43 "the write route answers that viewer with a 404")
44 }
45 if strings.Contains(body, "edit%3D") || strings.Contains(body, "edit=") {
46 t.Error("the table view gives a viewer per-cell edit links")
47 }
48
49 // Asking for the form by hand does not produce one either.
50 forced := path + "?edit=0,0"
51 rec = do(t, app, as(httptest.NewRequest(http.MethodGet, forced, nil), guestCookies))
52 if rec.Code != http.StatusOK {
53 t.Fatalf("GET the table view with an edit coordinate as a viewer = %d", rec.Code)
54 }
55 if strings.Contains(rec.Body.String(), `name="raw"`) {
56 t.Error("an edit coordinate handed a viewer a cell form; read-only " +
57 "must be expressed in the data, not left to the template")
58 }
59
60 // The owner still gets all of it, or the gate is too wide.
61 rec = do(t, app, as(httptest.NewRequest(http.MethodGet, path, nil), cookies))
62 owner := rec.Body.String()
63 if !strings.Contains(owner, "editable") {
64 t.Error("the owner lost the editable claim too — the gate is too wide")
65 }
66 if !strings.Contains(owner, "edit=") && !strings.Contains(owner, "edit%3D") {
67 t.Error("the owner lost the edit links too — the gate is too wide")
68 }
69}
70