package inbox import ( "os" "path/filepath" "testing" ) func TestReplyAddsAnswerMessage(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") threadID := seedThreadForInboxTests(t, dbPath, "leader", "worker-a") replyOut := runInboxCommand( t, "--db", dbPath, "--json", "reply", "--from", "leader", "--to", "worker-a", "--thread", threadID, "--summary", "Retry read timeouts", "--body", "Yes, include read timeouts in the retry policy.", ) var replyResp map[string]any mustDecodeJSON(t, replyOut, &replyResp) if kind := nestedString(t, replyResp, "data", "message", "kind"); kind != "answer" { t.Fatalf("expected answer message kind, got %q", kind) } if gotThreadID := nestedString(t, replyResp, "data", "thread", "thread_id"); gotThreadID != threadID { t.Fatalf("expected thread_id %q, got %q", threadID, gotThreadID) } if status := nestedString(t, replyResp, "data", "thread", "status"); status != "pending" { t.Fatalf("expected thread status pending, got %q", status) } } func TestReplySupportsControlKind(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") threadID := seedThreadForInboxTests(t, dbPath, "leader", "worker-a") replyOut := runInboxCommand( t, "--db", dbPath, "--json", "reply", "--from", "leader", "--to", "worker-a", "--thread", threadID, "--kind", "control", "--summary", "Pause rollout", "--body", "Pause rollout until QA confirms the fix.", ) var replyResp map[string]any mustDecodeJSON(t, replyOut, &replyResp) if kind := nestedString(t, replyResp, "data", "message", "kind"); kind != "control" { t.Fatalf("expected control message kind, got %q", kind) } } func TestReplyAttachesArtifact(t *testing.T) { t.Parallel() tempDir := t.TempDir() dbPath := filepath.Join(tempDir, "coord.db") decisionPath := filepath.Join(tempDir, "decision.md") if err := os.WriteFile(decisionPath, []byte("Decision note."), 0o644); err != nil { t.Fatalf("write decision file: %v", err) } threadID := seedThreadForInboxTests(t, dbPath, "leader", "worker-a") replyOut := runInboxCommand( t, "--db", dbPath, "--json", "reply", "--from", "leader", "--to", "worker-a", "--thread", threadID, "--summary", "Retry read timeouts", "--artifact", decisionPath, "--artifact-kind", "brief", "--artifact-metadata-json", `{"label":"decision"}`, ) var replyResp map[string]any mustDecodeJSON(t, replyOut, &replyResp) artifactsValue := nestedValue(t, replyResp, "data", "message", "artifacts") artifacts, ok := artifactsValue.([]any) if !ok || len(artifacts) != 1 { t.Fatalf("expected one artifact, got %#v", artifactsValue) } artifact, ok := artifacts[0].(map[string]any) if !ok { t.Fatalf("expected artifact object, got %#v", artifacts[0]) } if gotPath, _ := artifact["path"].(string); gotPath != decisionPath { t.Fatalf("expected artifact path %q, got %#v", decisionPath, artifact["path"]) } if gotKind, _ := artifact["kind"].(string); gotKind != "brief" { t.Fatalf("expected artifact kind brief, got %#v", artifact["kind"]) } metadata, ok := artifact["metadata_json"].(map[string]any) if !ok { t.Fatalf("expected metadata_json object, got %#v", artifact["metadata_json"]) } if gotLabel := metadata["label"]; gotLabel != "decision" { t.Fatalf("expected metadata label decision, got %#v", gotLabel) } } func TestReplyRejectsInvalidPayloadJSON(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") threadID := seedThreadForInboxTests(t, dbPath, "leader", "worker-a") stdout, _, exitCode := executeInboxCommand( "--db", dbPath, "--json", "reply", "--from", "leader", "--to", "worker-a", "--thread", threadID, "--summary", "Retry read timeouts", "--payload-json", "not-json", ) if exitCode != 30 { t.Fatalf("expected exit code 30, got %d with output %s", exitCode, stdout) } assertErrorJSON(t, stdout, "invalid_input") }