package app import ( "strings" "testing" ) // mkChild builds a Child without starting a PTY. Use sparingly — the // resulting child has no emulator / ring buffer / status pointer set // the way newChild would. func mkChild(id, name, parent string) *Child { return &Child{ID: id, Name: name, ParentID: parent} } func TestClassifySendMessageOrchestratorToChild(t *testing.T) { parent := mkChild("p_aaa", "claude-1", "") child := mkChild("p_bbb", "codex-1", "p_aaa") line, err := classifySendMessage(parent, child, parent.ID, "hi child") if err != nil { t.Fatalf("expected success, got %v", err) } if !strings.HasPrefix(line, "[orchestrator] hi child") { t.Fatalf("parent→child should tag [orchestrator], got %q", line) } } func TestClassifySendMessageChildToParent(t *testing.T) { parent := mkChild("p_aaa", "claude-1", "") child := mkChild("p_bbb", "codex-1", "p_aaa") line, err := classifySendMessage(child, parent, child.ID, "status update") if err != nil { t.Fatalf("expected success, got %v", err) } if !strings.Contains(line, "[sub-agent:codex-1]") { t.Fatalf("child→parent should tag [sub-agent:], got %q", line) } } func TestClassifySendMessageSiblingRejected(t *testing.T) { parent := mkChild("p_aaa", "claude-1", "") sibA := mkChild("p_bbb", "codex-1", "p_aaa") sibB := mkChild("p_ccc", "codex-2", "p_aaa") _ = parent _, err := classifySendMessage(sibA, sibB, sibA.ID, "hey") if err == nil { t.Fatalf("sibling send_message should fail with not_related") } if !strings.Contains(err.Error(), "neither parent nor child") { t.Fatalf("error should mention sibling routing rule, got %v", err) } } func TestClassifySendMessageUnrelatedRejected(t *testing.T) { // Two unrelated top-level processes — they have no shared lineage. a := mkChild("p_aaa", "claude-1", "") b := mkChild("p_bbb", "codex-1", "") _, err := classifySendMessage(a, b, a.ID, "hi") if err == nil { t.Fatalf("unrelated top-level send_message should fail") } } func TestClassifySendMessageCannotSendToSelf(t *testing.T) { c := mkChild("p_aaa", "claude-1", "") _, err := classifySendMessage(c, c, c.ID, "talking to myself") if err == nil { t.Fatalf("self-send should fail") } } func TestClassifySendMessageNilCallerAcceptsTopLevel(t *testing.T) { // Caller arrived over an MCP connection without a resolved patterm // identity (top-level tool client). Target is top-level; should // land as [orchestrator]. target := mkChild("p_aaa", "claude-1", "") line, err := classifySendMessage(nil, target, "" /* unknown caller id */, "go") if err != nil { t.Fatalf("expected success for nil-caller → top-level target, got %v", err) } if !strings.HasPrefix(line, "[orchestrator] go") { t.Fatalf("expected [orchestrator] tag, got %q", line) } } func TestClassifySendMessageNilCallerRejectsNonTopLevelTarget(t *testing.T) { parent := mkChild("p_aaa", "claude-1", "") child := mkChild("p_bbb", "codex-1", "p_aaa") _ = parent _, err := classifySendMessage(nil, child, "", "hi") if err == nil { t.Fatalf("nil caller → non-top-level should fail") } }