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>
110 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|