From 7ce65ac4cbf3e4d23a10867b0793eef125927aba Mon Sep 17 00:00:00 2001 From: hongming-codex-laptop Date: Wed, 13 May 2026 15:20:07 -0700 Subject: [PATCH] fix(handlers): repair current main test blockers --- .../internal/handlers/a2a_queue.go | 13 +++++++++--- .../internal/handlers/delegation_test.go | 18 ++++++++++------- .../internal/handlers/workspace_crud.go | 8 ++++++++ .../internal/handlers/workspace_crud_test.go | 20 +++++++++---------- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/workspace-server/internal/handlers/a2a_queue.go b/workspace-server/internal/handlers/a2a_queue.go index a3e25a9c..24e61f69 100644 --- a/workspace-server/internal/handlers/a2a_queue.go +++ b/workspace-server/internal/handlers/a2a_queue.go @@ -57,16 +57,23 @@ func extractIdempotencyKey(body []byte) string { func extractExpiresInSeconds(body []byte) int { var envelope struct { Params struct { - ExpiresInSeconds int `json:"expires_in_seconds"` + ExpiresInSeconds interface{} `json:"expires_in_seconds"` } `json:"params"` } if err := json.Unmarshal(body, &envelope); err != nil { return 0 } - if envelope.Params.ExpiresInSeconds < 0 { + var seconds int + switch v := envelope.Params.ExpiresInSeconds.(type) { + case float64: + seconds = int(v) + default: return 0 } - return envelope.Params.ExpiresInSeconds + if seconds < 0 { + return 0 + } + return seconds } const ( diff --git a/workspace-server/internal/handlers/delegation_test.go b/workspace-server/internal/handlers/delegation_test.go index 7d067d57..2f560972 100644 --- a/workspace-server/internal/handlers/delegation_test.go +++ b/workspace-server/internal/handlers/delegation_test.go @@ -282,6 +282,7 @@ func TestListDelegations_WithResults(t *testing.T) { dh := NewDelegationHandler(wh, broadcaster) now := time.Now() + deadline := now.Add(6 * time.Hour) // Ledger query returns rows — no fallback to activity_logs rows := sqlmock.NewRows([]string{ "delegation_id", "caller_id", "callee_id", "task_preview", @@ -290,10 +291,10 @@ func TestListDelegations_WithResults(t *testing.T) { }). AddRow("del-111", "ws-source", "ws-target", "Delegating to ws-target", "pending", "", "", - &now, &now.Add(6*time.Hour), now, now). + &now, &deadline, now, now). AddRow("del-222", "ws-source", "ws-target", "Delegation completed (hello world)", "completed", "hello world", "", - &now, &now.Add(6*time.Hour), now, now.Add(time.Minute)) + &now, &deadline, now, now.Add(time.Minute)) mock.ExpectQuery("SELECT d.delegation_id, d.caller_id, d.callee_id, d.task_preview"). WithArgs("ws-source"). @@ -1360,6 +1361,7 @@ func TestExtractResponseText_EmptyText(t *testing.T) { got := extractResponseText(body) if got != "" { t.Errorf("empty text: got %q, want %q", got, "") + } } // ---------- ListDelegations: ledger has rows → returns them (no activity_logs fallback) ---------- @@ -1372,6 +1374,7 @@ func TestListDelegations_LedgerRowsReturned(t *testing.T) { dh := NewDelegationHandler(wh, broadcaster) now := time.Now() + deadline := now.Add(6 * time.Hour) // Ledger query returns rows ledgerRows := sqlmock.NewRows([]string{ "delegation_id", "caller_id", "callee_id", "task_preview", @@ -1380,7 +1383,7 @@ func TestListDelegations_LedgerRowsReturned(t *testing.T) { }).AddRow( "del-ledger-001", "caller-uuid", "callee-uuid", "Analyze the codebase for bugs", "in_progress", "", "", - &now, &now.Add(6*time.Hour), now, now, + &now, &deadline, now, now, ) mock.ExpectQuery("SELECT d.delegation_id, d.caller_id, d.callee_id, d.task_preview"). WithArgs("caller-uuid"). @@ -1591,6 +1594,7 @@ func TestListDelegations_LedgerCompletedIncludesResultPreview(t *testing.T) { dh := NewDelegationHandler(wh, broadcaster) now := time.Now() + deadline := now.Add(6 * time.Hour) ledgerRows := sqlmock.NewRows([]string{ "delegation_id", "caller_id", "callee_id", "task_preview", "status", "result_preview", "error_detail", "last_heartbeat", @@ -1598,7 +1602,7 @@ func TestListDelegations_LedgerCompletedIncludesResultPreview(t *testing.T) { }).AddRow( "del-complete-001", "caller-uuid", "callee-uuid", "Run analysis", "completed", "Analysis complete: 42 issues found", "", - &now, &now.Add(6*time.Hour), now, now, + &now, &deadline, now, now, ) mock.ExpectQuery("SELECT d.delegation_id, d.caller_id, d.callee_id, d.task_preview"). WithArgs("caller-uuid"). @@ -1645,6 +1649,7 @@ func TestListDelegations_LedgerFailedIncludesErrorDetail(t *testing.T) { dh := NewDelegationHandler(wh, broadcaster) now := time.Now() + deadline := now.Add(6 * time.Hour) ledgerRows := sqlmock.NewRows([]string{ "delegation_id", "caller_id", "callee_id", "task_preview", "status", "result_preview", "error_detail", "last_heartbeat", @@ -1652,7 +1657,7 @@ func TestListDelegations_LedgerFailedIncludesErrorDetail(t *testing.T) { }).AddRow( "del-failed-001", "caller-uuid", "callee-uuid", "Fetch data", "failed", "", "Callee workspace not reachable", - &now, &now.Add(6*time.Hour), now, now, + &now, &deadline, now, now, ) mock.ExpectQuery("SELECT d.delegation_id, d.caller_id, d.callee_id, d.task_preview"). WithArgs("caller-uuid"). @@ -1682,7 +1687,6 @@ func TestListDelegations_LedgerFailedIncludesErrorDetail(t *testing.T) { t.Errorf("expected error detail, got %v", resp[0]["error"]) } if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unmet sqlmock expectations: %v", err) (fix(delegations): ListDelegations falls back to delegations table before activity_logs) + t.Errorf("unmet sqlmock expectations: %v", err) } } - (fix(delegations): ListDelegations falls back to delegations table before activity_logs) diff --git a/workspace-server/internal/handlers/workspace_crud.go b/workspace-server/internal/handlers/workspace_crud.go index df5008af..fcf2bb08 100644 --- a/workspace-server/internal/handlers/workspace_crud.go +++ b/workspace-server/internal/handlers/workspace_crud.go @@ -140,6 +140,14 @@ func (h *WorkspaceHandler) Update(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } + if wsDir, ok := body["workspace_dir"]; ok && wsDir != nil { + if dirStr, isStr := wsDir.(string); isStr && dirStr != "" { + if err := validateWorkspaceDir(dirStr); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace directory"}) + return + } + } + } ctx := c.Request.Context() diff --git a/workspace-server/internal/handlers/workspace_crud_test.go b/workspace-server/internal/handlers/workspace_crud_test.go index 7be1a6aa..6dfb5991 100644 --- a/workspace-server/internal/handlers/workspace_crud_test.go +++ b/workspace-server/internal/handlers/workspace_crud_test.go @@ -39,6 +39,11 @@ func newWorkspaceCrudHandler(t *testing.T) *WorkspaceHandler { return NewWorkspaceHandler(nil, nil, "", t.TempDir()) } +func expectWorkspaceLiveTokenCount(mock sqlmock.Sqlmock, count int) { + mock.ExpectQuery(`SELECT COUNT\(\*\) FROM workspace_auth_tokens`). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(count)) +} + // ---------- State ---------- func TestState_LegacyWorkspaceNoLiveToken(t *testing.T) { @@ -50,8 +55,7 @@ func TestState_LegacyWorkspaceNoLiveToken(t *testing.T) { // No live token — legacy workspace, no auth required. // HasAnyLiveToken always runs first (queries workspace_auth_tokens). - mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspace_auth_tokens`). - WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false)) + expectWorkspaceLiveTokenCount(mock, 0) mock.ExpectQuery(`SELECT status FROM workspaces WHERE id = \$1`). WithArgs(wsID). WillReturnRows(sqlmock.NewRows([]string{"status"}).AddRow("running")) @@ -86,8 +90,7 @@ func TestState_HasLiveTokenMissingAuth(t *testing.T) { wsID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspace_auth_tokens`). - WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true)) + expectWorkspaceLiveTokenCount(mock, 1) req, _ := http.NewRequest("GET", "/workspaces/"+wsID+"/state", nil) // No Authorization header @@ -106,8 +109,7 @@ func TestState_WorkspaceNotFound(t *testing.T) { wsID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspace_auth_tokens`). - WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false)) + expectWorkspaceLiveTokenCount(mock, 0) mock.ExpectQuery(`SELECT status FROM workspaces WHERE id = \$1`). WithArgs(wsID). WillReturnError(sql.ErrNoRows) @@ -136,8 +138,7 @@ func TestState_WorkspaceSoftDeleted(t *testing.T) { wsID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspace_auth_tokens`). - WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false)) + expectWorkspaceLiveTokenCount(mock, 0) mock.ExpectQuery(`SELECT status FROM workspaces WHERE id = \$1`). WithArgs(wsID). WillReturnRows(sqlmock.NewRows([]string{"status"}).AddRow("removed")) @@ -169,8 +170,7 @@ func TestState_QueryError(t *testing.T) { wsID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspace_auth_tokens`). - WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false)) + expectWorkspaceLiveTokenCount(mock, 0) mock.ExpectQuery(`SELECT status FROM workspaces WHERE id = \$1`). WithArgs(wsID). WillReturnError(sql.ErrConnDone)