141 lines
4.2 KiB
Go
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())
|
|
}
|
|
}
|