* test: Add test coverage for 16 files (40.3% -> 45.5%) Add comprehensive tests for previously untested packages: - internal/agent/state_test.go - internal/cmd/errors_test.go - internal/crew/types_test.go - internal/doctor/errors_test.go - internal/dog/types_test.go - internal/mail/bd_test.go - internal/opencode/plugin_test.go - internal/rig/overlay_test.go - internal/runtime/runtime_test.go - internal/session/town_test.go - internal/style/style_test.go - internal/ui/markdown_test.go - internal/ui/terminal_test.go - internal/wisp/io_test.go - internal/wisp/types_test.go - internal/witness/types_test.go style_test.go uses func(...string) to match lipgloss variadic Render signature. * fix(lint): remove unused error return from buildCVSummary buildCVSummary always returned nil for its error value, causing golangci-lint to fail with "result 1 (error) is always nil". The function handles errors internally by returning partial data, so the error return was misleading. Removed it and updated caller.
308 lines
7.6 KiB
Go
308 lines
7.6 KiB
Go
package runtime
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/steveyegge/gastown/internal/config"
|
|
)
|
|
|
|
func TestSessionIDFromEnv_Default(t *testing.T) {
|
|
// Clear all environment variables
|
|
oldGSEnv := os.Getenv("GT_SESSION_ID_ENV")
|
|
oldClaudeID := os.Getenv("CLAUDE_SESSION_ID")
|
|
defer func() {
|
|
if oldGSEnv != "" {
|
|
os.Setenv("GT_SESSION_ID_ENV", oldGSEnv)
|
|
} else {
|
|
os.Unsetenv("GT_SESSION_ID_ENV")
|
|
}
|
|
if oldClaudeID != "" {
|
|
os.Setenv("CLAUDE_SESSION_ID", oldClaudeID)
|
|
} else {
|
|
os.Unsetenv("CLAUDE_SESSION_ID")
|
|
}
|
|
}()
|
|
os.Unsetenv("GT_SESSION_ID_ENV")
|
|
os.Unsetenv("CLAUDE_SESSION_ID")
|
|
|
|
result := SessionIDFromEnv()
|
|
if result != "" {
|
|
t.Errorf("SessionIDFromEnv() with no env vars should return empty, got %q", result)
|
|
}
|
|
}
|
|
|
|
func TestSessionIDFromEnv_ClaudeSessionID(t *testing.T) {
|
|
oldGSEnv := os.Getenv("GT_SESSION_ID_ENV")
|
|
oldClaudeID := os.Getenv("CLAUDE_SESSION_ID")
|
|
defer func() {
|
|
if oldGSEnv != "" {
|
|
os.Setenv("GT_SESSION_ID_ENV", oldGSEnv)
|
|
} else {
|
|
os.Unsetenv("GT_SESSION_ID_ENV")
|
|
}
|
|
if oldClaudeID != "" {
|
|
os.Setenv("CLAUDE_SESSION_ID", oldClaudeID)
|
|
} else {
|
|
os.Unsetenv("CLAUDE_SESSION_ID")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_SESSION_ID_ENV")
|
|
os.Setenv("CLAUDE_SESSION_ID", "test-session-123")
|
|
|
|
result := SessionIDFromEnv()
|
|
if result != "test-session-123" {
|
|
t.Errorf("SessionIDFromEnv() = %q, want %q", result, "test-session-123")
|
|
}
|
|
}
|
|
|
|
func TestSessionIDFromEnv_CustomEnvVar(t *testing.T) {
|
|
oldGSEnv := os.Getenv("GT_SESSION_ID_ENV")
|
|
oldCustomID := os.Getenv("CUSTOM_SESSION_ID")
|
|
oldClaudeID := os.Getenv("CLAUDE_SESSION_ID")
|
|
defer func() {
|
|
if oldGSEnv != "" {
|
|
os.Setenv("GT_SESSION_ID_ENV", oldGSEnv)
|
|
} else {
|
|
os.Unsetenv("GT_SESSION_ID_ENV")
|
|
}
|
|
if oldCustomID != "" {
|
|
os.Setenv("CUSTOM_SESSION_ID", oldCustomID)
|
|
} else {
|
|
os.Unsetenv("CUSTOM_SESSION_ID")
|
|
}
|
|
if oldClaudeID != "" {
|
|
os.Setenv("CLAUDE_SESSION_ID", oldClaudeID)
|
|
} else {
|
|
os.Unsetenv("CLAUDE_SESSION_ID")
|
|
}
|
|
}()
|
|
|
|
os.Setenv("GT_SESSION_ID_ENV", "CUSTOM_SESSION_ID")
|
|
os.Setenv("CUSTOM_SESSION_ID", "custom-session-456")
|
|
os.Setenv("CLAUDE_SESSION_ID", "claude-session-789")
|
|
|
|
result := SessionIDFromEnv()
|
|
if result != "custom-session-456" {
|
|
t.Errorf("SessionIDFromEnv() with custom env = %q, want %q", result, "custom-session-456")
|
|
}
|
|
}
|
|
|
|
func TestSleepForReadyDelay_NilConfig(t *testing.T) {
|
|
// Should not panic with nil config
|
|
SleepForReadyDelay(nil)
|
|
}
|
|
|
|
func TestSleepForReadyDelay_ZeroDelay(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Tmux: &config.RuntimeTmuxConfig{
|
|
ReadyDelayMs: 0,
|
|
},
|
|
}
|
|
|
|
start := time.Now()
|
|
SleepForReadyDelay(rc)
|
|
elapsed := time.Since(start)
|
|
|
|
// Should return immediately
|
|
if elapsed > 100*time.Millisecond {
|
|
t.Errorf("SleepForReadyDelay() with zero delay took too long: %v", elapsed)
|
|
}
|
|
}
|
|
|
|
func TestSleepForReadyDelay_WithDelay(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Tmux: &config.RuntimeTmuxConfig{
|
|
ReadyDelayMs: 10, // 10ms delay
|
|
},
|
|
}
|
|
|
|
start := time.Now()
|
|
SleepForReadyDelay(rc)
|
|
elapsed := time.Since(start)
|
|
|
|
// Should sleep for at least 10ms
|
|
if elapsed < 10*time.Millisecond {
|
|
t.Errorf("SleepForReadyDelay() should sleep for at least 10ms, took %v", elapsed)
|
|
}
|
|
// But not too long
|
|
if elapsed > 50*time.Millisecond {
|
|
t.Errorf("SleepForReadyDelay() slept too long: %v", elapsed)
|
|
}
|
|
}
|
|
|
|
func TestSleepForReadyDelay_NilTmuxConfig(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Tmux: nil,
|
|
}
|
|
|
|
start := time.Now()
|
|
SleepForReadyDelay(rc)
|
|
elapsed := time.Since(start)
|
|
|
|
// Should return immediately
|
|
if elapsed > 100*time.Millisecond {
|
|
t.Errorf("SleepForReadyDelay() with nil Tmux config took too long: %v", elapsed)
|
|
}
|
|
}
|
|
|
|
func TestStartupFallbackCommands_NoHooks(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: &config.RuntimeHooksConfig{
|
|
Provider: "none",
|
|
},
|
|
}
|
|
|
|
commands := StartupFallbackCommands("polecat", rc)
|
|
if commands == nil {
|
|
t.Error("StartupFallbackCommands() with no hooks should return commands")
|
|
}
|
|
if len(commands) == 0 {
|
|
t.Error("StartupFallbackCommands() should return at least one command")
|
|
}
|
|
}
|
|
|
|
func TestStartupFallbackCommands_WithHooks(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: &config.RuntimeHooksConfig{
|
|
Provider: "claude",
|
|
},
|
|
}
|
|
|
|
commands := StartupFallbackCommands("polecat", rc)
|
|
if commands != nil {
|
|
t.Error("StartupFallbackCommands() with hooks provider should return nil")
|
|
}
|
|
}
|
|
|
|
func TestStartupFallbackCommands_NilConfig(t *testing.T) {
|
|
// Nil config defaults to claude provider, which has hooks
|
|
// So it returns nil (no fallback commands needed)
|
|
commands := StartupFallbackCommands("polecat", nil)
|
|
if commands != nil {
|
|
t.Error("StartupFallbackCommands() with nil config should return nil (defaults to claude with hooks)")
|
|
}
|
|
}
|
|
|
|
func TestStartupFallbackCommands_AutonomousRole(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: &config.RuntimeHooksConfig{
|
|
Provider: "none",
|
|
},
|
|
}
|
|
|
|
autonomousRoles := []string{"polecat", "witness", "refinery", "deacon"}
|
|
for _, role := range autonomousRoles {
|
|
t.Run(role, func(t *testing.T) {
|
|
commands := StartupFallbackCommands(role, rc)
|
|
if commands == nil || len(commands) == 0 {
|
|
t.Error("StartupFallbackCommands() should return commands for autonomous role")
|
|
}
|
|
// Should contain mail check
|
|
found := false
|
|
for _, cmd := range commands {
|
|
if contains(cmd, "mail check --inject") {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Commands for %s should contain mail check --inject", role)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStartupFallbackCommands_NonAutonomousRole(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: &config.RuntimeHooksConfig{
|
|
Provider: "none",
|
|
},
|
|
}
|
|
|
|
nonAutonomousRoles := []string{"mayor", "crew", "keeper"}
|
|
for _, role := range nonAutonomousRoles {
|
|
t.Run(role, func(t *testing.T) {
|
|
commands := StartupFallbackCommands(role, rc)
|
|
if commands == nil || len(commands) == 0 {
|
|
t.Error("StartupFallbackCommands() should return commands for non-autonomous role")
|
|
}
|
|
// Should NOT contain mail check
|
|
for _, cmd := range commands {
|
|
if contains(cmd, "mail check --inject") {
|
|
t.Errorf("Commands for %s should NOT contain mail check --inject", role)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStartupFallbackCommands_RoleCasing(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: &config.RuntimeHooksConfig{
|
|
Provider: "none",
|
|
},
|
|
}
|
|
|
|
// Role should be lowercased internally
|
|
commands := StartupFallbackCommands("POLECAT", rc)
|
|
if commands == nil {
|
|
t.Error("StartupFallbackCommands() should handle uppercase role")
|
|
}
|
|
}
|
|
|
|
func TestEnsureSettingsForRole_NilConfig(t *testing.T) {
|
|
// Should not panic with nil config
|
|
err := EnsureSettingsForRole("/tmp/test", "polecat", nil)
|
|
if err != nil {
|
|
t.Errorf("EnsureSettingsForRole() with nil config should not error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureSettingsForRole_NilHooks(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: nil,
|
|
}
|
|
|
|
err := EnsureSettingsForRole("/tmp/test", "polecat", rc)
|
|
if err != nil {
|
|
t.Errorf("EnsureSettingsForRole() with nil hooks should not error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureSettingsForRole_UnknownProvider(t *testing.T) {
|
|
rc := &config.RuntimeConfig{
|
|
Hooks: &config.RuntimeHooksConfig{
|
|
Provider: "unknown",
|
|
},
|
|
}
|
|
|
|
err := EnsureSettingsForRole("/tmp/test", "polecat", rc)
|
|
if err != nil {
|
|
t.Errorf("EnsureSettingsForRole() with unknown provider should not error, got %v", err)
|
|
}
|
|
}
|
|
|
|
// Helper function
|
|
func contains(s, substr string) bool {
|
|
return len(s) >= len(substr) && findSubstring(s, substr)
|
|
}
|
|
|
|
func findSubstring(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
match := true
|
|
for j := 0; j < len(substr); j++ {
|
|
if s[i+j] != substr[j] {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|