From 7a80cc064ac8a299a1ed166359f18ca339b74262 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Tue, 9 Jun 2026 17:05:18 +0000 Subject: [PATCH 1/2] test(scheduler): add missing unit tests for classifyTaskState, isEmptyResponse, a2aErrorFromBody Adds coverage for three previously-untested helpers in scheduler.go: - TestClassifyTaskState_*: verifies OK states return empty, failure states are surfaced, and malformed JSON is handled gracefully. - TestIsEmptyResponse_*: verifies empty bodies and sentinel strings are detected as empty, while actual content is not. - TestA2AErrorFromBody_*: verifies JSON-RPC and plain error extraction, plus empty/invalid JSON fallbacks. Full scheduler suite (49 tests) passes. Co-Authored-By: Claude Opus 4.8 --- .../internal/scheduler/scheduler_test.go | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/workspace-server/internal/scheduler/scheduler_test.go b/workspace-server/internal/scheduler/scheduler_test.go index 462f26f17..cc0d73d6f 100644 --- a/workspace-server/internal/scheduler/scheduler_test.go +++ b/workspace-server/internal/scheduler/scheduler_test.go @@ -1163,3 +1163,109 @@ func TestSanitizeUTF8(t *testing.T) { t.Errorf("sanitizeUTF8 did not produce valid UTF-8: %x", []byte(out)) } } + +// ── TestClassifyTaskState ─────────────────────────────────────────────────── + +func TestClassifyTaskState_NoStatus(t *testing.T) { + result := map[string]json.RawMessage{"other": json.RawMessage(`"x"`)} + if got := classifyTaskState(result); got != "" { + t.Errorf("classifyTaskState(no status) = %q, want empty", got) + } +} + +func TestClassifyTaskState_OKStates(t *testing.T) { + for _, state := range []string{"", "submitted", "working", "completed"} { + result := map[string]json.RawMessage{ + "status": json.RawMessage(`{"state":"` + state + `"}`), + } + if got := classifyTaskState(result); got != "" { + t.Errorf("classifyTaskState(%q) = %q, want empty (OK state)", state, got) + } + } +} + +func TestClassifyTaskState_FailureState(t *testing.T) { + result := map[string]json.RawMessage{ + "status": json.RawMessage(`{"state":"failed"}`), + } + if got := classifyTaskState(result); got != "failed" { + t.Errorf("classifyTaskState(failed) = %q, want failed", got) + } +} + +func TestClassifyTaskState_MalformedStatus(t *testing.T) { + result := map[string]json.RawMessage{ + "status": json.RawMessage(`{broken`), + } + if got := classifyTaskState(result); got != "" { + t.Errorf("classifyTaskState(malformed) = %q, want empty", got) + } +} + +// ── TestIsEmptyResponse ───────────────────────────────────────────────────── + +func TestIsEmptyResponse_EmptyBody(t *testing.T) { + if !isEmptyResponse([]byte{}) { + t.Error("isEmptyResponse(empty) should be true") + } +} + +func TestIsEmptyResponse_NoResponseGenerated(t *testing.T) { + if !isEmptyResponse([]byte(`(no response generated)`)) { + t.Error("isEmptyResponse(no-response-generated) should be true") + } +} + +func TestIsEmptyResponse_TextFieldEmpty(t *testing.T) { + if !isEmptyResponse([]byte(`{"result":{"parts":[{"text":""}]}}`)) { + t.Error("isEmptyResponse(empty text field) should be true") + } +} + +func TestIsEmptyResponse_TextFieldNoResponse(t *testing.T) { + if !isEmptyResponse([]byte(`{"result":{"parts":[{"text":"(no response generated)"}]}}`)) { + t.Error("isEmptyResponse(text=no-response-generated) should be true") + } +} + +func TestIsEmptyResponse_HasContent(t *testing.T) { + if isEmptyResponse([]byte(`{"result":{"parts":[{"text":"hello"}]}}`)) { + t.Error("isEmptyResponse(with content) should be false") + } +} + +// ── TestA2AErrorFromBody ──────────────────────────────────────────────────── + +func TestA2AErrorFromBody_Empty(t *testing.T) { + if got := a2aErrorFromBody([]byte{}); got != "" { + t.Errorf("a2aErrorFromBody(empty) = %q, want empty", got) + } +} + +func TestA2AErrorFromBody_JSONRPCMessage(t *testing.T) { + body := []byte(`{"error":{"code":-32603,"message":"internal error"}}`) + if got := a2aErrorFromBody(body); got != "internal error" { + t.Errorf("a2aErrorFromBody(JSON-RPC) = %q, want internal error", got) + } +} + +func TestA2AErrorFromBody_PlainString(t *testing.T) { + body := []byte(`{"error":"something went wrong"}`) + if got := a2aErrorFromBody(body); got != "something went wrong" { + t.Errorf("a2aErrorFromBody(plain) = %q, want something went wrong", got) + } +} + +func TestA2AErrorFromBody_NoError(t *testing.T) { + body := []byte(`{"result":"ok"}`) + if got := a2aErrorFromBody(body); got != "" { + t.Errorf("a2aErrorFromBody(no error) = %q, want empty", got) + } +} + +func TestA2AErrorFromBody_InvalidJSON(t *testing.T) { + body := []byte(`{broken`) + if got := a2aErrorFromBody(body); got != "" { + t.Errorf("a2aErrorFromBody(invalid) = %q, want empty", got) + } +} -- 2.52.0 From 6c9cc581c96d1d32471cdbfaea91d0cbb97c53eb Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Tue, 9 Jun 2026 18:15:32 +0000 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20retrigger=20CI=20=E2=80=94=20Local?= =?UTF-8?q?=20Provision=20E2E=20stub=20failed=20on=20provisioning=20timeou?= =?UTF-8?q?t=20(infra=20flake=20on=20main,=20unrelated=20to=20scheduler=20?= =?UTF-8?q?test=20addition)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.52.0