first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+145
View File
@@ -0,0 +1,145 @@
package tui
import (
"strings"
"testing"
"github.com/smallnest/pigo/internal/agentcore"
)
// newRemoteTestSession builds a fresh run session over a temp-dir store for the
// remote-control lifecycle tests (no resume, no tools).
func newRemoteTestSession(t *testing.T) *runSession {
t.Helper()
store := newTestStore(t)
s, _, err := newRunSessionWithStore(store, Options{})
if err != nil {
t.Fatalf("newRunSessionWithStore: %v", err)
}
return s
}
// TestStartStopRemote covers the start→already-running→stop lifecycle on
// runSession: startRemote binds a listener and stores the session, a second
// start reports "already running" without replacing it, and stopRemote clears
// the session so a later start can rebind.
func TestStartStopRemote(t *testing.T) {
s := newRemoteTestSession(t)
if s.remote != nil {
t.Fatal("remote should be nil before start")
}
url, err := s.startRemote()
if err != nil {
t.Fatalf("startRemote: %v", err)
}
if url == "" {
t.Fatal("startRemote returned empty url")
}
if s.remote == nil {
t.Fatal("remote should be set after start")
}
first := s.remote
// A second start is a no-op that reports the existing url and an error,
// leaving the running session untouched.
url2, err := s.startRemote()
if err == nil {
t.Error("second startRemote should report already-running error")
}
if url2 != url {
t.Errorf("second startRemote url = %q, want %q", url2, url)
}
if s.remote != first {
t.Error("second startRemote must not replace the running session")
}
s.stopRemote()
if s.remote != nil {
t.Error("remote should be nil after stop")
}
// Stop again is a no-op.
s.stopRemote()
// After stopping, a fresh start rebinds cleanly.
if _, err := s.startRemote(); err != nil {
t.Fatalf("restart after stop: %v", err)
}
s.stopRemote()
}
// TestBuildConfigInstallsRemoteSeam asserts buildConfig only installs the
// BeforeToolCall confirm seam while a remote session is present: off by default
// (up-front trust unchanged), wired once /remote-control is running.
func TestBuildConfigInstallsRemoteSeam(t *testing.T) {
s := newRemoteTestSession(t)
if cfg := s.buildConfig(); cfg.Batch.ToolExecutorConfig.BeforeToolCall != nil {
t.Error("BeforeToolCall should be nil when remote control is off")
}
if _, err := s.startRemote(); err != nil {
t.Fatalf("startRemote: %v", err)
}
defer s.stopRemote()
if cfg := s.buildConfig(); cfg.Batch.ToolExecutorConfig.BeforeToolCall == nil {
t.Error("BeforeToolCall should be installed when remote control is on")
}
}
// TestRemoteConfirmSeamAllowsWhenNoClient verifies the confirm seam is a no-op
// (returns nil = allow under up-front trust) when no browser is connected, so a
// running-but-unpaired server never blocks tool calls.
func TestRemoteConfirmSeamAllowsWhenNoClient(t *testing.T) {
s := newRemoteTestSession(t)
if _, err := s.startRemote(); err != nil {
t.Fatalf("startRemote: %v", err)
}
defer s.stopRemote()
// No client is paired, so hasClient() is false and the seam must allow.
seam := remoteConfirmSeam(s.remote, nil, "/tmp/project")
if d := seam(t.Context(), agentcore.AgentToolCall{Name: "bash"}); d != nil {
t.Errorf("seam should allow (nil) with no client, got %+v", d)
}
}
// TestRemoteInputIgnoredWhileRunning routes a remoteInputMsg into an idle vs a
// running Model: while a run is in flight the prompt is not started (a busy note
// is shown instead), and the listener is always re-issued so later submissions
// keep arriving.
func TestRemoteInputIgnoredWhileRunning(t *testing.T) {
s := newRemoteTestSession(t)
if _, err := s.startRemote(); err != nil {
t.Fatalf("startRemote: %v", err)
}
defer s.stopRemote()
m := NewModel(Options{})
m.session = s
m.running = true
updated, _ := m.Update(remoteInputMsg{text: "do something"})
got := updated.(Model)
if !hasSystemBlockContaining(got.transcript, "a run is in progress") {
t.Errorf("expected a busy note in the transcript while running, blocks=%v",
blockTexts(got.transcript))
}
// A run in progress must not consume the remote prompt as a new turn.
if !got.running {
t.Error("model should still be running; the remote prompt must not start a turn")
}
}
// hasSystemBlockContaining reports whether any transcript block's text contains
// sub (case-insensitive substring over the rendered block texts).
func hasSystemBlockContaining(t transcript, sub string) bool {
for _, s := range blockTexts(t) {
if strings.Contains(strings.ToLower(s), strings.ToLower(sub)) {
return true
}
}
return false
}