Files
gastown/internal/config/env_test.go
scout/crew/riker 169cc76e74
Some checks failed
CI / Check for .beads changes (push) Has been skipped
CI / Check embedded formulas (push) Successful in 26s
CI / Test (push) Failing after 1m39s
CI / Lint (push) Successful in 26s
CI / Integration Tests (push) Successful in 1m25s
CI / Coverage Report (push) Has been skipped
Windows CI / Windows Build and Unit Tests (push) Has been cancelled
fix(dog): set BD_ACTOR environment variable for dog sessions
Dogs spawned by gt dog dispatch couldn't find their mail because BD_ACTOR
wasn't set in their tmux environment. The AgentEnv function had no case for
the "dog" role.

Add "dog" case to AgentEnv that sets:
- BD_ACTOR=deacon/dogs/<name>
- GIT_AUTHOR_NAME=dog-<name>
- GIT_AUTHOR_EMAIL=dog-<name>@gastown.local

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 20:55:32 -08:00

351 lines
8.7 KiB
Go

package config
import (
"testing"
)
func TestAgentEnv_Mayor(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "mayor",
TownRoot: "/town",
})
assertEnv(t, env, "GT_ROLE", "mayor")
assertEnv(t, env, "BD_ACTOR", "mayor")
assertEnv(t, env, "GIT_AUTHOR_NAME", "mayor")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "mayor@gastown.local")
assertEnv(t, env, "GT_ROOT", "/town")
assertNotSet(t, env, "GT_RIG")
assertNotSet(t, env, "BEADS_NO_DAEMON")
}
func TestAgentEnv_Witness(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "witness",
Rig: "myrig",
TownRoot: "/town",
})
assertEnv(t, env, "GT_ROLE", "witness")
assertEnv(t, env, "GT_RIG", "myrig")
assertEnv(t, env, "BD_ACTOR", "myrig/witness")
assertEnv(t, env, "GIT_AUTHOR_NAME", "myrig/witness")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-witness@gastown.local")
assertEnv(t, env, "GT_ROOT", "/town")
}
func TestAgentEnv_Polecat(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "polecat",
Rig: "myrig",
AgentName: "Toast",
TownRoot: "/town",
BeadsNoDaemon: true,
})
assertEnv(t, env, "GT_ROLE", "polecat")
assertEnv(t, env, "GT_RIG", "myrig")
assertEnv(t, env, "GT_POLECAT", "Toast")
assertEnv(t, env, "BD_ACTOR", "myrig/polecats/Toast")
assertEnv(t, env, "GIT_AUTHOR_NAME", "Toast")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-polecat-Toast@gastown.local")
assertEnv(t, env, "BEADS_AGENT_NAME", "myrig/Toast")
assertEnv(t, env, "BEADS_NO_DAEMON", "1")
}
func TestAgentEnv_Crew(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "crew",
Rig: "myrig",
AgentName: "emma",
TownRoot: "/town",
BeadsNoDaemon: true,
})
assertEnv(t, env, "GT_ROLE", "crew")
assertEnv(t, env, "GT_RIG", "myrig")
assertEnv(t, env, "GT_CREW", "emma")
assertEnv(t, env, "BD_ACTOR", "myrig/crew/emma")
assertEnv(t, env, "GIT_AUTHOR_NAME", "emma")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-crew-emma@gastown.local")
assertEnv(t, env, "BEADS_AGENT_NAME", "myrig/emma")
assertEnv(t, env, "BEADS_NO_DAEMON", "1")
}
func TestAgentEnv_Refinery(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "refinery",
Rig: "myrig",
TownRoot: "/town",
BeadsNoDaemon: true,
})
assertEnv(t, env, "GT_ROLE", "refinery")
assertEnv(t, env, "GT_RIG", "myrig")
assertEnv(t, env, "BD_ACTOR", "myrig/refinery")
assertEnv(t, env, "GIT_AUTHOR_NAME", "myrig/refinery")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-refinery@gastown.local")
assertEnv(t, env, "BEADS_NO_DAEMON", "1")
}
func TestAgentEnv_Deacon(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "deacon",
TownRoot: "/town",
})
assertEnv(t, env, "GT_ROLE", "deacon")
assertEnv(t, env, "BD_ACTOR", "deacon")
assertEnv(t, env, "GIT_AUTHOR_NAME", "deacon")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "deacon@gastown.local")
assertEnv(t, env, "GT_ROOT", "/town")
assertNotSet(t, env, "GT_RIG")
assertNotSet(t, env, "BEADS_NO_DAEMON")
}
func TestAgentEnv_Boot(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "boot",
TownRoot: "/town",
})
assertEnv(t, env, "GT_ROLE", "boot")
assertEnv(t, env, "BD_ACTOR", "deacon-boot")
assertEnv(t, env, "GIT_AUTHOR_NAME", "boot")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "boot@gastown.local")
assertEnv(t, env, "GT_ROOT", "/town")
assertNotSet(t, env, "GT_RIG")
assertNotSet(t, env, "BEADS_NO_DAEMON")
}
func TestAgentEnv_Dog(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "dog",
AgentName: "alpha",
TownRoot: "/town",
})
assertEnv(t, env, "GT_ROLE", "dog")
assertEnv(t, env, "BD_ACTOR", "deacon/dogs/alpha")
assertEnv(t, env, "GIT_AUTHOR_NAME", "dog-alpha")
assertEnv(t, env, "GIT_AUTHOR_EMAIL", "dog-alpha@gastown.local")
assertEnv(t, env, "GT_ROOT", "/town")
assertNotSet(t, env, "GT_RIG")
assertNotSet(t, env, "BEADS_NO_DAEMON")
}
func TestAgentEnv_WithRuntimeConfigDir(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "polecat",
Rig: "myrig",
AgentName: "Toast",
TownRoot: "/town",
RuntimeConfigDir: "/home/user/.config/claude",
})
assertEnv(t, env, "CLAUDE_CONFIG_DIR", "/home/user/.config/claude")
}
func TestAgentEnv_WithoutRuntimeConfigDir(t *testing.T) {
t.Parallel()
env := AgentEnv(AgentEnvConfig{
Role: "polecat",
Rig: "myrig",
AgentName: "Toast",
TownRoot: "/town",
})
assertNotSet(t, env, "CLAUDE_CONFIG_DIR")
}
func TestAgentEnvSimple(t *testing.T) {
t.Parallel()
env := AgentEnvSimple("polecat", "myrig", "Toast")
assertEnv(t, env, "GT_ROLE", "polecat")
assertEnv(t, env, "GT_RIG", "myrig")
assertEnv(t, env, "GT_POLECAT", "Toast")
// Simple doesn't set TownRoot, so key should be absent
// (not empty string which would override tmux session environment)
assertNotSet(t, env, "GT_ROOT")
}
func TestAgentEnv_EmptyTownRootOmitted(t *testing.T) {
t.Parallel()
// Regression test: empty TownRoot should NOT create keys in the map.
// If it was set to empty string, ExportPrefix would generate "export GT_ROOT= ..."
// which overrides tmux session environment where it's correctly set.
env := AgentEnv(AgentEnvConfig{
Role: "polecat",
Rig: "myrig",
AgentName: "Toast",
TownRoot: "", // explicitly empty
})
// Key should be absent, not empty string
assertNotSet(t, env, "GT_ROOT")
// Other keys should still be set
assertEnv(t, env, "GT_ROLE", "polecat")
assertEnv(t, env, "GT_RIG", "myrig")
}
func TestExportPrefix(t *testing.T) {
t.Parallel()
tests := []struct {
name string
env map[string]string
expected string
}{
{
name: "empty",
env: map[string]string{},
expected: "",
},
{
name: "single var",
env: map[string]string{"FOO": "bar"},
expected: "export FOO=bar && ",
},
{
name: "multiple vars sorted",
env: map[string]string{
"ZZZ": "last",
"AAA": "first",
"MMM": "middle",
},
expected: "export AAA=first MMM=middle ZZZ=last && ",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ExportPrefix(tt.env)
if result != tt.expected {
t.Errorf("ExportPrefix() = %q, want %q", result, tt.expected)
}
})
}
}
func TestBuildStartupCommandWithEnv(t *testing.T) {
t.Parallel()
tests := []struct {
name string
env map[string]string
agentCmd string
prompt string
expected string
}{
{
name: "no env no prompt",
env: map[string]string{},
agentCmd: "claude",
prompt: "",
expected: "claude",
},
{
name: "env no prompt",
env: map[string]string{"GT_ROLE": "polecat"},
agentCmd: "claude",
prompt: "",
expected: "export GT_ROLE=polecat && claude",
},
{
name: "env with prompt",
env: map[string]string{"GT_ROLE": "polecat"},
agentCmd: "claude",
prompt: "gt prime",
expected: `export GT_ROLE=polecat && claude "gt prime"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := BuildStartupCommandWithEnv(tt.env, tt.agentCmd, tt.prompt)
if result != tt.expected {
t.Errorf("BuildStartupCommandWithEnv() = %q, want %q", result, tt.expected)
}
})
}
}
func TestMergeEnv(t *testing.T) {
t.Parallel()
a := map[string]string{"A": "1", "B": "2"}
b := map[string]string{"B": "override", "C": "3"}
result := MergeEnv(a, b)
assertEnv(t, result, "A", "1")
assertEnv(t, result, "B", "override")
assertEnv(t, result, "C", "3")
}
func TestFilterEnv(t *testing.T) {
t.Parallel()
env := map[string]string{"A": "1", "B": "2", "C": "3"}
result := FilterEnv(env, "A", "C")
assertEnv(t, result, "A", "1")
assertNotSet(t, result, "B")
assertEnv(t, result, "C", "3")
}
func TestWithoutEnv(t *testing.T) {
t.Parallel()
env := map[string]string{"A": "1", "B": "2", "C": "3"}
result := WithoutEnv(env, "B")
assertEnv(t, result, "A", "1")
assertNotSet(t, result, "B")
assertEnv(t, result, "C", "3")
}
func TestEnvToSlice(t *testing.T) {
t.Parallel()
env := map[string]string{"A": "1", "B": "2"}
result := EnvToSlice(env)
if len(result) != 2 {
t.Errorf("EnvToSlice() returned %d items, want 2", len(result))
}
// Check both entries exist (order not guaranteed)
found := make(map[string]bool)
for _, s := range result {
found[s] = true
}
if !found["A=1"] || !found["B=2"] {
t.Errorf("EnvToSlice() = %v, want [A=1, B=2]", result)
}
}
// Helper functions
func assertEnv(t *testing.T, env map[string]string, key, expected string) {
t.Helper()
if got := env[key]; got != expected {
t.Errorf("env[%q] = %q, want %q", key, got, expected)
}
}
func assertNotSet(t *testing.T, env map[string]string, key string) {
t.Helper()
if _, ok := env[key]; ok {
t.Errorf("env[%q] should not be set, but is %q", key, env[key])
}
}