Files
Agent-System/internal/server/task_run_json_test.go
T
weichenandCursor fbcb78cf92 test(server): assert snake_case task run response
Lock GET /api/v1/tasks/runs/{id} to run_id, status, and error_message so PascalCase cannot regress.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 03:05:49 +00:00

110 lines
2.8 KiB
Go

package server
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/config"
"chat-agent-system/internal/taskrun"
)
func TestTaskRunResponseMarshalUsesSnakeCase(t *testing.T) {
raw, err := json.Marshal(taskRunResponse{
RunRecord: taskrun.RunRecord{
RunID: "run_snake",
Status: "failed",
ErrorMessage: "dial tcp connection refused",
TaskKey: "user_requirements",
WorkspaceID: "ws_1",
},
Resumable: false,
})
if err != nil {
t.Fatal(err)
}
var body map[string]any
if err := json.Unmarshal(raw, &body); err != nil {
t.Fatal(err)
}
for _, key := range []string{"run_id", "status", "error_message", "workspace_id", "task_key", "resumable"} {
if _, ok := body[key]; !ok {
t.Fatalf("missing %q in %s", key, raw)
}
}
for _, pascal := range []string{"Status", "ErrorMessage", "RunID", "WorkspaceID"} {
if _, ok := body[pascal]; ok {
t.Fatalf("pascal key %q leaked into %s", pascal, raw)
}
}
if body["status"] != "failed" || body["error_message"] != "dial tcp connection refused" {
t.Fatalf("payload=%s", raw)
}
}
func TestGetTaskRunJSONUsesSnakeCase(t *testing.T) {
srv := newTestServer(t)
ts := httptest.NewServer(srv.Routes())
t.Cleanup(ts.Close)
runID := "run_snake_http"
rec := &taskrun.RunRecord{
RunID: runID,
SessionID: "sess-snake",
Status: "failed",
ErrorMessage: "dial tcp connection refused",
TaskKey: "intake",
UserID: 1,
}
if _, err := srv.taskRuns.CreateAndStart(
t.Context(),
runID,
rec,
bizdev.TaskRunRequest{TaskKey: "intake", ProjectName: "snake-json"},
bizdev.StageRunRequest{},
"",
); err != nil {
t.Fatalf("CreateAndStart: %v", err)
}
token := testBearerToken(t, ts.URL, &config.Config{
AdminUsername: "admin",
AdminPassword: "admin-test-pass",
})
req, err := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/tasks/runs/"+runID, nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
t.Fatalf("GET status=%d body=%s", resp.StatusCode, payload)
}
var body map[string]any
if err := json.Unmarshal(payload, &body); err != nil {
t.Fatalf("decode: %v body=%s", err, payload)
}
status, _ := body["status"].(string)
if status == "" {
t.Fatalf("status key missing or empty (pascal leak?): %s", payload)
}
if _, ok := body["Status"]; ok || strings.Contains(string(payload), `"ErrorMessage"`) {
t.Fatalf("pascal keys in GET body: %s", payload)
}
if _, ok := body["error_message"]; !ok {
t.Fatalf("missing error_message: %s", payload)
}
if _, ok := body["run_id"]; !ok {
t.Fatalf("missing run_id: %s", payload)
}
}