Finalize inbox artifacts and error protocol
This commit is contained in:
@@ -309,12 +309,12 @@ func TestInboxRenewWaitReplyAndCancel(t *testing.T) {
|
||||
type commandResult struct {
|
||||
stdout string
|
||||
stderr string
|
||||
err error
|
||||
exit int
|
||||
}
|
||||
|
||||
waitCh := make(chan commandResult, 1)
|
||||
go func() {
|
||||
stdout, stderr, err := executeInboxCommand(
|
||||
stdout, stderr, exitCode := executeInboxCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"wait-reply",
|
||||
@@ -322,7 +322,7 @@ func TestInboxRenewWaitReplyAndCancel(t *testing.T) {
|
||||
"--after-message", blockedMessageID,
|
||||
"--timeout-seconds", "2",
|
||||
)
|
||||
waitCh <- commandResult{stdout: stdout, stderr: stderr, err: err}
|
||||
waitCh <- commandResult{stdout: stdout, stderr: stderr, exit: exitCode}
|
||||
}()
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
@@ -346,8 +346,8 @@ func TestInboxRenewWaitReplyAndCancel(t *testing.T) {
|
||||
t.Fatal("wait-reply command did not return")
|
||||
}
|
||||
|
||||
if waitResult.err != nil {
|
||||
t.Fatalf("wait-reply failed: %v\nstderr:\n%s", waitResult.err, waitResult.stderr)
|
||||
if waitResult.exit != 0 {
|
||||
t.Fatalf("wait-reply failed with exit=%d\nstderr:\n%s\nstdout:\n%s", waitResult.exit, waitResult.stderr, waitResult.stdout)
|
||||
}
|
||||
|
||||
var waitResp map[string]any
|
||||
@@ -392,12 +392,12 @@ func TestInboxWatchListUnreadAndAppend(t *testing.T) {
|
||||
type commandResult struct {
|
||||
stdout string
|
||||
stderr string
|
||||
err error
|
||||
exit int
|
||||
}
|
||||
|
||||
watchCh := make(chan commandResult, 1)
|
||||
go func() {
|
||||
stdout, stderr, err := executeInboxCommand(
|
||||
stdout, stderr, exitCode := executeInboxCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"watch",
|
||||
@@ -405,7 +405,7 @@ func TestInboxWatchListUnreadAndAppend(t *testing.T) {
|
||||
"--status", "pending",
|
||||
"--timeout-seconds", "2",
|
||||
)
|
||||
watchCh <- commandResult{stdout: stdout, stderr: stderr, err: err}
|
||||
watchCh <- commandResult{stdout: stdout, stderr: stderr, exit: exitCode}
|
||||
}()
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
@@ -420,6 +420,9 @@ func TestInboxWatchListUnreadAndAppend(t *testing.T) {
|
||||
"--subject", "Build admin editor",
|
||||
"--summary", "Create the first editor screen",
|
||||
"--body-file", bodyPath,
|
||||
"--artifact", bodyPath,
|
||||
"--artifact-kind", "brief",
|
||||
"--artifact-metadata-json", `{"label":"task-brief"}`,
|
||||
"--run", "run_blog_004",
|
||||
"--task", "T4",
|
||||
)
|
||||
@@ -435,8 +438,8 @@ func TestInboxWatchListUnreadAndAppend(t *testing.T) {
|
||||
t.Fatal("watch command did not return")
|
||||
}
|
||||
|
||||
if watchResult.err != nil {
|
||||
t.Fatalf("watch failed: %v\nstderr:\n%s", watchResult.err, watchResult.stderr)
|
||||
if watchResult.exit != 0 {
|
||||
t.Fatalf("watch failed with exit=%d\nstderr:\n%s\nstdout:\n%s", watchResult.exit, watchResult.stderr, watchResult.stdout)
|
||||
}
|
||||
|
||||
var watchResp map[string]any
|
||||
@@ -514,29 +517,123 @@ func TestInboxWatchListUnreadAndAppend(t *testing.T) {
|
||||
if firstMessage["body"] != "Implement the initial admin post editor." {
|
||||
t.Fatalf("expected body-file content in first message, got %#v", firstMessage["body"])
|
||||
}
|
||||
artifacts, ok := firstMessage["artifacts"].([]any)
|
||||
if !ok || len(artifacts) != 1 {
|
||||
t.Fatalf("expected one artifact on first message, got %#v", firstMessage["artifacts"])
|
||||
}
|
||||
firstArtifact, ok := artifacts[0].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected artifact object, got %#v", artifacts[0])
|
||||
}
|
||||
if firstArtifact["path"] != bodyPath {
|
||||
t.Fatalf("expected artifact path %q, got %#v", bodyPath, firstArtifact["path"])
|
||||
}
|
||||
if firstArtifact["kind"] != "brief" {
|
||||
t.Fatalf("expected artifact kind brief, got %#v", firstArtifact["kind"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxJSONErrorsAndExitCodes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
|
||||
if _, _, exitCode := executeInboxCommand("--db", dbPath, "--json", "init"); exitCode != 0 {
|
||||
t.Fatalf("expected init exit code 0, got %d", exitCode)
|
||||
}
|
||||
|
||||
stdout, _, exitCode := executeInboxCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"fetch",
|
||||
"--agent", "worker-z",
|
||||
"--status", "pending",
|
||||
)
|
||||
if exitCode != 10 {
|
||||
t.Fatalf("expected fetch no-match exit code 10, got %d", exitCode)
|
||||
}
|
||||
assertErrorJSON(t, stdout, "no_matching_work")
|
||||
|
||||
stdout, _, exitCode = executeInboxCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"claim",
|
||||
"--agent", "worker-z",
|
||||
"--thread", "thr_missing",
|
||||
)
|
||||
if exitCode != 40 {
|
||||
t.Fatalf("expected claim missing-thread exit code 40, got %d", exitCode)
|
||||
}
|
||||
assertErrorJSON(t, stdout, "not_found")
|
||||
|
||||
sendOut := runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"send",
|
||||
"--from", "leader",
|
||||
"--to", "worker-z",
|
||||
"--subject", "Review cache settings",
|
||||
"--summary", "Check cache config",
|
||||
)
|
||||
|
||||
var sendResp map[string]any
|
||||
mustDecodeJSON(t, sendOut, &sendResp)
|
||||
threadID := nestedString(t, sendResp, "data", "thread", "thread_id")
|
||||
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"claim",
|
||||
"--agent", "worker-z",
|
||||
"--thread", threadID,
|
||||
)
|
||||
|
||||
stdout, _, exitCode = executeInboxCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"claim",
|
||||
"--agent", "worker-y",
|
||||
"--thread", threadID,
|
||||
)
|
||||
if exitCode != 20 {
|
||||
t.Fatalf("expected lease conflict exit code 20, got %d", exitCode)
|
||||
}
|
||||
assertErrorJSON(t, stdout, "lease_conflict")
|
||||
|
||||
stdout, _, exitCode = executeInboxCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"send",
|
||||
"--from", "leader",
|
||||
"--to", "worker-z",
|
||||
"--subject", "Invalid body flags",
|
||||
"--body", "inline",
|
||||
"--body-file", filepath.Join(t.TempDir(), "missing.md"),
|
||||
)
|
||||
if exitCode != 30 {
|
||||
t.Fatalf("expected invalid input exit code 30, got %d", exitCode)
|
||||
}
|
||||
assertErrorJSON(t, stdout, "invalid_input")
|
||||
}
|
||||
|
||||
func runInboxCommand(t *testing.T, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
stdout, stderr, err := executeInboxCommand(args...)
|
||||
if err != nil {
|
||||
t.Fatalf("execute inbox command %v: %v\nstderr:\n%s", args, err, stderr)
|
||||
stdout, stderr, exitCode := executeInboxCommand(args...)
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("execute inbox command %v: exit=%d\nstderr:\n%s\nstdout:\n%s", args, exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
return stdout
|
||||
}
|
||||
|
||||
func executeInboxCommand(args ...string) (string, string, error) {
|
||||
cmd := NewRootCmd()
|
||||
func executeInboxCommand(args ...string) (string, string, int) {
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs(args)
|
||||
|
||||
err := cmd.Execute()
|
||||
return stdout.String(), stderr.String(), err
|
||||
exitCode := Execute(args, &stdout, &stderr)
|
||||
return stdout.String(), stderr.String(), exitCode
|
||||
}
|
||||
|
||||
func mustDecodeJSON(t *testing.T, raw string, target any) {
|
||||
@@ -574,3 +671,20 @@ func nestedValue(t *testing.T, value map[string]any, keys ...string) any {
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func assertErrorJSON(t *testing.T, raw string, expectedCode string) {
|
||||
t.Helper()
|
||||
|
||||
var payload map[string]any
|
||||
mustDecodeJSON(t, raw, &payload)
|
||||
if ok, _ := payload["ok"].(bool); ok {
|
||||
t.Fatalf("expected ok=false error payload, got %#v", payload)
|
||||
}
|
||||
errorValue, ok := payload["error"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected error object, got %#v", payload["error"])
|
||||
}
|
||||
if code, _ := errorValue["code"].(string); code != expectedCode {
|
||||
t.Fatalf("expected error code %q, got %#v", expectedCode, errorValue["code"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user