Files
work-queue-api/internal/api/server_test.go
Steve W f0275c41d0
All checks were successful
ci / test-and-build (push) Successful in 11m21s
feat: add sqlite-backed API docs and tests
2026-04-11 18:42:05 +00:00

141 lines
4.2 KiB
Go

package api
import (
"bytes"
"database/sql"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"work-queue-api/internal/db"
)
func newTestServer(t *testing.T) (*Server, *sql.DB) {
t.Helper()
sqliteDB, err := db.Open(t.TempDir() + "/test.db")
if err != nil {
t.Fatalf("open db: %v", err)
}
server, err := NewServer(sqliteDB)
if err != nil {
t.Fatalf("new server: %v", err)
}
return server, sqliteDB
}
func doJSON(t *testing.T, h http.Handler, method, path string, body any) *httptest.ResponseRecorder {
t.Helper()
var payload []byte
if body != nil {
var err error
payload, err = json.Marshal(body)
if err != nil {
t.Fatalf("marshal body: %v", err)
}
}
req := httptest.NewRequest(method, path, bytes.NewReader(payload))
res := httptest.NewRecorder()
h.ServeHTTP(res, req)
return res
}
func TestProjectAndWorkLifecycle(t *testing.T) {
server, sqliteDB := newTestServer(t)
defer sqliteDB.Close()
router := server.Router()
projectRes := doJSON(t, router, http.MethodPost, "/projects", map[string]any{
"name": "Shopping List API",
"external_ref": "todoist:123",
})
if projectRes.Code != http.StatusCreated {
t.Fatalf("create project status = %d body=%s", projectRes.Code, projectRes.Body.String())
}
var project map[string]any
if err := json.Unmarshal(projectRes.Body.Bytes(), &project); err != nil {
t.Fatalf("decode project: %v", err)
}
workRes := doJSON(t, router, http.MethodPost, "/work", map[string]any{
"project_id": project["id"],
"type": "code_review",
"description": "Review PR #3",
"payload": map[string]any{"pr": 3},
"priority": 2,
"assigned_agent": "steve-w",
"created_by": "marcus-a",
})
if workRes.Code != http.StatusCreated {
t.Fatalf("create work status = %d body=%s", workRes.Code, workRes.Body.String())
}
var work map[string]any
if err := json.Unmarshal(workRes.Body.Bytes(), &work); err != nil {
t.Fatalf("decode work: %v", err)
}
for _, step := range []map[string]any{
{"status": "dispatched", "assigned_agent": "steve-w"},
{"status": "in_progress"},
{"status": "completed", "outcome": "success", "notes": "done"},
} {
res := doJSON(t, router, http.MethodPatch, "/work/"+work["id"].(string), step)
if res.Code != http.StatusOK {
t.Fatalf("patch work step=%v status = %d body=%s", step, res.Code, res.Body.String())
}
}
getRes := doJSON(t, router, http.MethodGet, "/work/"+work["id"].(string), nil)
if getRes.Code != http.StatusOK {
t.Fatalf("get work status = %d body=%s", getRes.Code, getRes.Body.String())
}
var fetched map[string]any
if err := json.Unmarshal(getRes.Body.Bytes(), &fetched); err != nil {
t.Fatalf("decode fetched work: %v", err)
}
if fetched["status"] != "completed" {
t.Fatalf("expected completed status, got %v", fetched["status"])
}
logs, ok := fetched["dispatch_log"].([]any)
if !ok || len(logs) != 1 {
t.Fatalf("expected 1 dispatch log, got %v", fetched["dispatch_log"])
}
}
func TestOneInProgressPerAgent(t *testing.T) {
server, sqliteDB := newTestServer(t)
defer sqliteDB.Close()
router := server.Router()
create := func(desc string) string {
res := doJSON(t, router, http.MethodPost, "/work", map[string]any{
"type": "bug_fix",
"description": desc,
"assigned_agent": "steve-w",
})
if res.Code != http.StatusCreated {
t.Fatalf("create work status = %d body=%s", res.Code, res.Body.String())
}
var body map[string]any
_ = json.Unmarshal(res.Body.Bytes(), &body)
return body["id"].(string)
}
first := create("first")
second := create("second")
for _, id := range []string{first, second} {
res := doJSON(t, router, http.MethodPatch, "/work/"+id, map[string]any{"status": "dispatched", "assigned_agent": "steve-w"})
if res.Code != http.StatusOK {
t.Fatalf("dispatch status = %d body=%s", res.Code, res.Body.String())
}
}
res := doJSON(t, router, http.MethodPatch, "/work/"+first, map[string]any{"status": "in_progress"})
if res.Code != http.StatusOK {
t.Fatalf("first in progress status = %d body=%s", res.Code, res.Body.String())
}
res = doJSON(t, router, http.MethodPatch, "/work/"+second, map[string]any{"status": "in_progress"})
if res.Code != http.StatusConflict {
t.Fatalf("expected 409, got %d body=%s", res.Code, res.Body.String())
}
}