353 lines
13 KiB
Go
353 lines
13 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"chub/internal/domain"
|
|
)
|
|
|
|
func TestStoreRoundTripAndCreatesPrivateFile(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "nested", "config.json")
|
|
store, err := New(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := File{Settings: Settings{ChromePath: "chrome.exe", RemoteDebugStartPort: domain.DefaultRemoteDebugPort, CloseOnExit: true}, Instances: []Instance{{ID: "a", Name: "运营", Launch: domain.LaunchSpec{Kind: domain.BrowserChrome, UserDataDir: `C:\profiles\a`}}}}
|
|
if err := store.Save(want); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Version != currentVersion || got.Settings.ChromePath != want.Settings.ChromePath || got.Settings.RemoteDebugStartPort != want.Settings.RemoteDebugStartPort || got.Settings.DefaultDirSource != DirectorySourcePortable || got.Settings.LogDirSource != DirectorySourcePortable || len(got.Instances) != 1 || got.Instances[0].ID != "a" {
|
|
t.Fatalf("round trip mismatch: %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestStoreAddsDefaultRemoteDebugPortForExistingConfig(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
if err := os.WriteFile(path, []byte(`{"version":1,"settings":{},"instances":[]}`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := New(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil || got.Settings.RemoteDebugStartPort != domain.DefaultRemoteDebugPort {
|
|
t.Fatalf("settings = %#v, error = %v", got.Settings, err)
|
|
}
|
|
}
|
|
|
|
func TestStoreAssignsPreferredPortsForLegacyInstancesInStableOrder(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
contents := `{"version":1,"settings":{"remoteDebugStartPort":9777},"instances":[{"id":"first","name":"第一实例","launch":{"Kind":"chrome","UserDataDir":"C:\\profiles\\first"}},{"id":"second","name":"第二实例","launch":{"Kind":"edge","UserDataDir":"C:\\profiles\\second"}}]}`
|
|
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := New(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Instances[0].PreferredRemoteDebugPort != 9777 || got.Instances[1].PreferredRemoteDebugPort != 9778 {
|
|
t.Fatalf("legacy preferred ports = %#v", got.Instances)
|
|
}
|
|
if err := store.Save(got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reloaded, err := store.Load()
|
|
if err != nil || reloaded.Instances[0].PreferredRemoteDebugPort != 9777 || reloaded.Instances[1].PreferredRemoteDebugPort != 9778 {
|
|
t.Fatalf("persisted preferred ports = %#v, error = %v", reloaded.Instances, err)
|
|
}
|
|
}
|
|
|
|
func TestStoreRejectsInvalidOrDuplicatePreferredPorts(t *testing.T) {
|
|
store, err := New(filepath.Join(t.TempDir(), "config.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
duplicate := File{Instances: []Instance{
|
|
{ID: "one", Name: "一号", PreferredRemoteDebugPort: 9666},
|
|
{ID: "two", Name: "二号", PreferredRemoteDebugPort: 9666},
|
|
}}
|
|
if err := store.Save(duplicate); err == nil {
|
|
t.Fatal("duplicate preferred port was accepted")
|
|
}
|
|
invalid := File{Instances: []Instance{{ID: "one", Name: "一号", PreferredRemoteDebugPort: domain.MinRemoteDebugPort - 1}}}
|
|
if err := store.Save(invalid); err == nil {
|
|
t.Fatal("invalid preferred port was accepted")
|
|
}
|
|
}
|
|
|
|
func TestStoreMissingFileReturnsDefaults(t *testing.T) {
|
|
store, err := New(filepath.Join(t.TempDir(), "config.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Version != currentVersion || len(got.Instances) != 0 {
|
|
t.Fatalf("unexpected defaults: %#v", got)
|
|
}
|
|
}
|
|
|
|
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 TestDefaultInstanceUserDataDirectoryUsesExecutableDirectoryAndStableID(t *testing.T) {
|
|
executable := filepath.Join(t.TempDir(), "published", "chub.exe")
|
|
root, err := defaultInstanceUserDataRootForExecutable(executable)
|
|
if err != nil || root != filepath.Join(filepath.Dir(executable), "user_data_dirs") {
|
|
t.Fatalf("defaultInstanceUserDataRootForExecutable() = %q, %v", root, err)
|
|
}
|
|
first, err := defaultInstanceUserDataDir(root, "instance-one")
|
|
if err != nil || first != filepath.Join(root, "instance-one") {
|
|
t.Fatalf("defaultInstanceUserDataDir() = %q, %v", first, err)
|
|
}
|
|
second, err := defaultInstanceUserDataDir(root, "instance-two")
|
|
if err != nil || second == first {
|
|
t.Fatalf("distinct instance directory = %q, %v", second, err)
|
|
}
|
|
if _, err := defaultInstanceUserDataRootForExecutable("chub.exe"); !errors.Is(err, ErrDefaultConfigUnavailable) {
|
|
t.Fatalf("relative executable root error = %v", err)
|
|
}
|
|
if _, err := defaultInstanceUserDataDir(root, `..\other`); !errors.Is(err, ErrDefaultConfigUnavailable) {
|
|
t.Fatalf("unsafe instance id error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStoreMigratesHistoricalDirectoriesToPortableDefaults(t *testing.T) {
|
|
base := filepath.Join(t.TempDir(), "published")
|
|
store, err := New(filepath.Join(base, "config.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.Save(File{Settings: Settings{DefaultDir: legacyDefaultUserDataDir, LogDir: legacyLogDir}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Settings.DefaultDir != filepath.Join(base, "user_data_dirs") || got.Settings.LogDir != filepath.Join(base, "logs") || got.Settings.DefaultDirSource != DirectorySourcePortable || got.Settings.LogDirSource != DirectorySourcePortable {
|
|
t.Fatalf("portable migration = %#v", got.Settings)
|
|
}
|
|
|
|
custom := filepath.Join(t.TempDir(), "custom-profiles")
|
|
got.Settings.DefaultDir = custom
|
|
got.Settings.DefaultDirSource = DirectorySourceCustom
|
|
if err := store.Save(got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reloaded, err := store.Load()
|
|
if err != nil || reloaded.Settings.DefaultDir != custom || reloaded.Settings.DefaultDirSource != DirectorySourceCustom {
|
|
t.Fatalf("custom directory = %#v, error = %v", reloaded.Settings, err)
|
|
}
|
|
}
|
|
|
|
func TestStoreRejectsInvalidAndDuplicateInstanceNames(t *testing.T) {
|
|
store, err := New(filepath.Join(t.TempDir(), "config.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, instances := range [][]Instance{
|
|
{{ID: "one", Name: "审核"}, {ID: "two", Name: "审核"}},
|
|
{{ID: "one", Name: "Audit"}, {ID: "two", Name: "aUDIT"}},
|
|
{{ID: "one", Name: "CON"}},
|
|
} {
|
|
if err := store.Save(File{Instances: instances}); err == nil {
|
|
t.Fatalf("unsafe instance configuration persisted: %#v", instances)
|
|
}
|
|
}
|
|
}
|
|
|
|
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", Name: "目标实例", 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", Name: "旧实例", 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)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := File{
|
|
Proxies: []ProxyProfile{{ID: "proxy-sg", Name: "新加坡出口", Server: "HTTPS://Proxy.Local:8443"}},
|
|
Instances: []Instance{{ID: "a", Name: "运营", ProxyID: "proxy-sg", Launch: domain.LaunchSpec{Kind: domain.BrowserChrome, UserDataDir: `C:\profiles\a`}}},
|
|
}
|
|
if err := store.Save(want); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil || len(got.Proxies) != 1 || got.Proxies[0].Server != "https://proxy.local:8443" || got.Instances[0].ProxyID != "proxy-sg" || got.Instances[0].Launch.ProxyServer != "" {
|
|
t.Fatalf("proxy round trip = %#v, error = %v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestStoreRejectsBlankOrDuplicateProxyNamesAndServers(t *testing.T) {
|
|
store, err := New(filepath.Join(t.TempDir(), "config.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, proxies := range [][]ProxyProfile{
|
|
{{ID: "blank", Name: " ", Server: "http://127.0.0.1:8080"}},
|
|
{{ID: "one", Name: "新加坡", Server: "http://127.0.0.1:8080"}, {ID: "two", Name: "新加坡", Server: "http://127.0.0.1:8081"}},
|
|
{{ID: "one", Name: "Singapore", Server: "http://127.0.0.1:8080"}, {ID: "two", Name: "sINGAPORE", Server: "http://127.0.0.1:8081"}},
|
|
{{ID: "one", Name: "新加坡", Server: "http://127.0.0.1:8080"}, {ID: "two", Name: "东京", Server: "HTTP://127.0.0.1:8080"}},
|
|
} {
|
|
if err := store.Save(File{Proxies: proxies}); err == nil {
|
|
t.Fatalf("unsafe proxy profiles persisted: %#v", proxies)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStoreMigratesLegacyProxyAndRejectsUnsafeConfig(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
if err := os.WriteFile(path, []byte(`{"version":1,"settings":{},"instances":[{"id":"a","name":"运营","launch":{"Kind":"chrome","UserDataDir":"C:\\profiles\\a","ProxyServer":"http://127.0.0.1:8080"}}]}`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := New(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := store.Load()
|
|
if err != nil || len(got.Proxies) != 1 || got.Instances[0].ProxyID == "" || got.Instances[0].Launch.ProxyServer != "" {
|
|
t.Fatalf("legacy proxy migration = %#v, error = %v", got, err)
|
|
}
|
|
if err := store.Save(File{Proxies: []ProxyProfile{{ID: "bad", Name: "bad", Server: "http://user:secret@127.0.0.1:8080"}}}); err == nil {
|
|
t.Fatal("credential proxy was persisted")
|
|
}
|
|
}
|