summaryrefslogtreecommitdiff
path: root/internal/api/server_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-04-04 09:30:13 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-04-04 09:30:13 +0000
commit940a5bab031bfe81cea9c90d64e6ebc804c366f9 (patch)
tree34f41c4675f0e238c5acd9665790d256ea39a041 /internal/api/server_test.go
parent2917c580ae3eab093e9e655ccdf210030b7b9d1f (diff)
feat: story ship gate — explicit POST /api/stories/{id}/ship; remove auto-deploy
- checkStoryCompletion now guards against re-running on already-SHIPPABLE stories and no longer auto-triggers triggerStoryDeploy on completion - New Pool.ShipStory method validates SHIPPABLE state then fires triggerStoryDeploy - POST /api/stories/{id}/ship route registered and handleShipStory handler added - Two new tests: 202 for SHIPPABLE story, 409 for non-SHIPPABLE story Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/server_test.go')
-rw-r--r--internal/api/server_test.go48
1 files changed, 48 insertions, 0 deletions
diff --git a/internal/api/server_test.go b/internal/api/server_test.go
index 67a2fc4..2530d55 100644
--- a/internal/api/server_test.go
+++ b/internal/api/server_test.go
@@ -2096,3 +2096,51 @@ func TestHandleRunTask_CascadesRetryToFailedDeps(t *testing.T) {
t.Errorf("task B: want QUEUED, got %s", b.State)
}
}
+
+func TestShipStory_ShippableStory_Returns202(t *testing.T) {
+ srv, store := testServer(t)
+
+ proj := &task.Project{
+ ID: "ship-proj-1", Name: "test", RemoteURL: "https://github.com/x/y",
+ Type: "web", DeployScript: "",
+ }
+ if err := store.CreateProject(proj); err != nil {
+ t.Fatalf("CreateProject: %v", err)
+ }
+
+ story := &task.Story{
+ ID: "ship-story-1", Name: "Ship Test", ProjectID: "ship-proj-1",
+ Status: task.StoryShippable, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := store.CreateStory(story); err != nil {
+ t.Fatalf("CreateStory: %v", err)
+ }
+
+ req := httptest.NewRequest("POST", "/api/stories/ship-story-1/ship", nil)
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusAccepted {
+ t.Errorf("expected 202, got %d: %s", w.Code, w.Body.String())
+ }
+}
+
+func TestShipStory_NonShippable_Returns409(t *testing.T) {
+ srv, store := testServer(t)
+
+ story := &task.Story{
+ ID: "nonship-1", Name: "Not Ready", ProjectID: "",
+ Status: task.StoryInProgress, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := store.CreateStory(story); err != nil {
+ t.Fatalf("CreateStory: %v", err)
+ }
+
+ req := httptest.NewRequest("POST", "/api/stories/nonship-1/ship", nil)
+ w := httptest.NewRecorder()
+ srv.Handler().ServeHTTP(w, req)
+
+ if w.Code != http.StatusConflict {
+ t.Errorf("expected 409, got %d", w.Code)
+ }
+}