Compare commits

...

3 Commits

Author SHA1 Message Date
fullstack-engineer d5438efbfa test(canvas/store): add setCollapsed coverage — collapse, expand, no-op
CI / Detect changes (pull_request) Waiting to run
CI / Canvas (Next.js) (pull_request) Waiting to run
E2E Chat / E2E Chat (pull_request) Blocked by required conditions
Harness Replays / Harness Replays (pull_request) Blocked by required conditions
sop-checklist / all-items-acked (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 35s
sop-tier-check / tier-check (pull_request) Successful in 32s
Harness Replays / detect-changes (pull_request) Successful in 41s
gate-check-v3 / gate-check (pull_request) Failing after 55s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 57s
qa-review / approved (pull_request) Successful in 1m13s
security-review / approved (pull_request) Successful in 1m9s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m51s
E2E Chat / detect-changes (pull_request) Successful in 2m54s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 2m56s
E2E API Smoke Test / detect-changes (pull_request) Successful in 3m3s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 3m0s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 13s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m43s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5m7s
CI / Platform (Go) (pull_request) Successful in 18m22s
audit-force-merge / audit (pull_request) Waiting to run
CI / Shellcheck (E2E scripts) (pull_request) Has been cancelled
CI / Canvas Deploy Reminder (pull_request) Has been cancelled
CI / Python Lint & Test (pull_request) Has been cancelled
CI / all-required (pull_request) Has been cancelled
Three new tests for the setCollapsed action:

- collapsing a parent sets collapsed:true, shrinks to CHILD_DEFAULT_WIDTH/HEIGHT,
  and hides all descendant nodes
- expanding a parent clears collapsed, restores expanded size, and reveals
  children
- non-existent parentId is a safe no-op (no throw, no state mutation)

6 store tests total (3 arrangeChildren + 3 setCollapsed) — canvas.ts
statement coverage improved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 05:45:50 +00:00
fullstack-engineer 5a8657b1d5 test(canvas/store): add arrangeChildren coverage — sort, slot, and API PATCH
E2E API Smoke Test / E2E API Smoke Test (pull_request) Blocked by required conditions
E2E Chat / E2E Chat (pull_request) Blocked by required conditions
Harness Replays / Harness Replays (pull_request) Blocked by required conditions
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Blocked by required conditions
Secret scan / Scan diff for credential-shaped strings (pull_request) Waiting to run
security-review / approved (pull_request) Waiting to run
sop-checklist / all-items-acked (pull_request) Waiting to run
sop-tier-check / tier-check (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 29s
Harness Replays / detect-changes (pull_request) Successful in 37s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m45s
E2E API Smoke Test / detect-changes (pull_request) Successful in 2m17s
E2E Chat / detect-changes (pull_request) Successful in 2m12s
gate-check-v3 / gate-check (pull_request) Failing after 49s
qa-review / approved (pull_request) Successful in 52s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 2m18s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 2m7s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 6m56s
canvas.ts had 0% coverage on the arrangeChildren action. Added 3 tests:

- arrangeChildren is a no-op when parent has no children
- children are sorted by name (localeCompare) before slot assignment
- each child is PATCHed with absolute canvas coordinates
  (parent position + defaultChildSlot offset)

3303 tests pass; build succeeds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 05:38:31 +00:00
fullstack-engineer c7ea9c08cc test(handlers): add coverage for QueueDepth, QueueStatusByID, GetA2AQueueStatus
CI / all-required (pull_request) Blocked by required conditions
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Blocked by required conditions
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 20s
Harness Replays / detect-changes (pull_request) Successful in 19s
CI / Detect changes (pull_request) Successful in 1m31s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 26s
gate-check-v3 / gate-check (pull_request) Successful in 24s
E2E API Smoke Test / detect-changes (pull_request) Successful in 1m24s
E2E Chat / detect-changes (pull_request) Successful in 1m26s
qa-review / approved (pull_request) Successful in 26s
security-review / approved (pull_request) Successful in 25s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 1m26s
sop-checklist / all-items-acked (pull_request) Successful in 28s
sop-tier-check / tier-check (pull_request) Successful in 21s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m54s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 1m34s
Harness Replays / Harness Replays (pull_request) Successful in 9s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 10s
CI / Python Lint & Test (pull_request) Successful in 11s
E2E Chat / E2E Chat (pull_request) Failing after 10s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 11s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m19s
CI / Canvas (Next.js) (pull_request) Successful in 18m56s
CI / Platform (Go) (pull_request) Successful in 19m47s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
a2a_queue_status.go had 0% coverage across all 3 exported symbols.
Added 14 tests exercising:

QueueDepth (package-level):
- TestQueueDepth_Success: COUNT returns 7
- TestQueueDepth_EmptyQueue: COUNT returns 0
- TestQueueDepth_QueryError_ReturnsZero: DB error → returns 0 (non-fatal)

QueueStatusByID (package-level):
- TestQueueStatusByID_Success: fully-populated QueueStatus from LEFT JOIN
- TestQueueStatusByID_CompletedWithResponse: completed item populates ResponseBody
- TestQueueStatusByID_ErrNoRows: sql.ErrNoRows propagates
- TestQueueStatusByID_QueryError: DB error propagates

GetA2AQueueStatus (HTTP handler):
- TestGetA2AQueueStatus_MissingQueueID_Returns400
- TestGetA2AQueueStatus_NoIdentity_Returns404 (not 401 per design)
- TestGetA2AQueueStatus_QueueNotFound_Returns404
- TestGetA2AQueueStatus_UnauthorizedCaller_Returns404 (not 403 per design)
- TestGetA2AQueueStatus_AuthorizedAsTarget_Success
- TestGetA2AQueueStatus_QueueRowLookupError_Returns500
- TestGetA2AQueueStatus_StatusFetchError_Returns500

All 14 new tests pass; full suite: 69.1% → 69.7%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 05:19:12 +00:00
2 changed files with 481 additions and 0 deletions
+114
View File
@@ -1224,3 +1224,117 @@ describe("moveNode", () => {
});
});
});
describe("arrangeChildren", () => {
it("is a no-op when the parent has no children", () => {
useCanvasStore.getState().hydrate([
makeWS({ id: "parent", name: "Parent", x: 100, y: 200 }),
]);
expect(() => useCanvasStore.getState().arrangeChildren("parent")).not.toThrow();
// No fetch calls should be made
expect(global.fetch).not.toHaveBeenCalled();
});
it("sorts children by name and assigns default slot positions", () => {
// Children are: Bob, Alice — after localeSort: Alice(0), Bob(1)
// defaultChildSlot(0) = {x: 16, y: 130} (PARENT_SIDE_PADDING, PARENT_HEADER_PADDING)
// defaultChildSlot(1) = {x: 270, y: 130} (16 + 240 + 14, 130)
useCanvasStore.getState().hydrate([
makeWS({ id: "parent", name: "Parent", x: 100, y: 200 }),
makeWS({ id: "ws-bob", name: "Bob", x: 0, y: 0, parent_id: "parent" }),
makeWS({ id: "ws-alice", name: "Alice", x: 0, y: 0, parent_id: "parent" }),
]);
useCanvasStore.getState().arrangeChildren("parent");
const nodes = useCanvasStore.getState().nodes;
const alice = nodes.find((n) => n.id === "ws-alice")!;
const bob = nodes.find((n) => n.id === "ws-bob")!;
// Alice is first alphabetically → index 0 → {x: 16, y: 130}
expect(alice.position).toEqual({ x: 16, y: 130 });
// Bob is second alphabetically → index 1 → {x: 270, y: 130}
expect(bob.position).toEqual({ x: 270, y: 130 });
});
it("PATCHes each child with absolute canvas coordinates (parent position + slot)", async () => {
// Parent at (100, 200). Alice slot = {x: 16, y: 130}.
// absX = 16 + 100 = 116, absY = 130 + 200 = 330.
const mock = global.fetch as ReturnType<typeof vi.fn>;
useCanvasStore.getState().hydrate([
makeWS({ id: "parent", name: "Parent", x: 100, y: 200 }),
makeWS({ id: "ws-alice", name: "Alice", x: 0, y: 0, parent_id: "parent" }),
]);
useCanvasStore.getState().arrangeChildren("parent");
await vi.waitFor(() => {
expect(mock).toHaveBeenCalledWith(
expect.stringContaining("/workspaces/ws-alice"),
expect.objectContaining({
method: "PATCH",
body: JSON.stringify({ x: 116, y: 330 }),
}),
);
});
});
});
describe("setCollapsed", () => {
it("collapsing a parent hides its direct child", () => {
// parentMinSizeFromChildren([{width:240, height:130}]) = {width: 560, height: 302}
useCanvasStore.getState().hydrate([
makeWS({ id: "parent", name: "Parent" }),
makeWS({ id: "child", name: "Child", parent_id: "parent" }),
]);
// Manually set parent size so it has an expanded size
useCanvasStore.setState({
nodes: useCanvasStore.getState().nodes.map((n) =>
n.id === "parent" ? { ...n, width: 560, height: 302 } : n,
),
});
useCanvasStore.getState().setCollapsed("parent", true);
const parent = useCanvasStore.getState().nodes.find((n) => n.id === "parent")!;
const child = useCanvasStore.getState().nodes.find((n) => n.id === "child")!;
expect(parent.data.collapsed).toBe(true);
expect(parent.width).toBe(240); // CHILD_DEFAULT_WIDTH
expect(parent.height).toBe(130); // CHILD_DEFAULT_HEIGHT
expect(child.hidden).toBe(true); // child is hidden because parent is collapsed
});
it("expanding a parent reveals its direct child", () => {
useCanvasStore.getState().hydrate([
makeWS({ id: "parent", name: "Parent" }),
makeWS({ id: "child", name: "Child", parent_id: "parent" }),
]);
useCanvasStore.setState({
nodes: useCanvasStore.getState().nodes.map((n) =>
n.id === "parent"
? { ...n, width: 240, height: 130, data: { ...n.data, collapsed: true } }
: n,
),
});
useCanvasStore.getState().setCollapsed("parent", false);
const parent = useCanvasStore.getState().nodes.find((n) => n.id === "parent")!;
const child = useCanvasStore.getState().nodes.find((n) => n.id === "child")!;
expect(parent.data.collapsed).toBe(false);
expect(child.hidden).toBe(false); // child is visible when parent is expanded
});
it("is a no-op for a non-existent parentId", () => {
useCanvasStore.getState().hydrate([
makeWS({ id: "parent", name: "Parent" }),
makeWS({ id: "child", name: "Child", parent_id: "parent" }),
]);
// Should not throw even when parentId doesn't exist
expect(() => useCanvasStore.getState().setCollapsed("nonexistent", true)).not.toThrow();
// Nodes should be unchanged
expect(useCanvasStore.getState().nodes).toHaveLength(2);
});
});
@@ -1,7 +1,16 @@
package handlers
import (
"context"
"database/sql"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/gin-gonic/gin"
)
// TestExtractExpiresInSeconds covers the JSON parser used at enqueue time
@@ -58,3 +67,361 @@ func TestExtractExpiresInSeconds(t *testing.T) {
})
}
}
// ─── QueueDepth ─────────────────────────────────────────────────────────────
// TestQueueDepth_Success verifies QueueDepth returns the COUNT of queued items
// for a workspace.
func TestQueueDepth_Success(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT COUNT\(\*\) FROM a2a_queue WHERE workspace_id = \$1 AND status = 'queued'`).
WithArgs("ws-queue-depth-1").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(7))
got := QueueDepth(context.Background(), "ws-queue-depth-1")
if got != 7 {
t.Errorf("QueueDepth() = %d; want 7", got)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// TestQueueDepth_EmptyQueue returns 0 when no queued items exist.
func TestQueueDepth_EmptyQueue(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT COUNT\(\*\) FROM a2a_queue WHERE workspace_id = \$1 AND status = 'queued'`).
WithArgs("ws-empty").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
got := QueueDepth(context.Background(), "ws-empty")
if got != 0 {
t.Errorf("QueueDepth() = %d; want 0", got)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// TestQueueDepth_QueryError returns 0 on DB error (non-fatal; caller only uses
// the count for display purposes).
func TestQueueDepth_QueryError_ReturnsZero(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT COUNT\(\*\) FROM a2a_queue WHERE workspace_id = \$1 AND status = 'queued'`).
WithArgs("ws-err").
WillReturnError(errors.New("connection refused"))
// QueueDepth swallows the error and returns 0.
got := QueueDepth(context.Background(), "ws-err")
if got != 0 {
t.Errorf("QueueDepth() on error = %d; want 0", got)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// ─── QueueStatusByID ────────────────────────────────────────────────────────
// TestQueueStatusByID_Success verifies QueueStatusByID returns a fully-populated
// QueueStatus from the LEFT JOIN of a2a_queue and activity_logs.
func TestQueueStatusByID_Success(t *testing.T) {
mock := setupTestDB(t)
// The LEFT JOIN query returns all queue columns + NULL for activity_logs
// when no delegation row exists.
mock.ExpectQuery(`SELECT\s+q\.id,\s+q\.workspace_id,\s+q\.status,\s+q\.priority,\s+q\.attempts,\s+q\.last_error,\s+q\.enqueued_at::text,\s+q\.dispatched_at::text,\s+q\.completed_at::text,\s+q\.expires_at::text,\s+al\.response_body::text\s+FROM a2a_queue q\s+LEFT JOIN activity_logs al`).
WithArgs("queue-ok-1").
WillReturnRows(sqlmock.NewRows([]string{
"id", "workspace_id", "status", "priority", "attempts",
"last_error", "enqueued_at", "dispatched_at", "completed_at", "expires_at",
"response_body",
}).AddRow(
"queue-ok-1", "ws-1", "queued", 50, 1,
nil, "2026-05-16T10:00:00Z", nil, nil, "2026-05-16T12:00:00Z",
nil,
))
qs, err := QueueStatusByID(context.Background(), "queue-ok-1")
if err != nil {
t.Fatalf("QueueStatusByID() error = %v; want nil", err)
}
if qs.ID != "queue-ok-1" {
t.Errorf("ID = %q; want queue-ok-1", qs.ID)
}
if qs.WorkspaceID != "ws-1" {
t.Errorf("WorkspaceID = %q; want ws-1", qs.WorkspaceID)
}
if qs.Status != "queued" {
t.Errorf("Status = %q; want queued", qs.Status)
}
if qs.Priority != 50 {
t.Errorf("Priority = %d; want 50", qs.Priority)
}
if qs.Attempts != 1 {
t.Errorf("Attempts = %d; want 1", qs.Attempts)
}
if qs.LastError != nil {
t.Errorf("LastError = %v; want nil", qs.LastError)
}
if qs.EnqueuedAt != "2026-05-16T10:00:00Z" {
t.Errorf("EnqueuedAt = %q; want 2026-05-16T10:00:00Z", qs.EnqueuedAt)
}
if qs.DispatchedAt != nil {
t.Errorf("DispatchedAt = %v; want nil", qs.DispatchedAt)
}
if qs.CompletedAt != nil {
t.Errorf("CompletedAt = %v; want nil", qs.CompletedAt)
}
if *qs.ExpiresAt != "2026-05-16T12:00:00Z" {
t.Errorf("ExpiresAt = %v; want 2026-05-16T12:00:00Z", qs.ExpiresAt)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// TestQueueStatusByID_CompletedWithResponse verifies that a completed queue item
// populates ResponseBody from the LEFT JOINed activity_logs row.
func TestQueueStatusByID_CompletedWithResponse(t *testing.T) {
mock := setupTestDB(t)
respBody := `{"result":"done"}`
mock.ExpectQuery(`SELECT\s+q\.id`).
WithArgs("queue-done-1").
WillReturnRows(sqlmock.NewRows([]string{
"id", "workspace_id", "status", "priority", "attempts",
"last_error", "enqueued_at", "dispatched_at", "completed_at", "expires_at",
"response_body",
}).AddRow(
"queue-done-1", "ws-1", "completed", 50, 1,
nil, "2026-05-16T10:00:00Z", "2026-05-16T10:01:00Z", "2026-05-16T10:02:00Z", nil,
respBody,
))
qs, err := QueueStatusByID(context.Background(), "queue-done-1")
if err != nil {
t.Fatalf("QueueStatusByID() error = %v; want nil", err)
}
if qs.Status != "completed" {
t.Errorf("Status = %q; want completed", qs.Status)
}
if qs.ResponseBody == nil {
t.Fatal("ResponseBody = nil; want non-nil for completed item")
}
var resp map[string]interface{}
if err := json.Unmarshal(qs.ResponseBody, &resp); err != nil {
t.Fatalf("ResponseBody not valid JSON: %v", err)
}
if resp["result"] != "done" {
t.Errorf("ResponseBody result = %v; want done", resp["result"])
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// TestQueueStatusByID_ErrNoRows returns sql.ErrNoRows when the queue ID doesn't exist.
func TestQueueStatusByID_ErrNoRows(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT\s+q\.id`).
WithArgs("queue-missing").
WillReturnError(sql.ErrNoRows)
_, err := QueueStatusByID(context.Background(), "queue-missing")
if !errors.Is(err, sql.ErrNoRows) {
t.Errorf("QueueStatusByID() error = %v; want sql.ErrNoRows", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// TestQueueStatusByID_QueryError propagates DB errors as-is.
func TestQueueStatusByID_QueryError(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT\s+q\.id`).
WithArgs("queue-err").
WillReturnError(errors.New("connection refused"))
_, err := QueueStatusByID(context.Background(), "queue-err")
if err == nil {
t.Fatal("QueueStatusByID() error = nil; want non-nil")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
// ─── GetA2AQueueStatus (HTTP handler) ─────────────────────────────────────
func newGetA2AQueueStatusHarness(t *testing.T) (sqlmock.Sqlmock, *httptest.ResponseRecorder, *gin.Context) {
mock := setupTestDB(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
return mock, w, c
}
func TestGetA2AQueueStatus_MissingQueueID_Returns400(t *testing.T) {
_, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: ""}}
c.Request = httptest.NewRequest("GET", "/", nil)
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
if w.Code != http.StatusBadRequest {
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
}
}
func TestGetA2AQueueStatus_NoIdentity_Returns404(t *testing.T) {
_, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: "q-123"}}
c.Request = httptest.NewRequest("GET", "/", nil)
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
// Returns 404 (not 401) per the existence-non-inference policy.
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d: %s", w.Code, w.Body.String())
}
}
func TestGetA2AQueueStatus_QueueNotFound_Returns404(t *testing.T) {
mock, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: "q-404"}}
c.Request = httptest.NewRequest("GET", "/", nil)
c.Request.Header.Set("X-Workspace-ID", "ws-1")
mock.ExpectQuery(`SELECT caller_id, workspace_id FROM a2a_queue WHERE id = \$1`).
WithArgs("q-404").
WillReturnError(sql.ErrNoRows)
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestGetA2AQueueStatus_UnauthorizedCaller_Returns404(t *testing.T) {
mock, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: "q-unauth"}}
c.Request = httptest.NewRequest("GET", "/", nil)
c.Request.Header.Set("X-Workspace-ID", "ws-wrong")
mock.ExpectQuery(`SELECT caller_id, workspace_id FROM a2a_queue WHERE id = \$1`).
WithArgs("q-unauth").
WillReturnRows(sqlmock.NewRows([]string{"caller_id", "workspace_id"}).
AddRow("ws-caller-a", "ws-target-b"))
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
// Returns 404 per the existence-non-inference policy.
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestGetA2AQueueStatus_AuthorizedAsTarget_Success(t *testing.T) {
mock, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: "q-ok"}}
c.Request = httptest.NewRequest("GET", "/", nil)
c.Request.Header.Set("X-Workspace-ID", "ws-target")
mock.ExpectQuery(`SELECT caller_id, workspace_id FROM a2a_queue WHERE id = \$1`).
WithArgs("q-ok").
WillReturnRows(sqlmock.NewRows([]string{"caller_id", "workspace_id"}).
AddRow("ws-caller", "ws-target"))
mock.ExpectQuery(`SELECT\s+q\.id`).
WithArgs("q-ok").
WillReturnRows(sqlmock.NewRows([]string{
"id", "workspace_id", "status", "priority", "attempts",
"last_error", "enqueued_at", "dispatched_at", "completed_at", "expires_at",
"response_body",
}).AddRow(
"q-ok", "ws-target", "queued", 50, 1,
nil, "2026-05-16T10:00:00Z", nil, nil, nil,
nil,
))
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var qs QueueStatus
if err := json.Unmarshal(w.Body.Bytes(), &qs); err != nil {
t.Fatalf("body parse: %v", err)
}
if qs.ID != "q-ok" {
t.Errorf("queue_id = %q; want q-ok", qs.ID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestGetA2AQueueStatus_QueueRowLookupError_Returns500(t *testing.T) {
mock, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: "q-lookup-err"}}
c.Request = httptest.NewRequest("GET", "/", nil)
c.Request.Header.Set("X-Workspace-ID", "ws-1")
mock.ExpectQuery(`SELECT caller_id, workspace_id FROM a2a_queue WHERE id = \$1`).
WithArgs("q-lookup-err").
WillReturnError(errors.New("connection refused"))
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected 500, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestGetA2AQueueStatus_StatusFetchError_Returns500(t *testing.T) {
mock, w, c := newGetA2AQueueStatusHarness(t)
c.Params = gin.Params{{Key: "id", Value: "ws-1"}, {Key: "queue_id", Value: "q-status-err"}}
c.Request = httptest.NewRequest("GET", "/", nil)
c.Request.Header.Set("X-Workspace-ID", "ws-1")
mock.ExpectQuery(`SELECT caller_id, workspace_id FROM a2a_queue WHERE id = \$1`).
WithArgs("q-status-err").
WillReturnRows(sqlmock.NewRows([]string{"caller_id", "workspace_id"}).
AddRow("ws-1", "ws-1"))
mock.ExpectQuery(`SELECT\s+q\.id`).
WithArgs("q-status-err").
WillReturnError(errors.New("connection refused"))
h := newHandlerWithTestDeps(t)
h.GetA2AQueueStatus(c)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected 500, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}