Files
gastown/internal/runtime/runtime_test.go
Daniel Sauer fdd4b0aeb0 test: Add test coverage for 16 files (40.3% → 45.5%) (#463)
* 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.
2026-01-13 13:19:27 -08:00

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
}