Files
kandev/apps/backend/internal/launcher/start_test.go
T
2026-08-07 23:32:04 +01:00

195 lines
5.7 KiB
Go

package launcher
import (
"context"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
)
func TestResolveLogThresholdsByMode(t *testing.T) {
t.Setenv("KANDEV_LOG_LEVEL", "")
tests := []struct {
name string
options Options
wantFile string
wantConsole string
}{
{name: "normal", wantFile: "info", wantConsole: "warn"},
{name: "debug", options: Options{Debug: true}, wantFile: "debug", wantConsole: "warn"},
{name: "verbose", options: Options{Verbose: true}, wantFile: "info", wantConsole: "info"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := resolveLogLevel(test.options); got != test.wantFile {
t.Fatalf("file level = %q, want %q", got, test.wantFile)
}
if got := resolveConsoleLogLevel(test.options); got != test.wantConsole {
t.Fatalf("console level = %q, want %q", got, test.wantConsole)
}
})
}
t.Setenv("KANDEV_LOG_LEVEL", "error")
if got := resolveLogLevel(Options{Debug: true}); got != "error" {
t.Fatalf("explicit file override = %q", got)
}
if os.Getenv("KANDEV_LOG_LEVEL") != "error" {
t.Fatal("test environment changed unexpectedly")
}
}
func TestBackendEnvCarriesIndependentThresholds(t *testing.T) {
env := backendEnv(portConfig{BackendPort: 1234, AgentctlPort: 5678}, "debug", "warn", true, "health-token")
joined := strings.Join(env, "\n")
for _, expected := range []string{
"KANDEV_LOG_LEVEL=debug",
"KANDEV_CONSOLE_LOG_LEVEL=warn",
"KANDEV_DEBUG_AGENT_MESSAGES=true",
"KANDEV_DESKTOP_HEALTH_TOKEN=health-token",
} {
if !strings.Contains(joined, expected) {
t.Fatalf("backend environment missing %q", expected)
}
}
}
func TestNewHealthTokenIsFreshAndOpaque(t *testing.T) {
first, err := newHealthToken()
if err != nil {
t.Fatal(err)
}
second, err := newHealthToken()
if err != nil {
t.Fatal(err)
}
if len(first) != healthTokenBytes*2 || len(second) != healthTokenBytes*2 {
t.Fatalf("health token lengths = %d and %d, want %d", len(first), len(second), healthTokenBytes*2)
}
if first == second {
t.Fatal("two health token generations returned the same value")
}
}
func TestRunStartUsesSelfExecutableAndBackendCWD(t *testing.T) {
oldExecutablePath := executablePath
oldLaunchManaged := launchManaged
t.Cleanup(func() {
executablePath = oldExecutablePath
launchManaged = oldLaunchManaged
})
exe := filepath.Join(canonicalTempDir(t), "bin", "kandev")
executablePath = func() (string, error) {
return exe, nil
}
var got managedAppConfig
launchManaged = func(_ context.Context, cfg managedAppConfig) int {
got = cfg
return 42
}
// ensureDataDir rejects a data dir with any symlinked ancestor, so the home
// dir must be canonical (macOS temp dirs sit under the /var -> /private/var link).
t.Setenv("KANDEV_HOME_DIR", canonicalTempDir(t))
code := runStart(context.Background(), Options{Command: CommandStart, BackendPort: 48123, Headless: true})
if code != 42 {
t.Fatalf("runStart() = %d, want 42", code)
}
if got.Backend != exe {
t.Fatalf("Backend = %q, want %q", got.Backend, exe)
}
if got.BackendCWD != filepath.Dir(exe) {
t.Fatalf("BackendCWD = %q, want %q", got.BackendCWD, filepath.Dir(exe))
}
if got.Mode != "start" {
t.Fatalf("Mode = %q, want start", got.Mode)
}
if got.Ports.BackendPort != 48123 {
t.Fatalf("BackendPort = %d, want 48123", got.Ports.BackendPort)
}
if !got.Opts.Headless {
t.Fatal("expected Headless option to be preserved")
}
}
func TestRunManagedAppAttachesSignalsBeforeBackendLaunch(t *testing.T) {
oldNewSupervisor := newSupervisorFn
oldLaunchBackend := launchBackendFn
oldAttachSignals := attachSignalsFn
oldWaitForHealth := waitForHealthFn
t.Cleanup(func() {
newSupervisorFn = oldNewSupervisor
launchBackendFn = oldLaunchBackend
attachSignalsFn = oldAttachSignals
waitForHealthFn = oldWaitForHealth
})
var events []string
var launchedQuiet bool
var launchedHealthToken string
var waitedHealthToken string
newSupervisorFn = func() *processSupervisor {
events = append(events, "new-supervisor")
return newSupervisor()
}
launchBackendFn = func(
_ string,
_ []string,
_ string,
env []string,
quiet bool,
_ portConfig,
_ string,
_ *processSupervisor,
) (*restartableBackend, func(), error) {
launchedQuiet = quiet
for _, item := range env {
if strings.HasPrefix(item, "KANDEV_DESKTOP_HEALTH_TOKEN=") {
launchedHealthToken = strings.TrimPrefix(item, "KANDEV_DESKTOP_HEALTH_TOKEN=")
}
}
events = append(events, "launch-backend")
exitCh := make(chan int, 1)
exitCh <- 0
return &restartableBackend{exitCh: exitCh}, func() {}, nil
}
attachSignalsFn = func(_ *processSupervisor) {
events = append(events, "attach-signals")
}
waitForHealthFn = func(_ context.Context, _ string, _ childState, _ time.Duration, expectedToken string, _ func()) error {
waitedHealthToken = expectedToken
events = append(events, "wait-health")
return nil
}
t.Setenv("KANDEV_HOME_DIR", t.TempDir())
code := runManagedApp(context.Background(), managedAppConfig{
Header: "test",
Mode: "start",
Backend: "kandev",
BackendCWD: t.TempDir(),
Ports: portConfig{
BackendPort: 48123,
BackendURL: "http://localhost:48123",
},
Opts: Options{Headless: true},
})
if code != 0 {
t.Fatalf("runManagedApp() = %d, want 0", code)
}
want := []string{"new-supervisor", "attach-signals", "launch-backend", "wait-health"}
if !reflect.DeepEqual(events, want) {
t.Fatalf("events = %v, want %v", events, want)
}
if launchedQuiet {
t.Fatal("backend stdout was not forwarded")
}
if launchedHealthToken == "" || launchedHealthToken != waitedHealthToken {
t.Fatalf("health token launched=%q waited=%q, want one shared non-empty token", launchedHealthToken, waitedHealthToken)
}
}