package inbox import ( "os" "path/filepath" "testing" ) func TestFailMarksThreadFailed(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") threadID := seedClaimedThreadForInboxTests(t, dbPath, "leader", "worker-b", "worker-b") failOut := runInboxCommand( t, "--db", dbPath, "--json", "fail", "--agent", "worker-b", "--thread", threadID, "--summary", "Migration failed", "--body", "The migration cannot proceed because the prior schema is inconsistent.", ) var failResp map[string]any mustDecodeJSON(t, failOut, &failResp) if status := nestedString(t, failResp, "data", "thread", "status"); status != "failed" { t.Fatalf("expected failed thread status, got %q", status) } if kind := nestedString(t, failResp, "data", "message", "kind"); kind != "result" { t.Fatalf("expected result message kind, got %q", kind) } if toAgent := nestedString(t, failResp, "data", "message", "to_agent"); toAgent != "leader" { t.Fatalf("expected message to_agent leader, got %q", toAgent) } } func TestFailPersistsFailureBodyAndArtifact(t *testing.T) { t.Parallel() tempDir := t.TempDir() dbPath := filepath.Join(tempDir, "coord.db") failurePath := filepath.Join(tempDir, "failure.md") body := "Failure details from file." if err := os.WriteFile(failurePath, []byte(body), 0o644); err != nil { t.Fatalf("write failure file: %v", err) } threadID := seedClaimedThreadForInboxTests(t, dbPath, "leader", "worker-b", "worker-b") runInboxCommand( t, "--db", dbPath, "--json", "fail", "--agent", "worker-b", "--thread", threadID, "--summary", "Migration failed", "--body-file", failurePath, "--artifact", failurePath, "--artifact-kind", "report", ) showOut := runInboxCommand( t, "--db", dbPath, "--json", "show", "--thread", threadID, ) var showResp map[string]any mustDecodeJSON(t, showOut, &showResp) lastMessage := lastThreadMessageFromShow(t, showResp) if gotBody, _ := lastMessage["body"].(string); gotBody != body { t.Fatalf("expected body %q, got %#v", body, lastMessage["body"]) } artifacts, ok := lastMessage["artifacts"].([]any) if !ok || len(artifacts) != 1 { t.Fatalf("expected one artifact, got %#v", lastMessage["artifacts"]) } artifact, ok := artifacts[0].(map[string]any) if !ok { t.Fatalf("expected artifact object, got %#v", artifacts[0]) } if gotPath, _ := artifact["path"].(string); gotPath != failurePath { t.Fatalf("expected artifact path %q, got %#v", failurePath, artifact["path"]) } if gotKind, _ := artifact["kind"].(string); gotKind != "report" { t.Fatalf("expected artifact kind report, got %#v", artifact["kind"]) } } func TestFailRejectsNonOwner(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") threadID := seedClaimedThreadForInboxTests(t, dbPath, "leader", "worker-b", "worker-b") stdout, _, exitCode := executeInboxCommand( "--db", dbPath, "--json", "fail", "--agent", "worker-x", "--thread", threadID, "--summary", "Migration failed", ) if exitCode != 20 { t.Fatalf("expected exit code 20, got %d with output %s", exitCode, stdout) } assertErrorJSON(t, stdout, "lease_conflict") } func TestFailRejectsOnTerminalThread(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") threadID := seedClaimedThreadForInboxTests(t, dbPath, "leader", "worker-b", "worker-b") runInboxCommand( t, "--db", dbPath, "--json", "fail", "--agent", "worker-b", "--thread", threadID, "--summary", "Migration failed", ) stdout, _, exitCode := executeInboxCommand( "--db", dbPath, "--json", "fail", "--agent", "worker-b", "--thread", threadID, "--summary", "Migration failed", ) if exitCode != 30 { t.Fatalf("expected exit code 30, got %d with output %s", exitCode, stdout) } assertErrorJSON(t, stdout, "invalid_state") }