package main import ( "encoding/json" "os" "path/filepath" "testing" ) func TestResolveDataDirAbsolute(t *testing.T) { abs := filepath.Join(t.TempDir(), "data") got := resolveDataDir(abs, "C:\\project") if got != abs { t.Fatalf("absolute data dir unchanged: got %q want %q", got, abs) } } func TestResolveDataDirRelativeWithProjectRoot(t *testing.T) { root := filepath.Join(t.TempDir(), "repo") got := resolveDataDir("data", root) want := filepath.Join(root, "data") if got != want { t.Fatalf("got %q want %q", got, want) } } func TestResolveDataDirRelativeWithoutProjectRoot(t *testing.T) { got := resolveDataDir("data", "") if !filepath.IsAbs(got) { t.Fatalf("expected absolute path when project root empty, got %q", got) } if filepath.Base(got) != "data" { t.Fatalf("expected basename data, got %q", got) } } func TestResolveDataDirDotProjectRoot(t *testing.T) { got := resolveDataDir("data", ".") if !filepath.IsAbs(got) { t.Fatalf("expected absolute path, got %q", got) } } func TestFindProjectRootFromServerDir(t *testing.T) { cwd, err := os.Getwd() if err != nil { t.Fatal(err) } root := findProjectRoot() found := false for _, name := range []string{"devrun.bat", "LAUNCH.bat", "run.bat"} { if _, err := os.Stat(filepath.Join(root, name)); err == nil { found = true break } } if !found { t.Fatalf("findProjectRoot=%q missing devrun.bat/LAUNCH.bat/run.bat", root) } // When tests run from server/, root should be parent of cwd or cwd itself. if root != cwd && root != filepath.Dir(cwd) { t.Logf("findProjectRoot=%q cwd=%q (acceptable if marker layout differs)", root, cwd) } } func TestFindAgentSourceDir(t *testing.T) { dir := findAgentSourceDir() goMod := filepath.Join(dir, "go.mod") if _, err := os.Stat(goMod); err != nil { t.Fatalf("agent source %q missing go.mod: %v", dir, err) } } func TestFindWebRoot(t *testing.T) { dir := findWebRoot() if dir == "" { t.Fatal("findWebRoot returned empty string") } index := filepath.Join(dir, "index.html") if _, err := os.Stat(index); err != nil { t.Fatalf("web root %q missing index.html: %v", dir, err) } } func TestServerConfigProviderPublicURL(t *testing.T) { cfg := DefaultConfig() cfg.Server.PublicURL = "https://forge.example" p := &serverConfigProvider{config: cfg} if got := p.PublicURL(); got != "https://forge.example" { t.Fatalf("PublicURL: got %q", got) } } func TestServerConfigProviderGetConfigJSON(t *testing.T) { cfg := DefaultConfig() cfg.Port = 7777 p := &serverConfigProvider{config: cfg} raw := p.GetConfigJSON() var decoded Config if err := json.Unmarshal(raw, &decoded); err != nil { t.Fatal(err) } if decoded.Port != 7777 { t.Fatalf("port in JSON: got %d", decoded.Port) } } func TestServerConfigProviderUpdateConfigFromJSON(t *testing.T) { dir := t.TempDir() cfg := DefaultConfig() cfg.DataDir = dir cfg.Wallet.Address = "48keep" p := &serverConfigProvider{config: cfg} if err := p.UpdateConfigFromJSON([]byte(`{"port":8888}`)); err != nil { t.Fatal(err) } if cfg.Port != 8888 { t.Fatalf("port not updated: %d", cfg.Port) } if cfg.Wallet.Address != "48keep" { t.Fatalf("partial update must preserve wallet: %q", cfg.Wallet.Address) } raw, err := os.ReadFile(filepath.Join(dir, "config.json")) if err != nil { t.Fatal(err) } var saved Config if err := json.Unmarshal(raw, &saved); err != nil { t.Fatal(err) } if saved.Port != 8888 { t.Fatalf("saved port: got %d", saved.Port) } } func TestServerConfigProviderUpdateConfigInvalidJSON(t *testing.T) { cfg := DefaultConfig() p := &serverConfigProvider{config: cfg} err := p.UpdateConfigFromJSON([]byte(`{not json`)) if err == nil { t.Fatal("expected error for invalid JSON") } } func TestServerConfigProviderOnSavedCallback(t *testing.T) { dir := t.TempDir() cfg := DefaultConfig() cfg.DataDir = dir called := false p := &serverConfigProvider{ config: cfg, onSaved: func(c *Config) { called = true if c.Port != 6666 { t.Fatalf("callback port: got %d", c.Port) } }, } if err := p.UpdateConfigFromJSON([]byte(`{"port":6666}`)); err != nil { t.Fatal(err) } if !called { t.Fatal("onSaved callback not invoked") } }