143 lines
4.2 KiB
Go
143 lines
4.2 KiB
Go
package inbox
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestReplyAddsAnswerMessage verifies reply appends an answer message to the thread.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestReplySupportsControlKind verifies reply accepts control messages in addition to answers.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestReplyAttachesArtifact verifies reply persists an attached artifact on the reply message.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestReplyRejectsInvalidPayloadJSON verifies reply returns invalid_input for malformed payload JSON.
|
|
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")
|
|
}
|