package inbox import ( "path/filepath" "testing" "time" ) type watchCommandResult struct { stdout string stderr string exit int } func TestWatchWakesOnMatchingThread(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") runInboxCommand(t, "--db", dbPath, "--json", "init") watchCh := make(chan watchCommandResult, 1) go func() { stdout, stderr, exitCode := executeInboxCommand( "--db", dbPath, "--json", "watch", "--agent", "worker-d", "--status", "pending", "--timeout-seconds", "2", ) watchCh <- watchCommandResult{stdout: stdout, stderr: stderr, exit: exitCode} }() time.Sleep(200 * time.Millisecond) sendOut := runInboxCommand( t, "--db", dbPath, "--json", "send", "--from", "leader", "--to", "worker-d", "--subject", "Build admin editor", "--summary", "Create the first editor screen", ) var sendResp map[string]any mustDecodeJSON(t, sendOut, &sendResp) threadID := nestedString(t, sendResp, "data", "thread", "thread_id") var watchResult watchCommandResult select { case watchResult = <-watchCh: case <-time.After(3 * time.Second): t.Fatal("watch command did not return") } 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 mustDecodeJSON(t, watchResult.stdout, &watchResp) if woke, ok := nestedValue(t, watchResp, "data", "woke").(bool); !ok || !woke { t.Fatalf("expected watch to wake, got %#v", nestedValue(t, watchResp, "data", "woke")) } if got := nestedString(t, watchResp, "data", "thread", "thread_id"); got != threadID { t.Fatalf("expected woken thread %q, got %q", threadID, got) } nextEventID, ok := nestedValue(t, watchResp, "data", "next_event_id").(float64) if !ok { t.Fatalf("expected numeric next_event_id, got %#v", nestedValue(t, watchResp, "data", "next_event_id")) } eventID, ok := nestedValue(t, watchResp, "data", "event", "event_id").(float64) if !ok { t.Fatalf("expected numeric event_id, got %#v", nestedValue(t, watchResp, "data", "event", "event_id")) } if nextEventID != eventID { t.Fatalf("expected next_event_id == event.event_id, got %v vs %v", nextEventID, eventID) } } func TestWatchRespectsStatusFilter(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") runInboxCommand(t, "--db", dbPath, "--json", "init") sendOut := runInboxCommand( t, "--db", dbPath, "--json", "send", "--from", "leader", "--to", "worker-c", "--subject", "Investigate policy edge case", "--summary", "Initial request", ) var sendResp map[string]any mustDecodeJSON(t, sendOut, &sendResp) threadID := nestedString(t, sendResp, "data", "thread", "thread_id") watchCh := make(chan watchCommandResult, 1) go func() { stdout, stderr, exitCode := executeInboxCommand( "--db", dbPath, "--json", "watch", "--agent", "worker-c", "--status", "blocked", "--timeout-seconds", "2", ) watchCh <- watchCommandResult{stdout: stdout, stderr: stderr, exit: exitCode} }() time.Sleep(200 * time.Millisecond) runInboxCommand( t, "--db", dbPath, "--json", "claim", "--agent", "worker-c", "--thread", threadID, ) runInboxCommand( t, "--db", dbPath, "--json", "update", "--agent", "worker-c", "--thread", threadID, "--status", "blocked", "--summary", "Need policy decision", ) var watchResult watchCommandResult select { case watchResult = <-watchCh: case <-time.After(3 * time.Second): t.Fatal("watch command did not return") } 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 mustDecodeJSON(t, watchResult.stdout, &watchResp) if status := nestedString(t, watchResp, "data", "thread", "status"); status != "blocked" { t.Fatalf("expected blocked status wake, got %q", status) } } func TestWatchTimesOutWithNoActivity(t *testing.T) { t.Parallel() dbPath := filepath.Join(t.TempDir(), "coord.db") runInboxCommand(t, "--db", dbPath, "--json", "init") stdout, _, exitCode := executeInboxCommand( "--db", dbPath, "--json", "watch", "--agent", "worker-d", "--status", "pending", "--timeout-seconds", "1", ) if exitCode != 10 { t.Fatalf("expected watch timeout exit code 10, got %d with %s", exitCode, stdout) } assertErrorJSON(t, stdout, "no_matching_work") }