package userdict import ( "encoding/json" "fmt" "os" "path/filepath" "testing" ) func TestAppendNewReplacementsAppendsEnabledRulesAndPersists0600(t *testing.T) { store := testStore(t) added, err := store.AppendNewReplacements([]Replacement{ {From: " 点 CSV ", To: " .csv "}, {From: "右奥特", To: "右 Alt"}, }) if err != nil { t.Fatal(err) } if len(added) != 2 { t.Fatalf("expected 2 added entries, got %d", len(added)) } if got := store.Apply("点 CSV 和 右奥特"); got != ".csv 和 右 Alt" { t.Fatalf("unexpected applied text: %q", got) } info, err := os.Stat(store.path) if err != nil { t.Fatal(err) } if got := info.Mode().Perm(); got != 0600 { t.Fatalf("expected userdict.json mode 0600, got %o", got) } } func TestAppendNewReplacementsRejectsDuplicateAndConflictWithoutMutation(t *testing.T) { store := testStore(t) if _, err := store.AppendNewReplacements([]Replacement{{From: "麦克", To: "Mac"}}); err != nil { t.Fatal(err) } if _, err := store.AppendNewReplacements([]Replacement{{From: "麦克", To: "Mac"}}); err == nil { t.Fatal("expected duplicate to fail") } if _, err := store.AppendNewReplacements([]Replacement{{From: "麦克", To: "Mike"}}); err == nil { t.Fatal("expected conflict to fail") } entries := store.GetAll() if len(entries) != 1 { t.Fatalf("expected dictionary to remain unchanged, got %d entries", len(entries)) } if entries[0].From != "麦克" || entries[0].To != "Mac" { t.Fatalf("unexpected entry after failed imports: %+v", entries[0]) } } func TestAppendNewReplacementsWriteFailureDoesNotMutateMemory(t *testing.T) { store := &Store{ path: filepath.Join(t.TempDir(), "missing", "userdict.json"), entries: []Entry{ {ID: 1, From: "麦克", To: "Mac", Enabled: true, CreatedAt: 1, UpdatedAt: 1}, }, } if _, err := store.AppendNewReplacements([]Replacement{{From: "点 CSV", To: ".csv"}}); err == nil { t.Fatal("expected write failure") } entries := store.GetAll() if len(entries) != 1 { t.Fatalf("expected memory to remain unchanged, got %d entries", len(entries)) } if got := store.Apply("麦克 点 CSV"); got != "Mac 点 CSV" { t.Fatalf("unexpected applied text after failed write: %q", got) } } func TestLoadOldFromToOnlyFormatDefaultsEnabled(t *testing.T) { path := filepath.Join(t.TempDir(), "userdict.json") data, err := json.Marshal(map[string]any{ "replacements": []map[string]string{ {"from": "点 CSV", "to": ".csv"}, }, }) if err != nil { t.Fatal(err) } if err := os.WriteFile(path, data, 0600); err != nil { t.Fatal(err) } store := &Store{path: path} store.load() entries := store.GetAll() if len(entries) != 1 { t.Fatalf("expected one loaded entry, got %d", len(entries)) } if !entries[0].Enabled { t.Fatal("expected old format entry to default enabled") } if got := store.Apply("点 CSV"); got != ".csv" { t.Fatalf("unexpected applied text: %q", got) } } func TestGetPageClampsAndSortsNewestFirst(t *testing.T) { store := testStore(t) for i := 0; i < 12; i++ { store.entries = append(store.entries, Entry{ ID: int64(i + 1), From: fmt.Sprintf("from %02d", i), To: fmt.Sprintf("to %02d", i), Enabled: true, CreatedAt: int64(1000 + i), UpdatedAt: int64(1000 + i), }) } page := store.GetPage(2, 5) if page.Total != 12 || page.TotalPages != 3 || page.Page != 2 || page.PageSize != 5 { t.Fatalf("unexpected page metadata: %+v", page) } if len(page.Entries) != 5 || page.Entries[0].From != "from 06" || page.Entries[4].From != "from 02" { t.Fatalf("unexpected page entries: %+v", page.Entries) } last := store.GetPage(99, 5) if last.Page != 3 || len(last.Entries) != 2 || last.Entries[0].From != "from 01" { t.Fatalf("expected clamped last page, got %+v", last) } defaultSize := store.GetPage(1, 0) if defaultSize.PageSize != defaultPageSize { t.Fatalf("expected default page size %d, got %d", defaultPageSize, defaultSize.PageSize) } limitedSize := store.GetPage(1, maxPageSize+1) if limitedSize.PageSize != maxPageSize { t.Fatalf("expected max page size %d, got %d", maxPageSize, limitedSize.PageSize) } } func TestGetPageHandlesEmptyDictionary(t *testing.T) { store := testStore(t) page := store.GetPage(99, 100) if page.Total != 0 || page.TotalPages != 0 || page.Page != 1 || len(page.Entries) != 0 { t.Fatalf("unexpected empty page: %+v", page) } } func testStore(t *testing.T) *Store { t.Helper() return &Store{ path: filepath.Join(t.TempDir(), "userdict.json"), } }