From b636b12566ea6c2862bf3adc6686c57e0d785ca3 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 2 Mar 2026 10:56:42 +0200 Subject: store: make Data carry injected deps (task 402) --- internal/cli/cli.go | 12 +++++------- internal/cli/cli_test.go | 11 +++++------ internal/store/data.go | 24 ++++++++++++++---------- internal/store/data_test.go | 29 ++++++++++++++--------------- internal/store/store.go | 14 ++++++++------ internal/store/store_test.go | 16 ++++++++-------- 6 files changed, 54 insertions(+), 52 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index b6d47fc..7b18c34 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -55,7 +55,6 @@ type CLI struct { g *git.Git clip *clipboard.Clipboard sh *shell.Shell - cipher *crypto.Cipher lastResult string // most recent search result description } @@ -101,11 +100,10 @@ func newCLI(ctx context.Context) (*CLI, error) { clip := clipboard.New(cfg.GnomeClipboardCmd, cfg.MacOSClipboardCmd) c := &CLI{ - cfg: &cfg, - st: st, - g: g, - clip: clip, - cipher: ciph, + cfg: &cfg, + st: st, + g: g, + clip: clip, } // Create the shell with a completion function that references the CLI. @@ -577,7 +575,7 @@ func (c *CLI) makeActionFn(ctx context.Context, action store.Action) func(contex if err := externalEdit(ctx, c.cfg.ExportDir, c.cfg.EditCmd, exportName); err != nil { return err } - return d.ReimportAfterExport(ctx, c.cipher, c.g) + return d.ReimportAfterExport(ctx) } default: diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 39c79aa..9ff0510 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -62,12 +62,11 @@ func testCLI(t *testing.T) (*CLI, *config.Config) { t.Cleanup(func() { sh.Close() }) return &CLI{ - cfg: cfg, - st: st, - g: g, - clip: clipboard.New("", ""), - sh: sh, - cipher: ciph, + cfg: cfg, + st: st, + g: g, + clip: clipboard.New("", ""), + sh: sh, }, cfg } diff --git a/internal/store/data.go b/internal/store/data.go index 25e64b1..8ab8429 100644 --- a/internal/store/data.go +++ b/internal/store/data.go @@ -17,11 +17,13 @@ type Data struct { Content []byte DataPath string // absolute path to .data file ExportedPath string // set by Export(), used by ReimportAfterExport() + encryptor Encryptor + committer Committer } // loadData decrypts a .data file and returns a Data struct with Content populated. // absoluteDataPath must be the full filesystem path to the encrypted .data file. -func loadData(ctx context.Context, absoluteDataPath string, encryptor Encryptor) (*Data, error) { +func loadData(ctx context.Context, absoluteDataPath string, encryptor Encryptor, committer Committer) (*Data, error) { ciphertext, err := os.ReadFile(absoluteDataPath) if err != nil { return nil, fmt.Errorf("reading data file %q: %w", absoluteDataPath, err) @@ -36,8 +38,10 @@ func loadData(ctx context.Context, absoluteDataPath string, encryptor Encryptor) } return &Data{ - Content: plain, - DataPath: absoluteDataPath, + Content: plain, + DataPath: absoluteDataPath, + encryptor: encryptor, + committer: committer, }, nil } @@ -69,21 +73,21 @@ func (d *Data) Export(ctx context.Context, exportDir, destinationFile string) er // ReimportAfterExport reads the (possibly edited) file from ExportedPath back // into Content and then commits it. This is used by the edit workflow: export → // user edits in external editor → reimport. -func (d *Data) ReimportAfterExport(ctx context.Context, encryptor Encryptor, committer Committer) error { +func (d *Data) ReimportAfterExport(ctx context.Context) error { content, err := os.ReadFile(d.ExportedPath) if err != nil { return fmt.Errorf("reading exported file %q: %w", d.ExportedPath, err) } d.Content = content - return d.Commit(ctx, encryptor, committer, true) + return d.Commit(ctx, true) } // Commit encrypts Content and writes it to DataPath, then stages the file with git. // If force is false and the file already exists, the commit is silently skipped // (matching the Ruby CommitFile#commit_content behaviour that avoids overwrites // without explicit force). -func (d *Data) Commit(ctx context.Context, encryptor Encryptor, committer Committer, force bool) error { +func (d *Data) Commit(ctx context.Context, force bool) error { if !force { if _, err := os.Stat(d.DataPath); err == nil { // File already exists; skip without error to preserve existing data. @@ -96,10 +100,10 @@ func (d *Data) Commit(ctx context.Context, encryptor Encryptor, committer Commit return fmt.Errorf("creating data directory for %q: %w", d.DataPath, err) } - if encryptor == nil { + if d.encryptor == nil { return fmt.Errorf("encrypting data for %q: missing encryptor", d.DataPath) } - ciphertext, err := encryptor.Encrypt(d.Content) + ciphertext, err := d.encryptor.Encrypt(d.Content) if err != nil { return fmt.Errorf("encrypting data for %q: %w", d.DataPath, err) } @@ -108,10 +112,10 @@ func (d *Data) Commit(ctx context.Context, encryptor Encryptor, committer Commit return fmt.Errorf("writing data file %q: %w", d.DataPath, err) } - if committer == nil { + if d.committer == nil { return fmt.Errorf("git add data %q: missing committer", d.DataPath) } - if err := committer.Add(ctx, d.DataPath); err != nil { + if err := d.committer.Add(ctx, d.DataPath); err != nil { return fmt.Errorf("git add data %q: %w", d.DataPath, err) } diff --git a/internal/store/data_test.go b/internal/store/data_test.go index 7bffd71..1c05a42 100644 --- a/internal/store/data_test.go +++ b/internal/store/data_test.go @@ -93,7 +93,7 @@ func TestDataCommitAndLoad(t *testing.T) { t.Fatalf("WriteFile: %v", err) } - loaded, err := loadData(ctx, dataPath, c) + loaded, err := loadData(ctx, dataPath, c, nil) if err != nil { t.Fatalf("loadData: %v", err) } @@ -157,7 +157,7 @@ func TestLoadDataMissingFile(t *testing.T) { ctx := context.Background() c := newTestCipher(t) - _, err := loadData(ctx, "/nonexistent/path/to.data", c) + _, err := loadData(ctx, "/nonexistent/path/to.data", c, nil) if err == nil { t.Error("loadData with missing file: expected error, got nil") } @@ -178,7 +178,7 @@ func TestLoadDataCorrupted(t *testing.T) { t.Fatalf("writing bad file: %v", err) } - _, err := loadData(ctx, badPath, c) + _, err := loadData(ctx, badPath, c, nil) if err == nil { t.Error("loadData with corrupted file: expected error, got nil") } @@ -221,16 +221,14 @@ func TestDataCommitSkipsExisting(t *testing.T) { } d := &Data{ - Content: []byte("new content that should NOT overwrite"), - DataPath: dataPath, + Content: []byte("new content that should NOT overwrite"), + DataPath: dataPath, + encryptor: c, } - // Commit with force=false must not overwrite; it also tries git.Add which - // we can't test without a real repo, so we only check the file is untouched. - // Since Commit calls git.Add on success, and that will fail without a repo, - // we skip the git.Add path by checking the file is unchanged after the skip. - // The function prints a warning and returns nil when force=false and file exists. - err := d.Commit(ctx, c, nil, false) // nil git — should not be reached when skipping + // Commit with force=false must not overwrite and should return before + // encrypting or touching git dependencies. + err := d.Commit(ctx, false) if err != nil { t.Errorf("Commit(force=false) with existing file returned error: %v", err) } @@ -250,7 +248,7 @@ func TestDataCommitMissingEncryptor(t *testing.T) { DataPath: filepath.Join(dir, "entry.data"), } - err := d.Commit(ctx, nil, nil, true) + err := d.Commit(ctx, true) if err == nil { t.Fatal("Commit with nil encryptor: expected error, got nil") } @@ -264,11 +262,12 @@ func TestDataCommitMissingCommitter(t *testing.T) { c := newTestCipher(t) dir := t.TempDir() d := &Data{ - Content: []byte("content"), - DataPath: filepath.Join(dir, "entry.data"), + Content: []byte("content"), + DataPath: filepath.Join(dir, "entry.data"), + encryptor: c, } - err := d.Commit(ctx, c, nil, true) + err := d.Commit(ctx, true) if err == nil { t.Fatal("Commit with nil committer: expected error, got nil") } diff --git a/internal/store/store.go b/internal/store/store.go index b92f011..8d251b3 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -194,7 +194,7 @@ func (s *Store) applyAction(ctx context.Context, idx *Index, action Action, acti // ActionPaste, ActionOpen, ActionEdit — require external tools; // delegate to the caller-supplied callback. if actionFn != nil { - d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher) + d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher, s.git) if err != nil { return err } @@ -211,7 +211,7 @@ func (s *Store) actionCat(ctx context.Context, idx *Index) error { fmt.Println("Not displaying/pasting binary data!") return nil } - d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher) + d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher, s.git) if err != nil { return err } @@ -223,7 +223,7 @@ func (s *Store) actionCat(ctx context.Context, idx *Index) error { // When fullPath is true the full description is used as the destination path; // when false only the basename is used (matching Ruby's :export vs :pathexport). func (s *Store) actionExport(ctx context.Context, idx *Index, fullPath bool) error { - d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher) + d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher, s.git) if err != nil { return err } @@ -431,7 +431,7 @@ func (s *Store) Add(ctx context.Context, description, data string) error { idx, dataObj := s.buildPair(description, hash) dataObj.Content = []byte(data) - if err := dataObj.Commit(ctx, s.cipher, s.git, false); err != nil { + if err := dataObj.Commit(ctx, false); err != nil { return fmt.Errorf("committing data for %q: %w", description, err) } if err := idx.CommitIndex(ctx, s.cipher, s.git, false); err != nil { @@ -456,7 +456,7 @@ func (s *Store) Import(ctx context.Context, srcPath, destPath string, force bool idx, dataObj := s.buildPair(destPath, hash) dataObj.Content = content - if err := dataObj.Commit(ctx, s.cipher, s.git, force); err != nil { + if err := dataObj.Commit(ctx, force); err != nil { return fmt.Errorf("committing data for %q: %w", destPath, err) } if err := idx.CommitIndex(ctx, s.cipher, s.git, force); err != nil { @@ -594,7 +594,9 @@ func (s *Store) buildPair(description, hash string) (*Index, *Data) { Hash: hashBase, } dataObj := &Data{ - DataPath: dataPath, + DataPath: dataPath, + encryptor: s.cipher, + committer: s.git, } return idx, dataObj } diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 270fe00..86b7d0d 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -139,7 +139,7 @@ func TestAddAndSearch(t *testing.T) { } dataPath := filepath.Join(cfg.DataDir, idx.DataFile) - d, err := loadData(ctx, dataPath, c) + d, err := loadData(ctx, dataPath, c, nil) if err != nil { t.Fatalf("loadData: %v", err) } @@ -221,7 +221,7 @@ func TestImport(t *testing.T) { } dataPath := filepath.Join(cfg.DataDir, found[0].DataFile) - d, err := loadData(ctx, dataPath, c) + d, err := loadData(ctx, dataPath, c, nil) if err != nil { t.Fatalf("loadData: %v", err) } @@ -259,7 +259,7 @@ func TestExport(t *testing.T) { } dataPath := filepath.Join(cfg.DataDir, idx.DataFile) - d, err := loadData(ctx, dataPath, c) + d, err := loadData(ctx, dataPath, c, nil) if err != nil { t.Fatalf("loadData: %v", err) } @@ -595,7 +595,7 @@ func TestImportRecursive(t *testing.T) { }()) continue } - d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c) + d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, nil) if err != nil { t.Fatalf("loadData for %q: %v", desc, err) } @@ -624,7 +624,7 @@ func TestReimportAfterExport(t *testing.T) { t.Fatalf("WalkIndexes: %v", err) } - d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c) + d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, g) if err != nil { t.Fatalf("loadData: %v", err) } @@ -641,12 +641,12 @@ func TestReimportAfterExport(t *testing.T) { } // Reimport overwrites the encrypted .data with the updated content. - if err := d.ReimportAfterExport(ctx, c, g); err != nil { + if err := d.ReimportAfterExport(ctx); err != nil { t.Fatalf("ReimportAfterExport: %v", err) } // Reload from disk and verify the update was persisted. - reloaded, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c) + reloaded, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, nil) if err != nil { t.Fatalf("loadData after reimport: %v", err) } @@ -1029,7 +1029,7 @@ func TestImportForceOverwrite(t *testing.T) { 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) + d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, nil) if err != nil { t.Fatalf("loadData: %v", err) } -- cgit v1.2.3