diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-22 20:21:34 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-22 20:21:34 +0200 |
| commit | 3373b7e90c4e1ff1abcebe0594316131f546dfa8 (patch) | |
| tree | 8acc26309fce0048f156e5cec2acca9a5d891628 /internal/store | |
| parent | 528a9c0888f1db2449d30bf9c0c8b55fcd3c645c (diff) | |
Add unit tests to reach 63.3% coverage (task 335)
- internal/shell/shell_internal_test.go (new): tests prefixCompleter.Do
with table-driven cases covering empty prefix, partial match, no match,
and multi-word line (prefix after space)
- internal/cli/cli_test.go (new): tests pure helpers (logMsg, warn,
printHelp, shredFile), dispatch paths with real store (ls, search, cat,
rm, shred, get, paste, export, pathexport, open, edit, unknown command,
empty argv), error paths for cmdAdd/cmdImport/cmdImportR, readPIN env-var
path, completionFn commands-only path, makeActionFn nil cases
- internal/store/store_test.go: added TestSearchActionPathExport,
TestSearchActionWithCallback, TestSearchActionNilCallback, TestFzfEmpty,
TestRemoveInteractiveInvalidThenDecline, TestImportForceOverwrite
Total coverage: 44.8% → 63.3%
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/store')
| -rw-r--r-- | internal/store/store_test.go | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 9670a01..47d4692 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -745,3 +745,180 @@ func TestCommitIndexSkipsExisting(t *testing.T) { t.Errorf("index file was overwritten: got %q; want %q", got, sentinel) } } + +// --- TestSearchActionPathExport ---------------------------------------------- + +// TestSearchActionPathExport verifies that ActionPathExport preserves the full +// description path (not just the basename) when writing to the export dir. +func TestSearchActionPathExport(t *testing.T) { + ctx, store, cfg, _, _ := testSetup(t) + initGitRepo(t, cfg.DataDir) + + wantContent := "path export content\n" + if err := store.Add(ctx, "docs/subdir/report.txt", wantContent); err != nil { + t.Fatalf("Add: %v", err) + } + + results, err := store.Search(ctx, "report.txt", ActionPathExport, nil) + if err != nil { + t.Fatalf("Search ActionPathExport: %v", err) + } + if len(results) != 1 { + t.Fatalf("expected 1 result; got %d", len(results)) + } + + // ActionPathExport uses the full description as the export path. + exportedPath := filepath.Join(cfg.ExportDir, "docs/subdir/report.txt") + got, err := os.ReadFile(exportedPath) + if err != nil { + t.Fatalf("reading exported file at full path: %v", err) + } + if string(got) != wantContent { + t.Errorf("exported content = %q; want %q", got, wantContent) + } +} + +// --- TestSearchActionWithCallback -------------------------------------------- + +// TestSearchActionWithCallback verifies that Search passes the correct Index and +// Data to the actionFn for actions that delegate to the caller (ActionPaste etc.). +func TestSearchActionWithCallback(t *testing.T) { + ctx, store, cfg, _, _ := testSetup(t) + initGitRepo(t, cfg.DataDir) + + wantContent := "callback content\n" + if err := store.Add(ctx, "cb/entry.txt", wantContent); err != nil { + t.Fatalf("Add: %v", err) + } + + var gotDesc string + var gotContent string + actionFn := func(_ context.Context, idx *Index, d *Data) error { + gotDesc = idx.Description + gotContent = string(d.Content) + return nil + } + + // ActionPaste falls through to the default/actionFn branch of applyAction. + results, err := store.Search(ctx, "cb/entry.txt", ActionPaste, actionFn) + if err != nil { + t.Fatalf("Search with callback: %v", err) + } + if len(results) != 1 { + t.Fatalf("expected 1 result; got %d", len(results)) + } + if gotDesc != "cb/entry.txt" { + t.Errorf("callback got description %q; want %q", gotDesc, "cb/entry.txt") + } + if gotContent != wantContent { + t.Errorf("callback got content %q; want %q", gotContent, wantContent) + } +} + +// --- TestSearchActionNilCallback --------------------------------------------- + +// TestSearchActionNilCallback confirms that passing a nil actionFn for a +// callback-delegated action (ActionPaste) is handled gracefully without panic. +func TestSearchActionNilCallback(t *testing.T) { + ctx, store, cfg, _, _ := testSetup(t) + initGitRepo(t, cfg.DataDir) + + if err := store.Add(ctx, "nil/cb.txt", "data"); err != nil { + t.Fatalf("Add: %v", err) + } + + // nil actionFn: applyAction must return nil without calling anything. + _, err := store.Search(ctx, "nil/cb.txt", ActionPaste, nil) + if err != nil { + t.Fatalf("Search with nil callback: %v", err) + } +} + +// --- TestFzfEmpty ------------------------------------------------------------ + +// TestFzfEmpty calls Fzf on an empty store and confirms it returns ("", nil) +// without attempting to launch fzf. +func TestFzfEmpty(t *testing.T) { + ctx, store, _, _, _ := testSetup(t) + // No entries added — Fzf must return immediately. + result, err := store.Fzf(ctx) + if err != nil { + t.Fatalf("Fzf on empty store: %v", err) + } + if result != "" { + t.Errorf("Fzf on empty store = %q; want empty string", result) + } +} + +// --- TestRemoveInteractiveInvalidThenDecline --------------------------------- + +// TestRemoveInteractiveInvalidThenDecline exercises the retry loop in +// confirmAndRemove: an unrecognised answer causes a re-prompt; "n" then exits. +func TestRemoveInteractiveInvalidThenDecline(t *testing.T) { + ctx, store, cfg, _, g := testSetup(t) + initGitRepo(t, cfg.DataDir) + + if err := store.Add(ctx, "retry/entry", "data"); err != nil { + t.Fatalf("Add: %v", err) + } + if err := g.Commit(ctx); err != nil { + t.Fatalf("Commit: %v", err) + } + + // "maybe\n" is not "y" or "n" → retry; "n\n" then declines. + input := strings.NewReader("maybe\nn\n") + if err := store.Remove(ctx, "retry/entry", input); err != nil { + t.Fatalf("Remove: %v", err) + } + + // Entry must still be present because deletion was declined. + count := 0 + if err := store.WalkIndexes(ctx, "", func(*Index) error { count++; return nil }); err != nil { + t.Fatalf("WalkIndexes: %v", err) + } + if count != 1 { + t.Errorf("expected 1 entry after decline; got %d", count) + } +} + +// --- TestImportForceOverwrite ------------------------------------------------ + +// TestImportForceOverwrite confirms that force=true overwrites an existing entry +// while force=false (the default) skips it silently. +func TestImportForceOverwrite(t *testing.T) { + ctx, store, cfg, c, _ := testSetup(t) + initGitRepo(t, cfg.DataDir) + + srcDir := t.TempDir() + src := filepath.Join(srcDir, "f.txt") + if err := os.WriteFile(src, []byte("original\n"), 0o600); err != nil { + t.Fatalf("write src: %v", err) + } + + // First import. + if err := store.Import(ctx, src, "force/f.txt", false); err != nil { + t.Fatalf("first Import: %v", err) + } + + // Overwrite source with new content. + if err := os.WriteFile(src, []byte("updated\n"), 0o600); err != nil { + t.Fatalf("write updated src: %v", err) + } + + // Second import with force=true must overwrite. + if err := store.Import(ctx, src, "force/f.txt", true); err != nil { + t.Fatalf("second Import (force): %v", err) + } + + var idx *Index + if err := store.WalkIndexes(ctx, "", func(i *Index) error { idx = i; return nil }); err != nil { + t.Fatalf("WalkIndexes: %v", err) + } + d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c) + if err != nil { + t.Fatalf("loadData: %v", err) + } + if string(d.Content) != "updated\n" { + t.Errorf("content after force import = %q; want %q", d.Content, "updated\n") + } +} |
