package inbox import ( "os" "path/filepath" "testing" ) func TestCancelMarksThreadCancelled(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") runInboxCommand(t, "--db", dbPath, "--json", "init") sendOut := runInboxCommand( t, "--db", dbPath, "--json", "send", "--from", "leader", "--to", "worker-a", "--subject", "Implement cancellation", "--summary", "Initial request", ) var sendResp map[string]any mustDecodeJSON(t, sendOut, &sendResp) threadID := nestedString(t, sendResp, "data", "thread", "thread_id") cancelOut := runInboxCommand( t, "--db", dbPath, "--json", "cancel", "--agent", "leader", "--thread", threadID, "--reason", "Task superseded by a larger refactor", ) var cancelResp map[string]any mustDecodeJSON(t, cancelOut, &cancelResp) if status := nestedString(t, cancelResp, "data", "thread", "status"); status != "cancelled" { t.Fatalf("expected cancelled thread, got %q", status) } if kind := nestedString(t, cancelResp, "data", "message", "kind"); kind != "control" { t.Fatalf("expected control message, got %q", kind) } } func TestCancelPersistsReasonAndArtifact(t *testing.T) { t.Parallel() tempDir := t.TempDir() dbPath := filepath.Join(tempDir, "coord.db") cancelPath := filepath.Join(tempDir, "cancel.md") if err := os.WriteFile(cancelPath, []byte("Cancelled by product decision"), 0o644); err != nil { t.Fatalf("write cancel artifact: %v", err) } runInboxCommand(t, "--db", dbPath, "--json", "init") sendOut := runInboxCommand( t, "--db", dbPath, "--json", "send", "--from", "leader", "--to", "worker-a", "--subject", "Implement cancellation", "--summary", "Initial request", ) var sendResp map[string]any mustDecodeJSON(t, sendOut, &sendResp) threadID := nestedString(t, sendResp, "data", "thread", "thread_id") runInboxCommand( t, "--db", dbPath, "--json", "cancel", "--agent", "leader", "--thread", threadID, "--reason", "Task superseded by a larger refactor", "--artifact", cancelPath, "--artifact-kind", "brief", ) showOut := runInboxCommand( t, "--db", dbPath, "--json", "show", "--thread", threadID, ) var showResp map[string]any mustDecodeJSON(t, showOut, &showResp) messages, ok := nestedValue(t, showResp, "data", "messages").([]any) if !ok || len(messages) == 0 { t.Fatalf("expected non-empty message history, got %#v", nestedValue(t, showResp, "data", "messages")) } lastMessage, ok := messages[len(messages)-1].(map[string]any) if !ok { t.Fatalf("expected message object, got %#v", messages[len(messages)-1]) } if got := lastMessage["summary"]; got != "Task superseded by a larger refactor" { t.Fatalf("expected cancel summary, got %#v", got) } if got := lastMessage["body"]; got != "Task superseded by a larger refactor" { t.Fatalf("expected cancel body, got %#v", got) } artifacts, ok := lastMessage["artifacts"].([]any) if !ok || len(artifacts) != 1 { t.Fatalf("expected one cancel artifact, got %#v", lastMessage["artifacts"]) } artifact, ok := artifacts[0].(map[string]any) if !ok { t.Fatalf("expected artifact object, got %#v", artifacts[0]) } if got := artifact["path"]; got != cancelPath { t.Fatalf("expected artifact path %q, got %#v", cancelPath, got) } if got := artifact["kind"]; got != "brief" { t.Fatalf("expected artifact kind brief, got %#v", got) } } func TestCancelRejectsWhenThreadMissing(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") runInboxCommand(t, "--db", dbPath, "--json", "init") stdout, _, exitCode := executeInboxCommand( "--db", dbPath, "--agent", "leader", "--json", "cancel", "--thread", "thr_missing", ) if exitCode != 40 { t.Fatalf("expected not-found exit code 40, got %d with %s", exitCode, stdout) } assertErrorJSON(t, stdout, "not_found") }