package docs import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "gitea.parodia.dev/drwily/petal/internal/db" ) // newTestServer spins up an isolated on-disk database and the docs router. func newTestServer(t *testing.T) http.Handler { t.Helper() database, err := db.Open(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("open db: %v", err) } t.Cleanup(func() { database.Close() }) return New(database).Routes() } func do(t *testing.T, srv http.Handler, method, path, body string) *httptest.ResponseRecorder { t.Helper() var r *http.Request if body != "" { r = httptest.NewRequest(method, path, bytes.NewBufferString(body)) } else { r = httptest.NewRequest(method, path, nil) } rec := httptest.NewRecorder() srv.ServeHTTP(rec, r) return rec } func TestDocumentCRUD(t *testing.T) { srv := newTestServer(t) // Empty list serializes as []. rec := do(t, srv, http.MethodGet, "/", "") if rec.Code != http.StatusOK || bytes.TrimSpace(rec.Body.Bytes())[0] != '[' { t.Fatalf("list empty: code=%d body=%s", rec.Code, rec.Body) } // Create. rec = do(t, srv, http.MethodPost, "/", "") if rec.Code != http.StatusCreated { t.Fatalf("create: code=%d body=%s", rec.Code, rec.Body) } var created db.Document if err := json.Unmarshal(rec.Body.Bytes(), &created); err != nil { t.Fatalf("decode create: %v", err) } if created.ID == "" || created.Title != "Untitled" { t.Fatalf("unexpected created doc: %+v", created) } // Update (auto-save shape) keeps content + content_text + word count together. body := `{"title":"My Essay","content":"{\"x\":1}","content_text":"hello world","word_count":2}` rec = do(t, srv, http.MethodPut, "/"+created.ID, body) if rec.Code != http.StatusOK { t.Fatalf("update: code=%d body=%s", rec.Code, rec.Body) } var updated db.Document _ = json.Unmarshal(rec.Body.Bytes(), &updated) if updated.Title != "My Essay" || updated.ContentText != "hello world" || updated.WordCount != 2 { t.Fatalf("update not applied: %+v", updated) } // Partial update (rename only) leaves other fields intact. rec = do(t, srv, http.MethodPut, "/"+created.ID, `{"title":"Renamed"}`) _ = json.Unmarshal(rec.Body.Bytes(), &updated) if updated.Title != "Renamed" || updated.WordCount != 2 { t.Fatalf("partial update clobbered fields: %+v", updated) } // Get. rec = do(t, srv, http.MethodGet, "/"+created.ID, "") if rec.Code != http.StatusOK { t.Fatalf("get: code=%d", rec.Code) } // List now has one entry. rec = do(t, srv, http.MethodGet, "/", "") var summaries []docSummary _ = json.Unmarshal(rec.Body.Bytes(), &summaries) if len(summaries) != 1 || summaries[0].Title != "Renamed" { t.Fatalf("list after create: %+v", summaries) } // Delete, then 404 on subsequent get/delete. if rec = do(t, srv, http.MethodDelete, "/"+created.ID, ""); rec.Code != http.StatusNoContent { t.Fatalf("delete: code=%d", rec.Code) } if rec = do(t, srv, http.MethodGet, "/"+created.ID, ""); rec.Code != http.StatusNotFound { t.Fatalf("get after delete: code=%d", rec.Code) } if rec = do(t, srv, http.MethodPut, "/missing", `{"title":"x"}`); rec.Code != http.StatusNotFound { t.Fatalf("update missing: code=%d", rec.Code) } }