Files
cairnquire/apps/server/internal/httpserver/collab_handlers_test.go

142 lines
5.0 KiB
Go

package httpserver
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/tim/cairnquire/apps/server/internal/auth"
"github.com/tim/cairnquire/apps/server/internal/collaboration"
)
func loginTestUser(t *testing.T, server *apiTestServer) *http.Cookie {
t.Helper()
login := httptest.NewRecorder()
server.handler.ServeHTTP(login, jsonRequest(http.MethodPost, "/api/auth/login/password", map[string]string{
"email": "editor@example.com", "password": "very secure password",
}))
if login.Code != http.StatusOK {
t.Fatalf("login response = %d %q", login.Code, login.Body.String())
}
return cookieByName(t, login.Result().Cookies(), auth.SessionCookieName)
}
func authenticatedJSONRequest(t *testing.T, method, target string, body any, session *http.Cookie) *http.Request {
t.Helper()
request := jsonRequest(method, target, body)
request.AddCookie(session)
return request
}
func TestDocumentLoadsCollaborationControlsAndWatchFlow(t *testing.T) {
server := newAPITestServer(t)
session := loginTestUser(t, server)
pageRequest := httptest.NewRequest(http.MethodGet, "/docs/hello", nil)
pageRequest.AddCookie(session)
page := httptest.NewRecorder()
server.handler.ServeHTTP(page, pageRequest)
if page.Code != http.StatusOK {
t.Fatalf("document response = %d %q", page.Code, page.Body.String())
}
for _, expected := range [][]byte{
[]byte(`/static/notification-bell.js`),
[]byte(`data-document-id="doc:hello"`),
[]byte(`data-document-watch`),
[]byte(`data-comments-history`),
} {
if !bytes.Contains(page.Body.Bytes(), expected) {
t.Errorf("document page missing %q", expected)
}
}
statusRequest := httptest.NewRequest(http.MethodGet, "/api/watch/status?document_id=doc%3Ahello", nil)
statusRequest.AddCookie(session)
status := httptest.NewRecorder()
server.handler.ServeHTTP(status, statusRequest)
if status.Code != http.StatusOK || !bytes.Contains(status.Body.Bytes(), []byte(`"watching":false`)) {
t.Fatalf("initial watch status = %d %q", status.Code, status.Body.String())
}
watch := httptest.NewRecorder()
server.handler.ServeHTTP(watch, authenticatedJSONRequest(t, http.MethodPost, "/api/watch/document", map[string]string{"documentId": "doc:hello"}, session))
if watch.Code != http.StatusOK {
t.Fatalf("watch response = %d %q", watch.Code, watch.Body.String())
}
status = httptest.NewRecorder()
server.handler.ServeHTTP(status, statusRequest.Clone(statusRequest.Context()))
if status.Code != http.StatusOK || !bytes.Contains(status.Body.Bytes(), []byte(`"watching":true`)) {
t.Fatalf("watched status = %d %q", status.Code, status.Body.String())
}
unwatch := httptest.NewRecorder()
server.handler.ServeHTTP(unwatch, authenticatedJSONRequest(t, http.MethodDelete, "/api/watch/document", map[string]string{"documentId": "doc:hello"}, session))
if unwatch.Code != http.StatusOK {
t.Fatalf("unwatch response = %d %q", unwatch.Code, unwatch.Body.String())
}
}
func TestCommentReplyResolveAndHistoryHTTPFlow(t *testing.T) {
server := newAPITestServer(t)
session := loginTestUser(t, server)
anchor := "section-anchor"
create := func(content string, parentID *string) collaboration.Comment {
t.Helper()
response := httptest.NewRecorder()
server.handler.ServeHTTP(response, authenticatedJSONRequest(t, http.MethodPost, "/api/comments", collaboration.CreateCommentInput{
DocumentID: "doc:hello", VersionHash: "hash", ParentID: parentID, Content: content, AnchorHash: &anchor,
}, session))
if response.Code != http.StatusCreated {
t.Fatalf("create comment response = %d %q", response.Code, response.Body.String())
}
var comment collaboration.Comment
if err := json.NewDecoder(response.Body).Decode(&comment); err != nil {
t.Fatalf("decode comment: %v", err)
}
return comment
}
root := create("Root", nil)
reply := create("Reply", &root.ID)
_ = create("Grandchild", &reply.ID)
resolve := httptest.NewRecorder()
server.handler.ServeHTTP(resolve, authenticatedJSONRequest(t, http.MethodPost, "/api/comments/"+root.ID+"/resolve", map[string]any{}, session))
if resolve.Code != http.StatusOK {
t.Fatalf("resolve response = %d %q", resolve.Code, resolve.Body.String())
}
list := func(includeResolved bool) []collaboration.Comment {
t.Helper()
target := "/api/comments?document_id=doc%3Ahello&anchor_hash=" + anchor
if includeResolved {
target += "&include_resolved=true"
}
request := httptest.NewRequest(http.MethodGet, target, nil)
request.AddCookie(session)
response := httptest.NewRecorder()
server.handler.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("list comments response = %d %q", response.Code, response.Body.String())
}
var body struct {
Comments []collaboration.Comment `json:"comments"`
}
if err := json.NewDecoder(response.Body).Decode(&body); err != nil {
t.Fatalf("decode comments: %v", err)
}
return body.Comments
}
if active := list(false); len(active) != 0 {
t.Fatalf("active comments = %d, want 0", len(active))
}
if history := list(true); len(history) != 3 {
t.Fatalf("history comments = %d, want 3", len(history))
}
}