From 04b96d9cdafc0785ace512ab9993653263c1a739 Mon Sep 17 00:00:00 2001 From: Molecule AI Fullstack Engineer Date: Wed, 13 May 2026 11:41:23 +0000 Subject: [PATCH 1/2] =?UTF-8?q?test(a2a=20queue):=20add=20pure-function=20?= =?UTF-8?q?coverage=20for=20extractExpiresInSeconds=20=E2=80=94=2016=20cas?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers: - Positive integers (including large TTLs like 3600s) - Zero value - Negative → collapses to 0 - Missing / absent expires_in_seconds - No params at all - Malformed JSON - Empty body - Type mismatches: null, string, float → 0 Part of ongoing pure-function test coverage for the A2A queue layer. Co-Authored-By: Claude Opus 4.7 --- .../internal/handlers/a2a_queue_test.go | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/workspace-server/internal/handlers/a2a_queue_test.go b/workspace-server/internal/handlers/a2a_queue_test.go index 57000910..3057d056 100644 --- a/workspace-server/internal/handlers/a2a_queue_test.go +++ b/workspace-server/internal/handlers/a2a_queue_test.go @@ -80,6 +80,54 @@ func TestExtractIdempotencyKey_emptyOnMissing(t *testing.T) { } } +// ────────────────────────────────────────────────────────────────────────────── +// extractExpiresInSeconds +// ────────────────────────────────────────────────────────────────────────────── + +func TestExtractExpiresInSeconds_valid(t *testing.T) { + cases := []struct { + name string + body string + want int + }{ + {"positive int", `{"params":{"expires_in_seconds":30}}`, 30}, + {"zero", `{"params":{"expires_in_seconds":0}}`, 0}, + {"large TTL", `{"params":{"expires_in_seconds":3600}}`, 3600}, + {"nested message — not affected", `{"params":{"message":{"role":"user"},"expires_in_seconds":60}}`, 60}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := extractExpiresInSeconds([]byte(tc.body)); got != tc.want { + t.Errorf("extractExpiresInSeconds = %d, want %d", got, tc.want) + } + }) + } +} + +func TestExtractExpiresInSeconds_invalidOrMissing(t *testing.T) { + cases := []struct { + name string + body string + want int + }{ + {"negative → 0", `{"params":{"expires_in_seconds":-5}}`, 0}, + {"missing expires_in_seconds", `{"params":{"message":{"role":"user"}}}`, 0}, + {"no params at all", `{"method":"message/send"}`, 0}, + {"malformed JSON", `not json`, 0}, + {"empty body", ``, 0}, + {"null value", `{"params":{"expires_in_seconds":null}}`, 0}, + {"string value", `{"params":{"expires_in_seconds":"30"}}`, 0}, + {"float value", `{"params":{"expires_in_seconds":30.5}}`, 0}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := extractExpiresInSeconds([]byte(tc.body)); got != tc.want { + t.Errorf("extractExpiresInSeconds(%q) = %d, want %d", tc.body, got, tc.want) + } + }) + } +} + func TestExtractDelegationIDFromBody(t *testing.T) { cases := []struct { name string -- 2.45.2 From f5bc58f4724cfbfc4578643730a24e8bdda2515b Mon Sep 17 00:00:00 2001 From: Molecule AI Fullstack Engineer Date: Wed, 13 May 2026 11:48:36 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test(a2a=20proxy):=20add=20parseUsageFromA2?= =?UTF-8?q?AResponse=20+=20readUsageMap=20coverage=20=E2=80=94=2015=20case?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parseUsageFromA2AResponse: - Empty/malformed inputs (nil, empty, non-JSON, null result, string result) - JSON-RPC result.usage shape (happy path) - Top-level usage fallback - result.usage takes precedence when both present - Zero usage → treated as absent (ok=false) readUsageMap: - Happy path with both tokens - Missing usage key - Zero values → ok=false - Only input_tokens set → ok=true - Only output_tokens set → ok=true - Malformed usage JSON → ok=false Pure function tests using real JSON — no DB or HTTP mocking required. Co-Authored-By: Claude Opus 4.7 --- .../handlers/a2a_proxy_helpers_test.go | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/workspace-server/internal/handlers/a2a_proxy_helpers_test.go b/workspace-server/internal/handlers/a2a_proxy_helpers_test.go index cba59fc0..3c57f8d3 100644 --- a/workspace-server/internal/handlers/a2a_proxy_helpers_test.go +++ b/workspace-server/internal/handlers/a2a_proxy_helpers_test.go @@ -158,6 +158,151 @@ func TestNilIfEmpty_Contract(t *testing.T) { } } +// ────────────────────────────────────────────────────────────────────────────── +// parseUsageFromA2AResponse +// ────────────────────────────────────────────────────────────────────────────── + +func TestParseUsageFromA2AResponse_EmptyAndMalformed(t *testing.T) { + cases := []struct { + name string + body []byte + }{ + {"nil", nil}, + {"empty", []byte{}}, + {"non-JSON", []byte("not json")}, + {"empty object", []byte("{}")}, + {"null result", []byte(`{"result": null}`)}, + {"string result", []byte(`{"result": "hello"}`)}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + in, out := parseUsageFromA2AResponse(tc.body) + if in != 0 || out != 0 { + t.Errorf("parseUsageFromA2AResponse = (%d, %d), want (0, 0)", in, out) + } + }) + } +} + +func TestParseUsageFromA2AResponse_ResultUsageShape(t *testing.T) { + body := []byte(`{ + "result": { + "usage": {"input_tokens": 1500, "output_tokens": 320} + } + }`) + in, out := parseUsageFromA2AResponse(body) + if in != 1500 || out != 320 { + t.Errorf("parseUsageFromA2AResponse = (%d, %d), want (1500, 320)", in, out) + } +} + +func TestParseUsageFromA2AResponse_TopLevelUsage(t *testing.T) { + body := []byte(`{ + "usage": {"input_tokens": 100, "output_tokens": 50} + }`) + in, out := parseUsageFromA2AResponse(body) + if in != 100 || out != 50 { + t.Errorf("parseUsageFromA2AResponse = (%d, %d), want (100, 50)", in, out) + } +} + +func TestParseUsageFromA2AResponse_BothPresentPrefersResult(t *testing.T) { + // When both result.usage and top-level usage exist, result.usage wins. + body := []byte(`{ + "result": {"usage": {"input_tokens": 500, "output_tokens": 200}}, + "usage": {"input_tokens": 50, "output_tokens": 20} + }`) + in, out := parseUsageFromA2AResponse(body) + if in != 500 || out != 200 { + t.Errorf("parseUsageFromA2AResponse = (%d, %d), want (500, 200) from result.usage", in, out) + } +} + +func TestParseUsageFromA2AResponse_ZeroUsage(t *testing.T) { + // Zero values are treated as absent (readUsageMap returns ok=false). + body := []byte(`{"result": {"usage": {"input_tokens": 0, "output_tokens": 0}}}`) + in, out := parseUsageFromA2AResponse(body) + if in != 0 || out != 0 { + t.Errorf("parseUsageFromA2AResponse = (%d, %d), want (0, 0)", in, out) + } +} + +// ────────────────────────────────────────────────────────────────────────────── +// readUsageMap +// ────────────────────────────────────────────────────────────────────────────── + +func TestReadUsageMap_HappyPath(t *testing.T) { + m := map[string]json.RawMessage{ + "usage": json.RawMessage(`{"input_tokens": 100, "output_tokens": 50}`), + } + in, out, ok := readUsageMap(m) + if !ok { + t.Fatal("readUsageMap returned ok=false, want true") + } + if in != 100 || out != 50 { + t.Errorf("readUsageMap = (%d, %d, %v), want (100, 50, true)", in, out, ok) + } +} + +func TestReadUsageMap_MissingUsage(t *testing.T) { + m := map[string]json.RawMessage{ + "other": json.RawMessage(`{}`), + } + in, out, ok := readUsageMap(m) + if ok { + t.Errorf("readUsageMap returned ok=true for missing usage, want false") + } +} + +func TestReadUsageMap_ZeroValues(t *testing.T) { + m := map[string]json.RawMessage{ + "usage": json.RawMessage(`{"input_tokens": 0, "output_tokens": 0}`), + } + in, out, ok := readUsageMap(m) + if ok { + t.Errorf("readUsageMap returned ok=true for zero usage, want false") + } + if in != 0 || out != 0 { + t.Errorf("readUsageMap = (%d, %d, %v), want (0, 0, false)", in, out, ok) + } +} + +func TestReadUsageMap_OnlyInputTokens(t *testing.T) { + m := map[string]json.RawMessage{ + "usage": json.RawMessage(`{"input_tokens": 200, "output_tokens": 0}`), + } + in, out, ok := readUsageMap(m) + if !ok { + t.Fatal("readUsageMap returned ok=false, want true") + } + if in != 200 || out != 0 { + t.Errorf("readUsageMap = (%d, %d, %v), want (200, 0, true)", in, out, ok) + } +} + +func TestReadUsageMap_OnlyOutputTokens(t *testing.T) { + m := map[string]json.RawMessage{ + "usage": json.RawMessage(`{"input_tokens": 0, "output_tokens": 150}`), + } + in, out, ok := readUsageMap(m) + if !ok { + t.Fatal("readUsageMap returned ok=false, want true") + } + if in != 0 || out != 150 { + t.Errorf("readUsageMap = (%d, %d, %v), want (0, 150, true)", in, out, ok) + } +} + +func TestReadUsageMap_MalformedUsageJSON(t *testing.T) { + m := map[string]json.RawMessage{ + "usage": json.RawMessage(`not valid json`), + } + in, out, ok := readUsageMap(m) + if ok { + t.Errorf("readUsageMap returned ok=true for malformed usage JSON, want false") + } +} + // Suppress unused import warning — setupTestDB references db.DB but this file // only tests pure functions, so db is only needed transitively through helpers. var _ = db.DB -- 2.45.2