Files
gastown/internal/polecat/session_manager_test.go
Johann Dirry 3d5a66f850 Fixing unit tests on windows (#813)
* Add Windows stub for orphan cleanup

* Fix account switch tests on Windows

* Make query session events test portable

* Disable beads daemon in query session events test

* Add Windows bd stubs for sling tests

* Make expandOutputPath test OS-agnostic

* Make role_agents test Windows-friendly

* Make config path tests OS-agnostic

* Make HealthCheckStateFile test OS-agnostic

* Skip orphan process check on Windows

* Normalize sparse checkout detail paths

* Make dog path tests OS-agnostic

* Fix bare repo refspec config on Windows

* Add Windows process detection for locks

* Add Windows CI workflow

* Make mail path tests OS-agnostic

* Skip plugin file mode test on Windows

* Skip tmux-dependent polecat tests on Windows

* Normalize polecat paths and AGENTS.md content

* Make beads init failure test Windows-friendly

* Skip rig agent bead init test on Windows

* Make XDG path tests OS-agnostic

* Make exec tests portable on Windows

* Adjust atomic write tests for Windows

* Make wisp tests Windows-friendly

* Make workspace find tests OS-agnostic

* Fix Windows rig add integration test

* Make sling var logging Windows-friendly

* Fix sling attached molecule update ordering

---------

Co-authored-by: Johann Dirry <johann.dirry@microsea.at>
2026-01-20 14:17:35 -08:00

216 lines
5.2 KiB
Go

package polecat
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/tmux"
)
func requireTmux(t *testing.T) {
t.Helper()
if runtime.GOOS == "windows" {
t.Skip("tmux not supported on Windows")
}
if _, err := exec.LookPath("tmux"); err != nil {
t.Skip("tmux not installed")
}
}
func TestSessionName(t *testing.T) {
r := &rig.Rig{
Name: "gastown",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
name := m.SessionName("Toast")
if name != "gt-gastown-Toast" {
t.Errorf("sessionName = %q, want gt-gastown-Toast", name)
}
}
func TestSessionManagerPolecatDir(t *testing.T) {
r := &rig.Rig{
Name: "gastown",
Path: "/home/user/ai/gastown",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
dir := m.polecatDir("Toast")
expected := "/home/user/ai/gastown/polecats/Toast"
if filepath.ToSlash(dir) != expected {
t.Errorf("polecatDir = %q, want %q", dir, expected)
}
}
func TestHasPolecat(t *testing.T) {
root := t.TempDir()
// hasPolecat checks filesystem, so create actual directories
for _, name := range []string{"Toast", "Cheedo"} {
if err := os.MkdirAll(filepath.Join(root, "polecats", name), 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
}
r := &rig.Rig{
Name: "gastown",
Path: root,
Polecats: []string{"Toast", "Cheedo"},
}
m := NewSessionManager(tmux.NewTmux(), r)
if !m.hasPolecat("Toast") {
t.Error("expected hasPolecat(Toast) = true")
}
if !m.hasPolecat("Cheedo") {
t.Error("expected hasPolecat(Cheedo) = true")
}
if m.hasPolecat("Unknown") {
t.Error("expected hasPolecat(Unknown) = false")
}
}
func TestStartPolecatNotFound(t *testing.T) {
r := &rig.Rig{
Name: "gastown",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
err := m.Start("Unknown", SessionStartOptions{})
if err == nil {
t.Error("expected error for unknown polecat")
}
}
func TestIsRunningNoSession(t *testing.T) {
requireTmux(t)
r := &rig.Rig{
Name: "gastown",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
running, err := m.IsRunning("Toast")
if err != nil {
t.Fatalf("IsRunning: %v", err)
}
if running {
t.Error("expected IsRunning = false for non-existent session")
}
}
func TestSessionManagerListEmpty(t *testing.T) {
requireTmux(t)
r := &rig.Rig{
Name: "test-rig-unlikely-name",
Polecats: []string{},
}
m := NewSessionManager(tmux.NewTmux(), r)
infos, err := m.List()
if err != nil {
t.Fatalf("List: %v", err)
}
if len(infos) != 0 {
t.Errorf("infos count = %d, want 0", len(infos))
}
}
func TestStopNotFound(t *testing.T) {
requireTmux(t)
r := &rig.Rig{
Name: "test-rig",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
err := m.Stop("Toast", false)
if err != ErrSessionNotFound {
t.Errorf("Stop = %v, want ErrSessionNotFound", err)
}
}
func TestCaptureNotFound(t *testing.T) {
requireTmux(t)
r := &rig.Rig{
Name: "test-rig",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
_, err := m.Capture("Toast", 50)
if err != ErrSessionNotFound {
t.Errorf("Capture = %v, want ErrSessionNotFound", err)
}
}
func TestInjectNotFound(t *testing.T) {
requireTmux(t)
r := &rig.Rig{
Name: "test-rig",
Polecats: []string{"Toast"},
}
m := NewSessionManager(tmux.NewTmux(), r)
err := m.Inject("Toast", "hello")
if err != ErrSessionNotFound {
t.Errorf("Inject = %v, want ErrSessionNotFound", err)
}
}
// TestPolecatCommandFormat verifies the polecat session command exports
// GT_ROLE, GT_RIG, GT_POLECAT, and BD_ACTOR inline before starting Claude.
// This is a regression test for gt-y41ep - env vars must be exported inline
// because tmux SetEnvironment only affects new panes, not the current shell.
func TestPolecatCommandFormat(t *testing.T) {
// This test verifies the expected command format.
// The actual command is built in Start() but we test the format here
// to document and verify the expected behavior.
rigName := "gastown"
polecatName := "Toast"
expectedBdActor := "gastown/polecats/Toast"
// Build the expected command format (mirrors Start() logic)
expectedPrefix := "export GT_ROLE=polecat GT_RIG=" + rigName + " GT_POLECAT=" + polecatName + " BD_ACTOR=" + expectedBdActor + " GIT_AUTHOR_NAME=" + expectedBdActor
expectedSuffix := "&& claude --dangerously-skip-permissions"
// The command must contain all required env exports
requiredParts := []string{
"export",
"GT_ROLE=polecat",
"GT_RIG=" + rigName,
"GT_POLECAT=" + polecatName,
"BD_ACTOR=" + expectedBdActor,
"GIT_AUTHOR_NAME=" + expectedBdActor,
"claude --dangerously-skip-permissions",
}
// Verify expected format contains all required parts
fullCommand := expectedPrefix + " " + expectedSuffix
for _, part := range requiredParts {
if !strings.Contains(fullCommand, part) {
t.Errorf("Polecat command should contain %q", part)
}
}
// Verify GT_ROLE is specifically "polecat" (not "mayor" or "crew")
if !strings.Contains(fullCommand, "GT_ROLE=polecat") {
t.Error("GT_ROLE must be 'polecat', not 'mayor' or 'crew'")
}
}