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 "net/url"
8 "strings"
9 "testing"
10)
11
12// A sheet can never be left without an owner — through ANY door.
13//
14// TestTheLastOwnerCannotBeRemoved already covers revoke. This covers the
15// other one, which was open: `grant` upserts on (sheet_id, member_id),
16// so re-sharing with somebody who already has access CHANGES their role.
17// Nothing counted owners on that path.
18//
19// So the sole owner typing their own address into the share form and
20// picking "viewer" demoted themselves. Zero owner grants remain, mayShare
21// is false for everybody, and — as the revoke path's own comment says —
22// there is no admin path to repair it. One request, no undo, sheet
23// permanently unshareable.
24//
25// It is a one-line form submission, not a contrived sequence, which is
26// what makes it worth a test rather than a note.
27func TestAnOwnerCannotDemoteThemselvesToNobody(t *testing.T) {
28 app, cookies := signIn(t)
29 s, _ := newSheet(t, app, cookies, "Only owner")
30
31 form := url.Values{}
32 form.Set("address", "paul@example.com")
33 form.Set("role", string(SheetViewer))
34 req := httptest.NewRequest(http.MethodPost,
35 fmt.Sprintf("/sheets/%d/share", s.ID), strings.NewReader(form.Encode()))
36 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
37 rec := do(t, app, as(req, cookies))
38 if rec.Code != http.StatusSeeOther {
39 t.Fatalf("share POST = %d, want 303", rec.Code)
40 }
41
42 // Whatever the screen says, the sheet must still have an owner.
43 people, err := app.grantsOn(s.ID)
44 if err != nil {
45 t.Fatal(err)
46 }
47 owners := 0
48 for _, p := range people {
49 if p.Role == SheetOwner {
50 owners++
51 }
52 }
53 if owners == 0 {
54 t.Fatal("the only owner demoted themselves: the sheet now has no " +
55 "owner, nobody can ever share it again, and there is no path " +
56 "to repair it")
57 }
58
59 // And the screen must still be reachable by them.
60 rec = do(t, app, as(httptest.NewRequest(http.MethodGet,
61 fmt.Sprintf("/sheets/%d/share", s.ID), nil), cookies))
62 if rec.Code != http.StatusOK {
63 t.Fatalf("the owner can no longer open the share screen: %d", rec.Code)
64 }
65}
66
67// Demoting one of two owners is fine, and must stay fine — the guard
68// above must refuse the LAST owner, not every demotion.
69func TestOneOfTwoOwnersCanBeDemoted(t *testing.T) {
70 app, cookies := signIn(t)
71 s, _ := newSheet(t, app, cookies, "Two owners")
72 guestOn(t, app, s.ID, "ada@elsewhere.example", SheetOwner)
73
74 form := url.Values{}
75 form.Set("address", "ada@elsewhere.example")
76 form.Set("role", string(SheetEditor))
77 req := httptest.NewRequest(http.MethodPost,
78 fmt.Sprintf("/sheets/%d/share", s.ID), strings.NewReader(form.Encode()))
79 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
80 if rec := do(t, app, as(req, cookies)); rec.Code != http.StatusSeeOther {
81 t.Fatalf("share POST = %d, want 303", rec.Code)
82 }
83
84 people, err := app.grantsOn(s.ID)
85 if err != nil {
86 t.Fatal(err)
87 }
88 for _, p := range people {
89 if p.Member.Address == "ada@elsewhere.example" && p.Role != SheetEditor {
90 t.Errorf("ada is %q, want editor: demoting a second owner is "+
91 "allowed and the guard has been drawn too wide", p.Role)
92 }
93 }
94}
95