Files
MyGoNavi/internal/appdata/root_test.go
Syngnat 97cffa7809 feat(settings): 支持自定义并持久化日志目录
- 将日志目录配置整合到数据目录设置,支持选择、打开、恢复默认与保存
- 持久化日志目录并保留环境变量优先级,目录调整后重启生效
- 通过跨进程锁和原子写入保护数据目录与日志目录配置
- 移除重复标题并互斥目录修改操作,补齐六语言文案
- 补充桌面接口、浏览器模拟及并发回归测试
2026-07-23 11:40:17 +08:00

233 lines
7.3 KiB
Go

package appdata
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestResolveActiveRootDefaultsToLegacyGonaviDir(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
root, err := ResolveActiveRoot()
if err != nil {
t.Fatalf("ResolveActiveRoot returned error: %v", err)
}
expected := filepath.Join(homeDir, ".gonavi")
if root != expected {
t.Fatalf("expected default root %q, got %q", expected, root)
}
}
func TestSetActiveRootWritesBootstrapAndResolveUsesIt(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
customRoot := filepath.Join(t.TempDir(), "gonavi-data")
savedRoot, err := SetActiveRoot(customRoot)
if err != nil {
t.Fatalf("SetActiveRoot returned error: %v", err)
}
if savedRoot != customRoot {
t.Fatalf("expected saved root %q, got %q", customRoot, savedRoot)
}
if _, err := os.Stat(BootstrapPath()); err != nil {
t.Fatalf("expected bootstrap file to exist: %v", err)
}
resolvedRoot, err := ResolveActiveRoot()
if err != nil {
t.Fatalf("ResolveActiveRoot returned error: %v", err)
}
if resolvedRoot != customRoot {
t.Fatalf("expected custom root %q, got %q", customRoot, resolvedRoot)
}
}
func TestSetActiveRootResetToDefaultRemovesBootstrap(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
customRoot := filepath.Join(t.TempDir(), "gonavi-data")
if _, err := SetActiveRoot(customRoot); err != nil {
t.Fatalf("SetActiveRoot custom returned error: %v", err)
}
defaultRoot, err := SetActiveRoot("")
if err != nil {
t.Fatalf("SetActiveRoot default returned error: %v", err)
}
expectedDefault := filepath.Join(homeDir, ".gonavi")
if defaultRoot != expectedDefault {
t.Fatalf("expected default root %q, got %q", expectedDefault, defaultRoot)
}
if _, err := os.Stat(BootstrapPath()); !os.IsNotExist(err) {
t.Fatalf("expected bootstrap file to be removed, got err=%v", err)
}
}
func TestResolveActiveRootPrefersEnvOverride(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
customRoot := filepath.Join(t.TempDir(), "custom-root")
if _, err := SetActiveRoot(customRoot); err != nil {
t.Fatalf("SetActiveRoot returned error: %v", err)
}
overrideRoot := filepath.Join(t.TempDir(), "override-root")
t.Setenv(dataRootEnvName, overrideRoot)
resolvedRoot, err := ResolveActiveRoot()
if err != nil {
t.Fatalf("ResolveActiveRoot returned error: %v", err)
}
if resolvedRoot != overrideRoot {
t.Fatalf("expected env override root %q, got %q", overrideRoot, resolvedRoot)
}
}
func TestDataRootAndLogDirectoryPreserveEachOtherInBootstrap(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
customDataRoot := filepath.Join(t.TempDir(), "gonavi-data")
customLogDirectory := filepath.Join(t.TempDir(), "gonavi-logs")
if _, err := SetConfiguredLogDirectory(customLogDirectory); err != nil {
t.Fatalf("SetConfiguredLogDirectory returned error: %v", err)
}
if _, err := SetActiveRoot(customDataRoot); err != nil {
t.Fatalf("SetActiveRoot returned error: %v", err)
}
resolvedLogDirectory, err := ResolveConfiguredLogDirectory()
if err != nil {
t.Fatalf("ResolveConfiguredLogDirectory returned error: %v", err)
}
if resolvedLogDirectory != customLogDirectory {
t.Fatalf("expected custom log directory %q, got %q", customLogDirectory, resolvedLogDirectory)
}
if _, err := SetActiveRoot(""); err != nil {
t.Fatalf("reset SetActiveRoot returned error: %v", err)
}
if _, err := os.Stat(BootstrapPath()); err != nil {
t.Fatalf("bootstrap should remain while log directory is customized: %v", err)
}
resolvedLogDirectory, err = ResolveConfiguredLogDirectory()
if err != nil || resolvedLogDirectory != customLogDirectory {
t.Fatalf("log directory after data-root reset = %q, %v", resolvedLogDirectory, err)
}
if _, err := SetActiveRoot(customDataRoot); err != nil {
t.Fatalf("restore custom data root returned error: %v", err)
}
if _, err := SetConfiguredLogDirectory(""); err != nil {
t.Fatalf("reset SetConfiguredLogDirectory returned error: %v", err)
}
resolvedRoot, err := ResolveActiveRoot()
if err != nil || resolvedRoot != customDataRoot {
t.Fatalf("data root after log reset = %q, %v", resolvedRoot, err)
}
if _, err := os.Stat(BootstrapPath()); err != nil {
t.Fatalf("bootstrap should remain while data root is customized: %v", err)
}
if _, err := SetActiveRoot(""); err != nil {
t.Fatalf("final SetActiveRoot reset returned error: %v", err)
}
if _, err := os.Stat(BootstrapPath()); !os.IsNotExist(err) {
t.Fatalf("bootstrap should be removed when both settings use defaults, got err=%v", err)
}
}
func TestSetConfiguredLogDirectoryRejectsFilePathWithoutChangingConfig(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
blockingPath := filepath.Join(t.TempDir(), "not-a-directory")
if err := os.WriteFile(blockingPath, []byte("blocked"), 0o644); err != nil {
t.Fatalf("write blocking file: %v", err)
}
if _, err := SetConfiguredLogDirectory(blockingPath); err == nil {
t.Fatal("expected file path to be rejected as a log directory")
}
configured, err := ResolveConfiguredLogDirectory()
if err != nil {
t.Fatalf("ResolveConfiguredLogDirectory returned error: %v", err)
}
if configured != "" {
t.Fatalf("failed update changed configured log directory to %q", configured)
}
}
func TestSetConfiguredLogDirectoryRejectsDirectoryAtLogFilePathWithoutChangingConfig(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
targetDirectory := filepath.Join(t.TempDir(), "logs")
if err := os.MkdirAll(filepath.Join(targetDirectory, configuredLogFileName), 0o755); err != nil {
t.Fatalf("create blocking log-file directory: %v", err)
}
if _, err := SetConfiguredLogDirectory(targetDirectory); err == nil {
t.Fatal("expected a directory at the log file path to be rejected")
}
configured, err := ResolveConfiguredLogDirectory()
if err != nil {
t.Fatalf("ResolveConfiguredLogDirectory returned error: %v", err)
}
if configured != "" {
t.Fatalf("failed update changed configured log directory to %q", configured)
}
}
func TestBootstrapFileLockSerializesAccess(t *testing.T) {
lockPath := filepath.Join(t.TempDir(), "storage_root.json.lock")
first, err := acquireBootstrapFileLock(lockPath)
if err != nil {
t.Fatalf("acquire first bootstrap lock: %v", err)
}
defer first.Close()
acquired := make(chan *bootstrapFileLock, 1)
errs := make(chan error, 1)
go func() {
second, err := acquireBootstrapFileLock(lockPath)
if err != nil {
errs <- err
return
}
acquired <- second
}()
select {
case second := <-acquired:
_ = second.Close()
t.Fatal("second bootstrap lock acquired before the first was released")
case err := <-errs:
t.Fatalf("acquire second bootstrap lock: %v", err)
case <-time.After(50 * time.Millisecond):
}
if err := first.Close(); err != nil {
t.Fatalf("release first bootstrap lock: %v", err)
}
select {
case second := <-acquired:
if err := second.Close(); err != nil {
t.Fatalf("release second bootstrap lock: %v", err)
}
case err := <-errs:
t.Fatalf("acquire second bootstrap lock after release: %v", err)
case <-time.After(5 * time.Second):
t.Fatal("second bootstrap lock did not acquire after the first was released")
}
}