summaryrefslogtreecommitdiff
path: root/internal/store/store_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-06 11:12:03 +0300
committerPaul Buetow <paul@buetow.org>2026-04-06 11:12:03 +0300
commit616beecc41b573503dad9f5bfd9f353c6f826a8a (patch)
tree3189ae3f048dfc4e8ff79b83caab8ea43c2d7492 /internal/store/store_test.go
parent05f54cc0cb8cf3535698ab5027d200842bdb28e3 (diff)
refactor: extract shared CardStore into internal/store to eliminate duplication
FindCardDirectory, FindOrCreateCardDirectory, GenerateCardID and the ScanWords helper previously existed in both internal/utils.go (as standalone functions) and were partially duplicated in internal/gui/card_service.go (readWordFromDir, ScanExistingWords). Introduce internal/store.CardStore as the single source of truth for all on-disk card-directory operations. Both internal/processor and internal/gui now hold a *store.CardStore field and delegate to it, removing the last copy of the directory-scanning loop from card_service.go. internal/utils.go keeps thin forwarding wrappers for callers that import the root internal package. Also adds table-driven unit tests for the new package covering FindCardDirectory (including legacy _word.txt fallback), FindOrCreateCardDirectory, and CardStore.ScanWords. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/store/store_test.go')
-rw-r--r--internal/store/store_test.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/internal/store/store_test.go b/internal/store/store_test.go
new file mode 100644
index 0000000..6d34d0c
--- /dev/null
+++ b/internal/store/store_test.go
@@ -0,0 +1,140 @@
+package store_test
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "codeberg.org/snonux/totalrecall/internal/store"
+)
+
+// TestFindCardDirectory verifies that FindCardDirectory locates a directory by
+// its word.txt content and returns an empty string when no match exists.
+func TestFindCardDirectory(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ // Create a card directory with a word file.
+ cardDir := filepath.Join(tmpDir, "someCardID")
+ if err := os.MkdirAll(cardDir, 0755); err != nil {
+ t.Fatalf("setup: %v", err)
+ }
+ if err := os.WriteFile(filepath.Join(cardDir, "word.txt"), []byte("котка"), 0644); err != nil {
+ t.Fatalf("setup: %v", err)
+ }
+
+ tests := []struct {
+ name string
+ word string
+ wantDir string // empty means expect ""
+ wantHit bool
+ }{
+ {
+ name: "existing word found",
+ word: "котка",
+ wantDir: cardDir,
+ wantHit: true,
+ },
+ {
+ name: "unknown word returns empty string",
+ word: "куче",
+ wantDir: "",
+ wantHit: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := store.FindCardDirectory(tmpDir, tt.word)
+ if tt.wantHit && got != tt.wantDir {
+ t.Errorf("FindCardDirectory(%q) = %q; want %q", tt.word, got, tt.wantDir)
+ }
+ if !tt.wantHit && got != "" {
+ t.Errorf("FindCardDirectory(%q) = %q; want empty string", tt.word, got)
+ }
+ })
+ }
+}
+
+// TestFindCardDirectoryLegacyFallback checks that the legacy _word.txt naming
+// convention is still supported for backward compatibility.
+func TestFindCardDirectoryLegacyFallback(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ // Create a card directory with the old _word.txt naming.
+ cardDir := filepath.Join(tmpDir, "legacyCardID")
+ if err := os.MkdirAll(cardDir, 0755); err != nil {
+ t.Fatalf("setup: %v", err)
+ }
+ if err := os.WriteFile(filepath.Join(cardDir, "_word.txt"), []byte("ябълка"), 0644); err != nil {
+ t.Fatalf("setup: %v", err)
+ }
+
+ got := store.FindCardDirectory(tmpDir, "ябълка")
+ if got != cardDir {
+ t.Errorf("FindCardDirectory (legacy) = %q; want %q", got, cardDir)
+ }
+}
+
+// TestFindOrCreateCardDirectory verifies that a new directory is created when
+// no matching one exists, and that the same directory is returned on a second
+// call for the same word.
+func TestFindOrCreateCardDirectory(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ // First call: directory does not exist yet.
+ dir1 := store.FindOrCreateCardDirectory(tmpDir, "хляб")
+ if dir1 == "" || dir1 == tmpDir {
+ t.Fatalf("expected a new card directory, got %q", dir1)
+ }
+
+ // word.txt must have been written.
+ data, err := os.ReadFile(filepath.Join(dir1, "word.txt"))
+ if err != nil {
+ t.Fatalf("word.txt not created: %v", err)
+ }
+ if string(data) != "хляб" {
+ t.Errorf("word.txt content = %q; want %q", string(data), "хляб")
+ }
+
+ // Second call: must return the same directory.
+ dir2 := store.FindOrCreateCardDirectory(tmpDir, "хляб")
+ if dir2 != dir1 {
+ t.Errorf("second call returned %q; want %q", dir2, dir1)
+ }
+}
+
+// TestCardStoreScanWords verifies that ScanWords returns only words from
+// directories that pass the predicate and ignores hidden directories.
+func TestCardStoreScanWords(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ // Helper: create a card directory with word.txt.
+ makeCard := func(id, word string) string {
+ cardDir := filepath.Join(tmpDir, id)
+ _ = os.MkdirAll(cardDir, 0755)
+ _ = os.WriteFile(filepath.Join(cardDir, "word.txt"), []byte(word), 0644)
+ return cardDir
+ }
+
+ dir1 := makeCard("card1", "котка")
+ makeCard("card2", "куче")
+
+ // Hidden directory must be skipped.
+ makeCard(".hidden", "hidden")
+
+ // ScanWords with a predicate that only passes dir1.
+ cs := store.New(tmpDir)
+ words := cs.ScanWords(func(wordDir string) bool {
+ return wordDir == dir1
+ })
+
+ if len(words) != 1 || words[0] != "котка" {
+ t.Errorf("ScanWords = %v; want [котка]", words)
+ }
+
+ // ScanWords with nil predicate must return all non-hidden words.
+ allWords := cs.ScanWords(nil)
+ if len(allWords) != 2 {
+ t.Errorf("ScanWords(nil) = %v; want 2 words", allWords)
+ }
+}