package runtimeconfig import ( "errors" "os" "path/filepath" "testing" ) // 本文件锁定 `$DATA_DIR/config/memory-tidy.json`(memorytidy.go)的读取口径: // // ① 文件缺失 / 空白 ⇒ 零值 + nil(「还没配过」不是错误); // ② `autoApply` **没写** ⇒ 不覆盖(AutoApplyOff=false)—— 这是指针存在的理由: // 「没配」与「显式配成 false」必须可区分,否则只写上限的文件会静默关掉自动档; // ③ `autoApply: false` ⇒ AutoApplyOff=true(一键回到只产建议); // ④ 文件存在但解不开 ⇒ 分类错误(调用方据此记 WARN 并回落代码默认值); // ⑤ 落点常量与 backthink 那一族同一目录、不同文件名(不共用常量,避免静默搬家)。 func TestLoadMemoryTidyTuning_MissingIsNotAnError(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "config", "memory-tidy.json") got, err := LoadMemoryTidyTuning(path) if err != nil { t.Fatalf("缺失文件的 err = %v, want nil", err) } if got.AutoApplyOff() || got.AutoApplyPerRound != 0 { t.Fatalf("缺失文件应当给零值:%+v", got) } } func TestLoadMemoryTidyTuning_BlankIsNotAnError(t *testing.T) { t.Parallel() path := writeMemoryTidyFile(t, " \n\t") got, err := LoadMemoryTidyTuning(path) if err != nil { t.Fatalf("空白文件的 err = %v, want nil", err) } if got.AutoApplyOff() { t.Fatal("空白文件不该被读成「关掉自动档」") } } func TestLoadMemoryTidyTuning_AbsentAutoApplyKeepsDefault(t *testing.T) { t.Parallel() path := writeMemoryTidyFile(t, `{"autoApplyPerRound": 7}`) got, err := LoadMemoryTidyTuning(path) if err != nil { t.Fatalf("LoadMemoryTidyTuning: %v", err) } if got.AutoApply != nil { t.Fatalf("没写 autoApply ⇒ 指针应为 nil,实际 %v", *got.AutoApply) } if got.AutoApplyOff() { t.Fatal("只写了上限的文件不得把自动档关掉(代码默认是开)") } if got.AutoApplyPerRound != 7 { t.Fatalf("autoApplyPerRound = %d, want 7", got.AutoApplyPerRound) } } func TestLoadMemoryTidyTuning_ExplicitFalseTurnsAutoApplyOff(t *testing.T) { t.Parallel() path := writeMemoryTidyFile(t, `{"autoApply": false}`) got, err := LoadMemoryTidyTuning(path) if err != nil { t.Fatalf("LoadMemoryTidyTuning: %v", err) } if !got.AutoApplyOff() { t.Fatal("显式 false 必须被读成「关掉自动档」") } on := writeMemoryTidyFile(t, `{"autoApply": true, "autoApplyPerRound": 3}`) got, err = LoadMemoryTidyTuning(on) if err != nil { t.Fatalf("LoadMemoryTidyTuning(on): %v", err) } if got.AutoApplyOff() || got.AutoApplyPerRound != 3 { t.Fatalf("显式 true 的回读 = %+v", got) } } func TestLoadMemoryTidyTuning_BrokenIsClassifiedError(t *testing.T) { t.Parallel() path := writeMemoryTidyFile(t, `{"autoApply": "yes"}`) if _, err := LoadMemoryTidyTuning(path); !errors.Is(err, ErrInvalidMemoryTidy) { t.Fatalf("坏配置的 err = %v, want ErrInvalidMemoryTidy", err) } } func TestDefaultMemoryTidyPathIsDataDirConfig(t *testing.T) { t.Parallel() got := DefaultMemoryTidyPath(filepath.Join("root", "data")) want := filepath.Join("root", "data", "config", "memory-tidy.json") if got != want { t.Fatalf("落点 = %q, want %q", got, want) } if MemoryTidyConfigRelPath != "config/memory-tidy.json" { t.Fatalf("相对路径 = %q, want config/memory-tidy.json", MemoryTidyConfigRelPath) } // 与 backthink 那一族同目录、不同文件:共用常量会在某一族搬家时静默带走另一族。 if filepath.Dir(MemoryTidyConfigRelPath) != filepath.Dir(BackthinkConfigRelPath) { t.Fatalf("两族应当同目录:%q vs %q", MemoryTidyConfigRelPath, BackthinkConfigRelPath) } } // writeMemoryTidyFile 在临时目录里写一份 memory-tidy.json 并返回路径。 func writeMemoryTidyFile(t *testing.T, body string) string { t.Helper() dir := filepath.Join(t.TempDir(), "config") if err := os.MkdirAll(dir, 0o700); err != nil { t.Fatalf("mkdir: %v", err) } path := filepath.Join(dir, memoryTidyFileName) if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatalf("write: %v", err) } return path }