feat: migrate config beside executable

This commit is contained in:
QiuSW
2026-07-27 11:33:22 +08:00
parent 206794f78c
commit eeb7950806
9 changed files with 307 additions and 42 deletions
+130
View File
@@ -1,6 +1,7 @@
package config
import (
"errors"
"os"
"path/filepath"
"testing"
@@ -100,6 +101,135 @@ func TestStoreMissingFileReturnsDefaults(t *testing.T) {
}
}
func TestDefaultPathForExecutableUsesExecutableDirectory(t *testing.T) {
executable := filepath.Join(t.TempDir(), "bin", "chub.exe")
path, err := defaultPathForExecutable(executable)
if err != nil || path != filepath.Join(filepath.Dir(executable), "config.json") {
t.Fatalf("defaultPathForExecutable() = %q, %v", path, err)
}
if _, err := defaultPathForExecutable("chub.exe"); !errors.Is(err, ErrDefaultConfigUnavailable) {
t.Fatalf("relative executable error = %v", err)
}
}
func TestOpenDefaultAtMigratesValidLegacyWithoutRemovingIt(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "portable", "config.json")
legacy := filepath.Join(directory, "legacy", "config.json")
legacyStore, err := New(legacy)
if err != nil {
t.Fatal(err)
}
want := File{
Settings: Settings{RemoteDebugStartPort: 9777, CloseOnExit: true},
Instances: []Instance{{
ID: "migrated",
Name: "迁移实例",
PreferredRemoteDebugPort: 9777,
Launch: domain.LaunchSpec{Kind: domain.BrowserChrome, UserDataDir: `C:\profiles\migrated`},
}},
}
if err := legacyStore.Save(want); err != nil {
t.Fatal(err)
}
store, err := openDefaultAt(target, legacy)
if err != nil || store.Path() != target {
t.Fatalf("openDefaultAt() = %#v, %v", store, err)
}
got, err := store.Load()
if err != nil || len(got.Instances) != 1 || got.Instances[0].ID != "migrated" || got.Instances[0].PreferredRemoteDebugPort != 9777 {
t.Fatalf("migrated config = %#v, error = %v", got, err)
}
if _, err := os.Stat(legacy); err != nil {
t.Fatalf("legacy config was not retained: %v", err)
}
}
func TestOpenDefaultAtPrefersExistingTargetOverLegacy(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "portable", "config.json")
legacy := filepath.Join(directory, "legacy", "config.json")
targetStore, err := New(target)
if err != nil {
t.Fatal(err)
}
if err := targetStore.Save(File{Instances: []Instance{{ID: "target", PreferredRemoteDebugPort: 9666}}}); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(legacy), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(legacy, []byte(`not-json`), 0o600); err != nil {
t.Fatal(err)
}
store, err := openDefaultAt(target, legacy)
if err != nil {
t.Fatalf("existing target was affected by legacy error: %v", err)
}
got, err := store.Load()
if err != nil || len(got.Instances) != 1 || got.Instances[0].ID != "target" {
t.Fatalf("target config = %#v, error = %v", got, err)
}
}
func TestOpenDefaultAtLeavesMissingTargetAbsentWithoutLegacy(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "portable", "config.json")
store, err := openDefaultAt(target, filepath.Join(directory, "legacy", "config.json"))
if err != nil || store.Path() != target {
t.Fatalf("openDefaultAt() = %#v, %v", store, err)
}
if _, err := os.Stat(target); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("target was unexpectedly created: %v", err)
}
}
func TestOpenDefaultAtRejectsInvalidLegacyWithoutCreatingTarget(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "portable", "config.json")
legacy := filepath.Join(directory, "legacy", "config.json")
if err := os.MkdirAll(filepath.Dir(legacy), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(legacy, []byte(`{`), 0o600); err != nil {
t.Fatal(err)
}
if _, err := openDefaultAt(target, legacy); !errors.Is(err, ErrDefaultConfigUnavailable) {
t.Fatalf("invalid legacy error = %v", err)
}
if _, err := os.Stat(target); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("target was created after invalid legacy: %v", err)
}
if _, err := os.Stat(legacy); err != nil {
t.Fatalf("legacy config was not retained: %v", err)
}
}
func TestOpenDefaultAtRejectsUncreatableTargetWithoutTouchingLegacy(t *testing.T) {
directory := t.TempDir()
parentFile := filepath.Join(directory, "not-a-directory")
if err := os.WriteFile(parentFile, []byte("x"), 0o600); err != nil {
t.Fatal(err)
}
target := filepath.Join(parentFile, "config.json")
legacy := filepath.Join(directory, "legacy", "config.json")
legacyStore, err := New(legacy)
if err != nil {
t.Fatal(err)
}
if err := legacyStore.Save(File{Instances: []Instance{{ID: "legacy", PreferredRemoteDebugPort: 9666}}}); err != nil {
t.Fatal(err)
}
if _, err := openDefaultAt(target, legacy); !errors.Is(err, ErrDefaultConfigUnavailable) {
t.Fatalf("uncreatable target error = %v", err)
}
if _, err := os.Stat(legacy); err != nil {
t.Fatalf("legacy config was changed: %v", err)
}
}
func TestStoreRoundTripsReferencedProxyWithoutCredentials(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.json")
store, err := New(path)