summaryrefslogtreecommitdiff
path: root/internal/api/templates_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-03 21:15:50 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-03 21:15:50 +0000
commit74cc740398cf2d90804ab19db728c844c2e056b7 (patch)
treee8532d1da9273e1613beb7b762b16134da0de286 /internal/api/templates_test.go
parentf527972f4d8311a09e639ede6c4da4ca669cfd5e (diff)
Add elaborate, logs-stream, templates, and subtask-list endpoints
- POST /api/tasks/elaborate: calls claude to draft a task config from a natural-language prompt - GET /api/executions/{id}/logs/stream: SSE tail of stdout.log - CRUD /api/templates: create/list/get/update/delete reusable task configs - GET /api/tasks/{id}/subtasks: list child tasks - Server.NewServer accepts claudeBinPath for elaborate; injectable elaborateCmdPath and logStore for test isolation - Valid-transition guard added to POST /api/tasks/{id}/run - CLI passes claude binary path through to the server Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/templates_test.go')
-rw-r--r--internal/api/templates_test.go183
1 files changed, 183 insertions, 0 deletions
diff --git a/internal/api/templates_test.go b/internal/api/templates_test.go
new file mode 100644
index 0000000..bbcfc87
--- /dev/null
+++ b/internal/api/templates_test.go
@@ -0,0 +1,183 @@
+package api
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/thepeterstone/claudomator/internal/storage"
+)
+
+func TestListTemplates_Empty(t *testing.T) {
+ srv, _ := testServer(t)
+
+ req := httptest.NewRequest("GET", "/api/templates", nil)
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusOK {
+ t.Fatalf("status: want 200, got %d; body: %s", w.Code, w.Body.String())
+ }
+ var templates []storage.Template
+ json.NewDecoder(w.Body).Decode(&templates)
+ if len(templates) != 0 {
+ t.Errorf("want 0 templates, got %d", len(templates))
+ }
+}
+
+func TestCreateTemplate_Success(t *testing.T) {
+ srv, _ := testServer(t)
+
+ payload := `{
+ "name": "Go: Run Tests",
+ "description": "Run the full test suite with race detector",
+ "claude": {
+ "model": "sonnet",
+ "instructions": "Run go test -race ./...",
+ "max_budget_usd": 0.50,
+ "allowed_tools": ["Bash"]
+ },
+ "timeout": "10m",
+ "priority": "normal",
+ "tags": ["go", "testing"]
+ }`
+ req := httptest.NewRequest("POST", "/api/templates", bytes.NewBufferString(payload))
+ req.Header.Set("Content-Type", "application/json")
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusCreated {
+ t.Fatalf("status: want 201, got %d; body: %s", w.Code, w.Body.String())
+ }
+ var created storage.Template
+ json.NewDecoder(w.Body).Decode(&created)
+ if created.Name != "Go: Run Tests" {
+ t.Errorf("name: want 'Go: Run Tests', got %q", created.Name)
+ }
+ if created.ID == "" {
+ t.Error("expected auto-generated ID")
+ }
+}
+
+func TestGetTemplate_AfterCreate(t *testing.T) {
+ srv, _ := testServer(t)
+
+ payload := `{"name": "Fetch Me", "claude": {"instructions": "do thing", "model": "haiku"}}`
+ req := httptest.NewRequest("POST", "/api/templates", bytes.NewBufferString(payload))
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+ if w.Code != http.StatusCreated {
+ t.Fatalf("create: want 201, got %d", w.Code)
+ }
+ var created storage.Template
+ json.NewDecoder(w.Body).Decode(&created)
+
+ req2 := httptest.NewRequest("GET", fmt.Sprintf("/api/templates/%s", created.ID), nil)
+ w2 := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w2, req2)
+
+ if w2.Code != http.StatusOK {
+ t.Fatalf("get: want 200, got %d; body: %s", w2.Code, w2.Body.String())
+ }
+ var fetched storage.Template
+ json.NewDecoder(w2.Body).Decode(&fetched)
+ if fetched.ID != created.ID {
+ t.Errorf("id: want %q, got %q", created.ID, fetched.ID)
+ }
+ if fetched.Name != "Fetch Me" {
+ t.Errorf("name: want 'Fetch Me', got %q", fetched.Name)
+ }
+}
+
+func TestGetTemplate_NotFound(t *testing.T) {
+ srv, _ := testServer(t)
+
+ req := httptest.NewRequest("GET", "/api/templates/nonexistent", nil)
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusNotFound {
+ t.Errorf("status: want 404, got %d", w.Code)
+ }
+}
+
+func TestUpdateTemplate(t *testing.T) {
+ srv, _ := testServer(t)
+
+ payload := `{"name": "Original Name", "claude": {"instructions": "original"}}`
+ req := httptest.NewRequest("POST", "/api/templates", bytes.NewBufferString(payload))
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+ var created storage.Template
+ json.NewDecoder(w.Body).Decode(&created)
+
+ update := `{"name": "Updated Name", "claude": {"instructions": "updated"}}`
+ req2 := httptest.NewRequest("PUT", fmt.Sprintf("/api/templates/%s", created.ID), bytes.NewBufferString(update))
+ w2 := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w2, req2)
+
+ if w2.Code != http.StatusOK {
+ t.Fatalf("update: want 200, got %d; body: %s", w2.Code, w2.Body.String())
+ }
+ var updated storage.Template
+ json.NewDecoder(w2.Body).Decode(&updated)
+ if updated.Name != "Updated Name" {
+ t.Errorf("name: want 'Updated Name', got %q", updated.Name)
+ }
+}
+
+func TestUpdateTemplate_NotFound(t *testing.T) {
+ srv, _ := testServer(t)
+
+ update := `{"name": "Ghost", "claude": {"instructions": "x"}}`
+ req := httptest.NewRequest("PUT", "/api/templates/nonexistent", bytes.NewBufferString(update))
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusNotFound {
+ t.Errorf("status: want 404, got %d", w.Code)
+ }
+}
+
+func TestDeleteTemplate(t *testing.T) {
+ srv, _ := testServer(t)
+
+ payload := `{"name": "To Delete", "claude": {"instructions": "bye"}}`
+ req := httptest.NewRequest("POST", "/api/templates", bytes.NewBufferString(payload))
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+ var created storage.Template
+ json.NewDecoder(w.Body).Decode(&created)
+
+ req2 := httptest.NewRequest("DELETE", fmt.Sprintf("/api/templates/%s", created.ID), nil)
+ w2 := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w2, req2)
+
+ if w2.Code != http.StatusNoContent {
+ t.Fatalf("delete: want 204, got %d; body: %s", w2.Code, w2.Body.String())
+ }
+
+ // Subsequent GET returns 404.
+ req3 := httptest.NewRequest("GET", fmt.Sprintf("/api/templates/%s", created.ID), nil)
+ w3 := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w3, req3)
+
+ if w3.Code != http.StatusNotFound {
+ t.Fatalf("get after delete: want 404, got %d", w3.Code)
+ }
+}
+
+func TestDeleteTemplate_NotFound(t *testing.T) {
+ srv, _ := testServer(t)
+
+ req := httptest.NewRequest("DELETE", "/api/templates/nonexistent", nil)
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusNotFound {
+ t.Errorf("status: want 404, got %d", w.Code)
+ }
+}