Files
kandev/apps/backend/internal/launcher/devpaths_test.go
T
2026-08-21 19:28:21 +01:00

220 lines
6.4 KiB
Go

package launcher
import (
"os"
"path/filepath"
"strings"
"testing"
commonconfig "github.com/kandev/kandev/internal/common/config"
)
func makeRepoTree(t *testing.T) string {
t.Helper()
root := t.TempDir()
for _, dir := range []string{"apps/backend", "apps/web"} {
if err := os.MkdirAll(filepath.Join(root, dir), 0o700); err != nil {
t.Fatal(err)
}
}
return root
}
func TestFindRepoRootFromNestedWorkingDirectory(t *testing.T) {
root := makeRepoTree(t)
got, err := findRepoRoot(filepath.Join(root, "apps", "backend", "internal", "launcher"))
if err != nil {
t.Fatal(err)
}
if got != root {
t.Fatalf("findRepoRoot = %q, want %q", got, root)
}
}
func TestFindRepoRootFromRepoRootItself(t *testing.T) {
root := makeRepoTree(t)
got, err := findRepoRoot(root)
if err != nil {
t.Fatal(err)
}
if got != root {
t.Fatalf("findRepoRoot = %q, want %q", got, root)
}
}
func TestFindRepoRootFromAppsDirectory(t *testing.T) {
root := makeRepoTree(t)
got, err := findRepoRoot(filepath.Join(root, "apps"))
if err != nil {
t.Fatal(err)
}
if got != root {
t.Fatalf("findRepoRoot = %q, want %q", got, root)
}
}
func TestFindRepoRootRequiresBothApps(t *testing.T) {
onlyBackend := t.TempDir()
if err := os.MkdirAll(filepath.Join(onlyBackend, "apps", "backend"), 0o700); err != nil {
t.Fatal(err)
}
if _, err := findRepoRoot(onlyBackend); err == nil {
t.Fatal("findRepoRoot accepted a tree with only apps/backend")
}
}
func TestFindRepoRootFailsOutsideRepository(t *testing.T) {
if _, err := findRepoRoot(t.TempDir()); err == nil {
t.Fatal("findRepoRoot accepted an empty tree")
}
}
func TestIsInsideKandevTaskSignals(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("KANDEV_TASK_ID", "")
repoInTasks := filepath.Join(home, ".kandev", "tasks", "ws", "repo")
if !isInsideKandevTask(repoInTasks) {
t.Fatalf("isInsideKandevTask(%q) = false, want true for a repo under ~/.kandev/tasks", repoInTasks)
}
if isInsideKandevTask(filepath.Join(home, "projects", "repo")) {
t.Fatal("isInsideKandevTask = true for a repo outside the tasks dir")
}
tasksDir := filepath.Join(home, ".kandev", "tasks")
if isInsideKandevTask(tasksDir) {
t.Fatal("isInsideKandevTask = true for the tasks dir itself (no child segment)")
}
t.Setenv("KANDEV_TASK_ID", "task-123")
if !isInsideKandevTask(filepath.Join(home, "projects", "repo")) {
t.Fatal("isInsideKandevTask = false with KANDEV_TASK_ID set")
}
}
func devEnvToMap(extra []string) map[string]string {
out := map[string]string{}
for _, item := range extra {
key, value, ok := strings.Cut(item, "=")
if ok {
out[key] = value
}
}
return out
}
func TestResolveDevBackendEnvDefaultUsesRepoLocalHome(t *testing.T) {
repo := makeRepoTree(t)
t.Setenv("KANDEV_TASK_ID", "")
t.Setenv("KANDEV_DATABASE_PATH", "")
dbPath, extra := resolveDevBackendEnv(repo)
env := devEnvToMap(extra)
wantHome := filepath.Join(repo, ".kandev-dev")
wantDB := filepath.Join(wantHome, "data", "kandev.db")
if dbPath != wantDB {
t.Fatalf("dbPath = %q, want %q", dbPath, wantDB)
}
if env["KANDEV_HOME_DIR"] != wantHome {
t.Fatalf("KANDEV_HOME_DIR = %q, want %q", env["KANDEV_HOME_DIR"], wantHome)
}
if env["KANDEV_DATABASE_PATH"] != "" {
t.Fatalf("KANDEV_DATABASE_PATH = %q, want empty (derive from home)", env["KANDEV_DATABASE_PATH"])
}
if env["KANDEV_DEBUG_DEV_MODE"] != "true" {
t.Fatalf("KANDEV_DEBUG_DEV_MODE = %q, want true", env["KANDEV_DEBUG_DEV_MODE"])
}
}
func TestResolveDevBackendEnvHonorsExplicitDatabasePath(t *testing.T) {
repo := makeRepoTree(t)
t.Setenv("KANDEV_TASK_ID", "")
t.Setenv("KANDEV_DATABASE_PATH", "/custom/kandev.db")
dbPath, extra := resolveDevBackendEnv(repo)
env := devEnvToMap(extra)
if dbPath != "/custom/kandev.db" {
t.Fatalf("dbPath = %q, want the explicit override", dbPath)
}
if env["KANDEV_DATABASE_PATH"] != "/custom/kandev.db" {
t.Fatalf("KANDEV_DATABASE_PATH = %q", env["KANDEV_DATABASE_PATH"])
}
if _, ok := env["KANDEV_HOME_DIR"]; ok {
t.Fatal("explicit override must not set KANDEV_HOME_DIR")
}
if env["KANDEV_DEBUG_DEV_MODE"] != "true" {
t.Fatalf("KANDEV_DEBUG_DEV_MODE = %q, want true", env["KANDEV_DEBUG_DEV_MODE"])
}
}
func TestResolveDevBackendEnvIgnoresWhitespaceOnlyOverride(t *testing.T) {
repo := makeRepoTree(t)
t.Setenv("KANDEV_TASK_ID", "")
t.Setenv("KANDEV_DATABASE_PATH", " ")
dbPath, extra := resolveDevBackendEnv(repo)
env := devEnvToMap(extra)
wantHome := filepath.Join(repo, ".kandev-dev")
if dbPath != filepath.Join(wantHome, "data", "kandev.db") {
t.Fatalf("dbPath = %q, want the repo-local dev db for a blank override", dbPath)
}
if env["KANDEV_HOME_DIR"] != wantHome {
t.Fatalf("KANDEV_HOME_DIR = %q, want %q", env["KANDEV_HOME_DIR"], wantHome)
}
if env["KANDEV_DATABASE_PATH"] != "" {
t.Fatalf("KANDEV_DATABASE_PATH = %q, want empty", env["KANDEV_DATABASE_PATH"])
}
}
func TestResolveDevBackendEnvHonorsEnvironmentConfiguredHome(t *testing.T) {
repo := makeRepoTree(t)
customHome := filepath.Join(t.TempDir(), "kandev")
t.Setenv("KANDEV_HOME_DIR", customHome)
t.Setenv("KANDEV_TASK_ID", "")
t.Setenv("KANDEV_DATABASE_PATH", "")
cfg := &commonconfig.Config{
HomeDir: customHome,
Source: commonconfig.ConfigSource{Values: map[string]commonconfig.SettingSource{
"homeDir": commonconfig.SourceEnvironment,
}},
}
dbPath, extra := resolveDevBackendEnv(repo, cfg)
if want := filepath.Join(customHome, "data", "kandev.db"); dbPath != want {
t.Fatalf("dbPath = %q, want %q", dbPath, want)
}
if env := devEnvToMap(extra); env["KANDEV_HOME_DIR"] != "" {
t.Fatalf("dev extra environment overwrote configured home: %q", env["KANDEV_HOME_DIR"])
}
}
func TestResolveDevBackendEnvClearsLeakedPathInTaskWorkspace(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("KANDEV_TASK_ID", "")
t.Setenv("KANDEV_DATABASE_PATH", "/leaked/from/parent/kandev.db")
repo := filepath.Join(home, ".kandev", "tasks", "ws", "repo")
dbPath, extra := resolveDevBackendEnv(repo)
env := devEnvToMap(extra)
wantHome := filepath.Join(repo, ".kandev-dev")
if dbPath != filepath.Join(wantHome, "data", "kandev.db") {
t.Fatalf("dbPath = %q, want the repo-local dev db", dbPath)
}
if env["KANDEV_HOME_DIR"] != wantHome {
t.Fatalf("KANDEV_HOME_DIR = %q, want %q", env["KANDEV_HOME_DIR"], wantHome)
}
if env["KANDEV_DATABASE_PATH"] != "" {
t.Fatalf("KANDEV_DATABASE_PATH = %q, want empty (parent-leaked path cleared)", env["KANDEV_DATABASE_PATH"])
}
}