From 1b2deb13daa788dc43d98caeaa9507254b1ca283 Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Mon, 16 Mar 2026 20:01:59 +0000 Subject: feat: display deployment status badge on READY task cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add deployment_status field to task list/get API responses for READY tasks. The field includes deployed_commit, fix_commits, and includes_fix so the UI can show whether the deployed server includes each fix. - internal/api/task_view.go: taskView struct + enrichTask() helper - handleListTasks/handleGetTask: return enriched taskView responses - web/app.js: export renderDeploymentBadge(); add badge to READY cards - web/test/deployment-badge.test.mjs: 8 tests for renderDeploymentBadge - web/style.css: .deployment-badge--deployed / --pending styles - server_test.go: 3 new tests (red→green) for enriched task responses Co-Authored-By: Claude Sonnet 4.6 --- internal/api/server_test.go | 134 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) (limited to 'internal/api/server_test.go') diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 516e289..83f83f4 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -1781,3 +1781,137 @@ func TestDeploymentStatus_NotFound(t *testing.T) { t.Fatalf("want 404, got %d", w.Code) } } + +// TestListTasks_ReadyTask_IncludesDeploymentStatus verifies that GET /api/tasks +// returns a deployment_status field for READY tasks containing deployed_commit, +// fix_commits, and includes_fix. +func TestListTasks_ReadyTask_IncludesDeploymentStatus(t *testing.T) { + srv, store := testServer(t) + + tk := createTaskWithState(t, store, "enrich-list-ready-1", task.StateReady) + exec := &storage.Execution{ + ID: "enrich-list-exec-1", + TaskID: tk.ID, + StartTime: time.Now(), + EndTime: time.Now(), + Status: "COMPLETED", + Commits: []task.GitCommit{{Hash: "aabbcc", Message: "fix: list test"}}, + } + if err := store.CreateExecution(exec); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/api/tasks", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("want 200, got %d; body: %s", w.Code, w.Body.String()) + } + + var tasks []map[string]interface{} + if err := json.NewDecoder(w.Body).Decode(&tasks); err != nil { + t.Fatalf("decode: %v", err) + } + + var found map[string]interface{} + for _, tsk := range tasks { + if tsk["id"] == tk.ID { + found = tsk + break + } + } + if found == nil { + t.Fatalf("task %q not found in list response", tk.ID) + } + + ds, ok := found["deployment_status"].(map[string]interface{}) + if !ok { + t.Fatalf("READY task missing deployment_status field; got: %v", found["deployment_status"]) + } + if _, ok := ds["deployed_commit"]; !ok { + t.Error("deployment_status missing deployed_commit") + } + if _, ok := ds["includes_fix"]; !ok { + t.Error("deployment_status missing includes_fix") + } +} + +// TestGetTask_ReadyTask_IncludesDeploymentStatus verifies that GET /api/tasks/{id} +// returns a deployment_status field for a READY task. +func TestGetTask_ReadyTask_IncludesDeploymentStatus(t *testing.T) { + srv, store := testServer(t) + + tk := createTaskWithState(t, store, "enrich-get-ready-1", task.StateReady) + exec := &storage.Execution{ + ID: "enrich-get-exec-1", + TaskID: tk.ID, + StartTime: time.Now(), + EndTime: time.Now(), + Status: "COMPLETED", + Commits: []task.GitCommit{{Hash: "ddeeff", Message: "fix: get test"}}, + } + if err := store.CreateExecution(exec); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/api/tasks/"+tk.ID, nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("want 200, got %d", w.Code) + } + + var resp map[string]interface{} + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("decode: %v", err) + } + + ds, ok := resp["deployment_status"].(map[string]interface{}) + if !ok { + t.Fatalf("READY task GET response missing deployment_status; got: %v", resp["deployment_status"]) + } + if _, ok := ds["deployed_commit"]; !ok { + t.Error("deployment_status missing deployed_commit") + } + if _, ok := ds["includes_fix"]; !ok { + t.Error("deployment_status missing includes_fix") + } +} + +// TestListTasks_NonReadyTask_OmitsDeploymentStatus verifies that non-READY tasks +// (e.g. PENDING) do not include a deployment_status field. +func TestListTasks_NonReadyTask_OmitsDeploymentStatus(t *testing.T) { + srv, store := testServer(t) + + tk := createTaskWithState(t, store, "enrich-list-pending-1", task.StatePending) + + req := httptest.NewRequest("GET", "/api/tasks", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("want 200, got %d", w.Code) + } + + var tasks []map[string]interface{} + if err := json.NewDecoder(w.Body).Decode(&tasks); err != nil { + t.Fatalf("decode: %v", err) + } + + var found map[string]interface{} + for _, tsk := range tasks { + if tsk["id"] == tk.ID { + found = tsk + break + } + } + if found == nil { + t.Fatalf("task %q not found in list", tk.ID) + } + + if _, ok := found["deployment_status"]; ok { + t.Error("PENDING task should not include deployment_status field") + } +} -- cgit v1.2.3