| 1 | package docs_test |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "net/http" |
| 6 | "net/http/httptest" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | |
| 10 | "github.com/carlosframework/docs/internal/docs" |
| 11 | ) |
| 12 | |
| 13 | // A writer grantee can actually write, over HTTP, and the owner sees it. |
| 14 | // |
| 15 | // The branch tested the writer's authorization at the store |
| 16 | // (grants_write_test.go asserts "not ErrNotWritable" for a one-byte |
| 17 | // update) and the reader's refusal at the route, and never the writer's |
| 18 | // success at the route. So if updatesPost regressed to owner-only Get, |
| 19 | // every existing test would still pass while collaborative editing — the |
| 20 | // point of a writer grant — was silently dead. (Branch review, finding |
| 21 | // 7d.) |
| 22 | func TestAWriterCanPostUpdatesOverHTTP(t *testing.T) { |
| 23 | app, _ := docs.NewForTest(t) |
| 24 | alice := signInCookies(t, app, "alice@example.com") |
| 25 | sqid := createDoc(t, app, alice, "Minutes") |
| 26 | bob := signInCookies(t, app, "bob@example.com") |
| 27 | post(t, app, alice, "/d/"+sqid+"/share", "address=bob@example.com&role=writer") |
| 28 | |
| 29 | rec := postBytes(t, app, bob, "/d/"+sqid+"/updates", docs.ReadFragmentFixture(t, "hello")) |
| 30 | if rec.Code != http.StatusNoContent { |
| 31 | t.Fatalf("writer POST /updates: status %d, want 204", rec.Code) |
| 32 | } |
| 33 | |
| 34 | // The owner sees what the writer wrote. Without this the test passes |
| 35 | // on a 204 that dropped the update on the floor. |
| 36 | if body := get(t, app, alice, "/d/"+sqid).Body.String(); !strings.Contains(body, "hello") { |
| 37 | t.Error("the owner's page does not show the writer's text") |
| 38 | } |
| 39 | |
| 40 | // And a grantee can read the update log, which is how the editor |
| 41 | // catches up — also untested before. |
| 42 | logRec := get(t, app, bob, "/d/"+sqid+"/updates") |
| 43 | if logRec.Code != http.StatusOK || logRec.Body.Len() == 0 { |
| 44 | t.Errorf("writer GET /updates: status %d, %d bytes", logRec.Code, logRec.Body.Len()) |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | // postBytes is post for a binary body: the update routes take raw CRDT |
| 49 | // bytes, not a form. |
| 50 | func postBytes(t *testing.T, app *docs.App, cookies []*http.Cookie, path string, body []byte) *httptest.ResponseRecorder { |
| 51 | t.Helper() |
| 52 | req := httptest.NewRequest(http.MethodPost, path, bytes.NewReader(body)) |
| 53 | req.Header.Set("Content-Type", "application/octet-stream") |
| 54 | req.Header.Set("Origin", docs.DefaultOrigin) |
| 55 | for _, c := range cookies { |
| 56 | req.AddCookie(c) |
| 57 | } |
| 58 | rec := httptest.NewRecorder() |
| 59 | app.Handler().ServeHTTP(rec, req) |
| 60 | return rec |
| 61 | } |
| 62 | |