summaryrefslogtreecommitdiff
path: root/internal/api/stories_test.go
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator>2026-06-05 09:21:32 +0000
committerClaudomator Agent <agent@claudomator>2026-06-05 09:21:32 +0000
commit0eb0b79396663c4901597becc6857a4cd795c58e (patch)
tree3532c82aa3f7c6deeed03f143d1c360d7cf6cc47 /internal/api/stories_test.go
parentfa3ed5220a28f326d443726233c37e1a7df0ced5 (diff)
chore: remove stories/checker backend dead code
Remove the dead stories/checker backend tracked as design debt in CLAUDE.md: - Delete internal/api/stories.go, stories_test.go, elaborate.go - Delete internal/task/story.go, story_test.go - Remove story/checker columns from storage schema and all CRUD methods - Remove checkStoryCompletion, spawnCheckerTask, triggerStoryDeploy, createValidationTask, checkValidationResult, ShipStory, CheckStoryCompletion from executor; simplify handleRunResult - Remove story/checker fields from task.Task (StoryID, AcceptanceCriteria, CheckerForTaskID, CheckerReport) - Remove story routes and geminiBinPath from API server - Move claudeJSONResult/extractJSON from elaborate.go to validate.go - Rename ensureStoryBranch -> ensureBranch in ContainerRunner - Fix git identity in bare-repo tests; fix initial-branch to use main Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/stories_test.go')
-rw-r--r--internal/api/stories_test.go351
1 files changed, 0 insertions, 351 deletions
diff --git a/internal/api/stories_test.go b/internal/api/stories_test.go
deleted file mode 100644
index f43ad86..0000000
--- a/internal/api/stories_test.go
+++ /dev/null
@@ -1,351 +0,0 @@
-package api
-
-import (
- "bytes"
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-
- "github.com/thepeterstone/claudomator/internal/deployment"
- "github.com/thepeterstone/claudomator/internal/task"
-)
-
-func TestCreateStory_API(t *testing.T) {
- srv, _ := testServer(t)
-
- body := `{"name":"My Story","project_id":"proj-1"}`
- req := httptest.NewRequest("POST", "/api/stories", bytes.NewBufferString(body))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
-
- if w.Code != http.StatusCreated {
- t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
- }
- var st task.Story
- if err := json.NewDecoder(w.Body).Decode(&st); err != nil {
- t.Fatalf("decode response: %v", err)
- }
- if st.Name != "My Story" {
- t.Errorf("Name: want 'My Story', got %q", st.Name)
- }
- if st.ID == "" {
- t.Error("ID should be auto-generated")
- }
- if st.Status != task.StoryPending {
- t.Errorf("Status: want PENDING, got %q", st.Status)
- }
-}
-
-func TestGetStory_API(t *testing.T) {
- srv, _ := testServer(t)
-
- // Create a story first.
- body := `{"name":"Get Me"}`
- req := httptest.NewRequest("POST", "/api/stories", bytes.NewBufferString(body))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
- if w.Code != http.StatusCreated {
- t.Fatalf("create story: expected 201, got %d", w.Code)
- }
- var created task.Story
- json.NewDecoder(w.Body).Decode(&created)
-
- // Fetch it.
- req2 := httptest.NewRequest("GET", "/api/stories/"+created.ID, nil)
- w2 := httptest.NewRecorder()
- srv.mux.ServeHTTP(w2, req2)
-
- if w2.Code != http.StatusOK {
- t.Fatalf("get story: expected 200, got %d: %s", w2.Code, w2.Body.String())
- }
- var got task.Story
- if err := json.NewDecoder(w2.Body).Decode(&got); err != nil {
- t.Fatalf("decode: %v", err)
- }
- if got.ID != created.ID {
- t.Errorf("ID: want %q, got %q", created.ID, got.ID)
- }
- if got.Name != "Get Me" {
- t.Errorf("Name: want 'Get Me', got %q", got.Name)
- }
-}
-
-func TestAddTaskToStory_AutoWiresDependsOn(t *testing.T) {
- srv, _ := testServer(t)
-
- // Create a story.
- storyBody := `{"name":"Story For Tasks"}`
- req := httptest.NewRequest("POST", "/api/stories", bytes.NewBufferString(storyBody))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
- if w.Code != http.StatusCreated {
- t.Fatalf("create story: %d %s", w.Code, w.Body.String())
- }
- var story task.Story
- json.NewDecoder(w.Body).Decode(&story)
-
- addTask := func(name string) *task.Task {
- body := `{"name":"` + name + `","agent":{"type":"claude","instructions":"do it"}}`
- r := httptest.NewRequest("POST", "/api/stories/"+story.ID+"/tasks", bytes.NewBufferString(body))
- r.Header.Set("Content-Type", "application/json")
- wr := httptest.NewRecorder()
- srv.mux.ServeHTTP(wr, r)
- if wr.Code != http.StatusCreated {
- t.Fatalf("add task %s: expected 201, got %d: %s", name, wr.Code, wr.Body.String())
- }
- var tk task.Task
- json.NewDecoder(wr.Body).Decode(&tk)
- return &tk
- }
-
- task1 := addTask("Task 1")
- task2 := addTask("Task 2")
- task3 := addTask("Task 3")
-
- // task1 has no dependencies.
- if len(task1.DependsOn) != 0 {
- t.Errorf("task1.DependsOn: want [], got %v", task1.DependsOn)
- }
- // task2 depends on task1.
- if len(task2.DependsOn) != 1 || task2.DependsOn[0] != task1.ID {
- t.Errorf("task2.DependsOn: want [%s], got %v", task1.ID, task2.DependsOn)
- }
- // task3 depends on task2.
- if len(task3.DependsOn) != 1 || task3.DependsOn[0] != task2.ID {
- t.Errorf("task3.DependsOn: want [%s], got %v", task2.ID, task3.DependsOn)
- }
-}
-
-func TestBuildStoryElaboratePrompt(t *testing.T) {
- prompt := buildStoryElaboratePrompt()
- checks := []struct {
- label string
- want string
- }{
- {"schema: name field", `"name"`},
- {"schema: branch_name field", `"branch_name"`},
- {"schema: tasks field", `"tasks"`},
- {"schema: validation field", `"validation"`},
- {"rule: git push", "git push origin"},
- {"rule: sequential subtasks", "sequentially"},
- {"rule: specific file paths", "file paths"},
- }
- for _, c := range checks {
- if !strings.Contains(prompt, c.want) {
- t.Errorf("%s: prompt should contain %q", c.label, c.want)
- }
- }
-}
-
-func TestHandleStoryApprove_WiresDepends(t *testing.T) {
- srv, _ := testServer(t)
-
- body := `{
- "name": "My Story",
- "branch_name": "story/my-story",
- "tasks": [
- {"name": "Task 1", "instructions": "do task 1", "subtasks": []},
- {"name": "Task 2", "instructions": "do task 2", "subtasks": []},
- {"name": "Task 3", "instructions": "do task 3", "subtasks": []}
- ],
- "validation": {"type": "build", "steps": ["go build ./..."], "success_criteria": "compiles"}
- }`
- req := httptest.NewRequest("POST", "/api/stories/approve", bytes.NewBufferString(body))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
-
- if w.Code != http.StatusCreated {
- t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
- }
-
- var resp struct {
- Story task.Story `json:"story"`
- TaskIDs []string `json:"task_ids"`
- }
- if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
- t.Fatalf("decode response: %v", err)
- }
- if len(resp.TaskIDs) != 3 {
- t.Fatalf("expected 3 task IDs, got %d", len(resp.TaskIDs))
- }
- if resp.Story.Name != "My Story" {
- t.Errorf("story name: want 'My Story', got %q", resp.Story.Name)
- }
-
- // Verify depends_on chain via the store.
- store := srv.store
- task1, err := store.GetTask(resp.TaskIDs[0])
- if err != nil {
- t.Fatalf("GetTask[0]: %v", err)
- }
- task2, err := store.GetTask(resp.TaskIDs[1])
- if err != nil {
- t.Fatalf("GetTask[1]: %v", err)
- }
- task3, err := store.GetTask(resp.TaskIDs[2])
- if err != nil {
- t.Fatalf("GetTask[2]: %v", err)
- }
-
- if len(task1.DependsOn) != 0 {
- t.Errorf("task1.DependsOn: want [], got %v", task1.DependsOn)
- }
- if len(task2.DependsOn) != 1 || task2.DependsOn[0] != task1.ID {
- t.Errorf("task2.DependsOn: want [%s], got %v", task1.ID, task2.DependsOn)
- }
- if len(task3.DependsOn) != 1 || task3.DependsOn[0] != task2.ID {
- t.Errorf("task3.DependsOn: want [%s], got %v", task2.ID, task3.DependsOn)
- }
-}
-
-func TestHandleStoryApprove_SetsRepositoryURL(t *testing.T) {
- srv, store := testServer(t)
-
- proj := &task.Project{
- ID: "proj-repo",
- Name: "claudomator",
- RemoteURL: "/site/git.terst.org/repos/claudomator.git",
- // LocalPath intentionally empty: branch creation is a non-fatal side effect,
- // omitting it keeps the test fast and free of real git operations.
- }
- if err := store.CreateProject(proj); err != nil {
- t.Fatalf("CreateProject: %v", err)
- }
-
- body := `{
- "name": "Repo URL Story",
- "branch_name": "story/repo-url",
- "project_id": "proj-repo",
- "tasks": [
- {"name": "Task A", "instructions": "do A", "subtasks": []},
- {"name": "Task B", "instructions": "do B", "subtasks": [
- {"name": "Sub B1", "instructions": "do B1"}
- ]}
- ],
- "validation": {"type": "build", "steps": ["go build ./..."], "success_criteria": "ok"}
- }`
- req := httptest.NewRequest("POST", "/api/stories/approve", bytes.NewBufferString(body))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
-
- if w.Code != http.StatusCreated {
- t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
- }
-
- var resp struct {
- TaskIDs []string `json:"task_ids"`
- }
- if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
- t.Fatalf("decode: %v", err)
- }
-
- for _, id := range resp.TaskIDs {
- tk, err := store.GetTask(id)
- if err != nil {
- t.Fatalf("GetTask %s: %v", id, err)
- }
- if tk.RepositoryURL != proj.RemoteURL {
- t.Errorf("task %s RepositoryURL: want %q, got %q", tk.ID, proj.RemoteURL, tk.RepositoryURL)
- }
- }
-}
-
-func TestApproveStory_AcceptanceCriteriaStored(t *testing.T) {
- srv, store := testServer(t)
-
- proj := &task.Project{
- ID: "ac-proj", Name: "test", RemoteURL: "https://github.com/x/y",
- Type: "web", DeployScript: "",
- }
- store.CreateProject(proj)
-
- body := `{
- "name": "AC Story",
- "branch_name": "story/ac-test",
- "project_id": "ac-proj",
- "tasks": [
- {
- "name": "Add feature",
- "instructions": "implement the thing",
- "acceptance_criteria": "run go test ./... and verify all pass",
- "subtasks": []
- }
- ],
- "validation": {"type": "test", "steps": [], "success_criteria": "tests pass"}
- }`
- req := httptest.NewRequest("POST", "/api/stories/approve", strings.NewReader(body))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
-
- if w.Code != http.StatusCreated {
- t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
- }
-
- var resp struct {
- TaskIDs []string `json:"task_ids"`
- }
- json.NewDecoder(w.Body).Decode(&resp)
- if len(resp.TaskIDs) == 0 {
- t.Fatal("expected task_ids in response")
- }
-
- tk, err := store.GetTask(resp.TaskIDs[0])
- if err != nil {
- t.Fatalf("GetTask: %v", err)
- }
- if tk.AcceptanceCriteria != "run go test ./... and verify all pass" {
- t.Errorf("expected acceptance criteria stored on task, got %q", tk.AcceptanceCriteria)
- }
-}
-
-func TestHandleStoryDeploymentStatus(t *testing.T) {
- srv, store := testServer(t)
-
- // Create a story.
- now := time.Now().UTC()
- story := &task.Story{
- ID: "deploy-story-1",
- Name: "Deploy Status Story",
- Status: task.StoryInProgress,
- CreatedAt: now,
- UpdatedAt: now,
- }
- if err := store.CreateStory(story); err != nil {
- t.Fatalf("CreateStory: %v", err)
- }
-
- // Request deployment status — no tasks yet.
- req := httptest.NewRequest("GET", "/api/stories/deploy-story-1/deployment-status", nil)
- w := httptest.NewRecorder()
- srv.mux.ServeHTTP(w, req)
-
- if w.Code != http.StatusOK {
- t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
- }
-
- var status deployment.Status
- if err := json.NewDecoder(w.Body).Decode(&status); err != nil {
- t.Fatalf("decode: %v", err)
- }
- // No tasks → no commits → IncludesFix = false (nothing to check).
- if status.IncludesFix {
- t.Error("expected IncludesFix=false when no commits")
- }
-
- // 404 for unknown story.
- req2 := httptest.NewRequest("GET", "/api/stories/nonexistent/deployment-status", nil)
- w2 := httptest.NewRecorder()
- srv.mux.ServeHTTP(w2, req2)
- if w2.Code != http.StatusNotFound {
- t.Errorf("expected 404 for unknown story, got %d", w2.Code)
- }
-}