package correctioncsv_test import ( "bytes" "encoding/csv" "encoding/json" "os" "path/filepath" "testing" "time" "voicesnap/internal/correctioncsv" "voicesnap/internal/history" "voicesnap/internal/userdict" ) func TestCorrectionCSVEndToEndPreviewThenAppend(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config")) store := userdict.New() if _, err := store.AppendNewReplacements([]userdict.Replacement{ {From: "点 CSV", To: ".csv"}, {From: "麦克", To: "Mac"}, }); err != nil { t.Fatal(err) } exported, err := correctioncsv.BuildHistoryCSV([]history.Entry{ {Timestamp: time.Date(2026, 5, 30, 13, 9, 0, 0, time.UTC).UnixMilli(), Text: "普通中文句子"}, {Timestamp: time.Date(2026, 5, 30, 13, 10, 0, 0, time.UTC).UnixMilli(), Text: "CSV 2026, \"quoted\"\nnext"}, {Timestamp: time.Date(2026, 5, 30, 13, 11, 0, 0, time.UTC).UnixMilli(), Text: "已经后处理的最终文本"}, }, time.UTC) if err != nil { t.Fatal(err) } records := readCSV(t, exported) if len(records) != 5 { t.Fatalf("expected header, example, and 3 history rows, got %d rows", len(records)) } records[2][3] = "点 PDF" records[2][4] = ".pdf" records[2][5] = "点 CSV" records[2][6] = ".csv" records[2][7] = "麦克" records[2][8] = "Mike" records[3][3] = "换\n行" records[3][4] = "换行词" importData := writeCSV(t, records) before := readUserDictFile(t, store.Path()) preview, err := correctioncsv.ParseAndPreviewCSV(importData, store.GetAll(), correctioncsv.Options{}) if err != nil { t.Fatal(err) } afterPreview := readUserDictFile(t, store.Path()) if !bytes.Equal(before, afterPreview) { t.Fatal("preview changed userdict.json before confirmation") } if preview.Summary.New != 2 { t.Fatalf("expected 2 new rules, got %+v", preview.Summary) } if preview.Summary.DictDuplicate != 1 { t.Fatalf("expected 1 dictionary duplicate, got %+v", preview.Summary) } if preview.Summary.Conflict != 1 { t.Fatalf("expected 1 conflict, got %+v", preview.Summary) } added, err := store.AppendNewReplacements(toReplacements(preview.NewRules)) if err != nil { t.Fatal(err) } if len(added) != 2 { t.Fatalf("expected 2 appended rules, got %d", len(added)) } if got := store.Apply("点 PDF 和 换 行 和 点 CSV 和 麦克"); got != ".pdf 和 换行词 和 .csv 和 Mac" { t.Fatalf("unexpected applied text: %q", got) } } func toReplacements(rules []correctioncsv.Rule) []userdict.Replacement { replacements := make([]userdict.Replacement, 0, len(rules)) for _, rule := range rules { replacements = append(replacements, userdict.Replacement{From: rule.From, To: rule.To}) } return replacements } func readCSV(t *testing.T, data []byte) [][]string { t.Helper() reader := csv.NewReader(bytes.NewReader(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF}))) records, err := reader.ReadAll() if err != nil { t.Fatal(err) } return records } func writeCSV(t *testing.T, records [][]string) []byte { t.Helper() var buf bytes.Buffer writer := csv.NewWriter(&buf) for _, row := range records { if err := writer.Write(row); err != nil { t.Fatal(err) } } writer.Flush() if err := writer.Error(); err != nil { t.Fatal(err) } return buf.Bytes() } func readUserDictFile(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var parsed any if err := json.Unmarshal(data, &parsed); err != nil { t.Fatal(err) } return data }