| 1 | package docs_test |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/docs/internal/docs" |
| 9 | ) |
| 10 | |
| 11 | // A reader grantee must not be handed an editor. Found by the branch |
| 12 | // review, and the worst defect on this branch: `show` used GetFor |
| 13 | // (correct — a reader may read) and then rendered the owner's page |
| 14 | // verbatim, so ProseMirror mounted, the toolbar enabled, and the document |
| 15 | // became contenteditable for somebody the server will refuse. |
| 16 | // |
| 17 | // The server refusal was never in doubt: ApplyUpdate returns ErrNotWritable |
| 18 | // and updatesPost answers 404. The damage is upstream of that. The browser |
| 19 | // has already taken the keystrokes; the save queue classifies the 404 as |
| 20 | // permanent, drops the batch, and keeps accepting typing. The person is |
| 21 | // told "save failed" after writing a paragraph into a page that told them |
| 22 | // they could. |
| 23 | // |
| 24 | // "It fails safely on the server" is not a defence when the cost is a |
| 25 | // person's work. |
| 26 | func TestAReaderIsNotGivenAnEditor(t *testing.T) { |
| 27 | app, _ := docs.NewForTest(t) |
| 28 | alice := signInCookies(t, app, "alice@example.com") |
| 29 | sqid := createDoc(t, app, alice, "Minutes") |
| 30 | bob := signInCookies(t, app, "bob@example.com") |
| 31 | post(t, app, alice, "/d/"+sqid+"/share", "address=bob@example.com&role=reader") |
| 32 | |
| 33 | rec := get(t, app, bob, "/d/"+sqid) |
| 34 | if rec.Code != http.StatusOK { |
| 35 | t.Fatalf("GET /d/%s as a reader: status %d, want 200", sqid, rec.Code) |
| 36 | } |
| 37 | body := rec.Body.String() |
| 38 | |
| 39 | // The mount ELEMENT, not the string: `[docs-editor]` also appears a |
| 40 | // dozen times in this page's own <style> block, so banning the bare |
| 41 | // word would fail on a page that is perfectly correct — and would |
| 42 | // have hidden whether the element itself was gone. `docs-text |
| 43 | // docs-editor` is the attribute pair the mount point carries and the |
| 44 | // stylesheet never does. |
| 45 | // |
| 46 | // data-sqid is the bundle's only way to learn where to post, and the |
| 47 | // editor script is checked by its real served path rather than a |
| 48 | // guessed substring: the review found this test's earlier sibling |
| 49 | // banning "editor-entry", a string that appears on no page in this |
| 50 | // app, which is a ban that could never fail. |
| 51 | for _, banned := range []string{"docs-text docs-editor", "data-sqid", docs.TestEditorScriptPath()} { |
| 52 | if strings.Contains(body, banned) { |
| 53 | t.Errorf("a reader's document page ships %q", banned) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | // The content is still there. A read-only page that renders nothing is |
| 58 | // not the fix. |
| 59 | if !strings.Contains(body, "Minutes") { |
| 60 | t.Error("the reader's page does not show the document") |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // The writer keeps everything. Without this the fix above passes on a |
| 65 | // page that gives nobody an editor. |
| 66 | func TestAWriterKeepsTheEditor(t *testing.T) { |
| 67 | app, _ := docs.NewForTest(t) |
| 68 | alice := signInCookies(t, app, "alice@example.com") |
| 69 | sqid := createDoc(t, app, alice, "Minutes") |
| 70 | bob := signInCookies(t, app, "bob@example.com") |
| 71 | post(t, app, alice, "/d/"+sqid+"/share", "address=bob@example.com&role=writer") |
| 72 | |
| 73 | for who, cookies := range map[string][]*http.Cookie{"writer": bob, "owner": alice} { |
| 74 | body := get(t, app, cookies, "/d/"+sqid).Body.String() |
| 75 | for _, needed := range []string{"docs-text docs-editor", "data-sqid", docs.TestEditorScriptPath()} { |
| 76 | if !strings.Contains(body, needed) { |
| 77 | t.Errorf("the %s's page is missing %q", who, needed) |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | |