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
3// roleFor is the single place both sources of access to a sheet are
4// decided — an explicit grant, and team standing — so it is also the
5// only place the "does this sheet exist for anybody" question has to
6// be asked. These cover the two ways it can be wrong.
7
8import (
9 "context"
10 "net/http"
11 "net/http/httptest"
12 "strconv"
13 "testing"
14 "time"
15
16 "amadan.net/oficina/sheets/internal/sheets/store"
17)
18
19// TestAFillingSheetIsClosedToItsOwnImporter is the blocker the
20// follow-up review found: every import mints an explicit owner grant
21// for the person who started it, and roleFor used to hand back that
22// grant's role without ever asking whether the sheet was one anybody
23// could open. The grid API and the live stream gate on roleFor alone,
24// so that was direct read and write access to a sheet no list shows.
25func TestAFillingSheetIsClosedToItsOwnImporter(t *testing.T) {
26 app, cookies := signIn(t)
27 importer := signedInMember(t, app)
28
29 s, err := app.createFillingSheet("half a workbook", importer)
30 if err != nil {
31 t.Fatalf("createFillingSheet: %v", err)
32 }
33 if _, has := app.roleFor(s.ID, importer); has {
34 t.Error("the importer holds a role on a sheet that is still filling")
35 }
36
37 sheetID := strconv.FormatUint(s.ID, 10)
38 tabID := strconv.FormatUint(s.TabID, 10)
39 base := "/api/sheets/" + sheetID + "/tabs/" + tabID
40
41 for _, tc := range []struct{ name, path string }{
42 {"grid cells", base + "/cells"},
43 {"live stream", base + "/stream"},
44 } {
45 // A deadline, because the stream route is the one that does NOT
46 // return when it lets you in: refused it 404s at once, allowed
47 // it holds the connection open by design. Without this the
48 // regression shape is a test that hangs until CI gives up,
49 // which is a worse signal than a red line. Measured: removing
50 // the roleFor check turns this from a 404 into a hang.
51 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
52 req := as(httptest.NewRequest(http.MethodGet, tc.path, nil), cookies).WithContext(ctx)
53 rec := do(t, app, req)
54 cancel()
55 if rec.Code != http.StatusNotFound {
56 t.Errorf("%s answered %d on a filling sheet, want 404", tc.name, rec.Code)
57 }
58 }
59
60 // Once filled it is ordinary again, so this is a gate and not a
61 // wall.
62 if err := app.markFilled(s.ID); err != nil {
63 t.Fatalf("markFilled: %v", err)
64 }
65 if _, has := app.roleFor(s.ID, importer); !has {
66 t.Error("the importer lost their role on a sheet that finished filling")
67 }
68}
69
70// TestADeletedSheetIsClosedToAGrantHolder is the same hole in the shape
71// it had before import existed: a grant outlived the sheet it pointed
72// at, and roleFor honoured it.
73func TestADeletedSheetIsClosedToAGrantHolder(t *testing.T) {
74 app := newTestApp(t)
75 owner := testMember(t, app)
76
77 s, err := app.createSheet("a sheet about to go", owner)
78 if err != nil {
79 t.Fatalf("createSheet: %v", err)
80 }
81 if _, has := app.roleFor(s.ID, owner); !has {
82 t.Fatal("the owner holds no role on a live sheet; fixture is wrong")
83 }
84
85 now := time.Now().UTC()
86 if err := app.db.Model(&store.Sheet{}).Where("id = ?", s.ID).
87 Update("deleted_at", now).Error; err != nil {
88 t.Fatalf("soft delete: %v", err)
89 }
90 if _, has := app.roleFor(s.ID, owner); has {
91 t.Error("a grant holder keeps their role on a deleted sheet")
92 }
93}
94