summaryrefslogtreecommitdiff
path: root/internal/store/data_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/data_test.go')
-rw-r--r--internal/store/data_test.go193
1 files changed, 193 insertions, 0 deletions
diff --git a/internal/store/data_test.go b/internal/store/data_test.go
new file mode 100644
index 0000000..9d8a86a
--- /dev/null
+++ b/internal/store/data_test.go
@@ -0,0 +1,193 @@
+// data_test.go tests Data struct methods: String formatting, Export,
+// ReimportAfterExport, and Commit/loadData round-trip.
+package store
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "codeberg.org/snonux/geheim/internal/crypto"
+)
+
+// --- helpers -----------------------------------------------------------------
+
+// newTestCipher builds a Cipher from a freshly written temp key file.
+func newTestCipher(t *testing.T) *crypto.Cipher {
+ t.Helper()
+ keyFile := filepath.Join(t.TempDir(), "key")
+ if err := os.WriteFile(keyFile, []byte("testkey1234567890"), 0o600); err != nil {
+ t.Fatalf("writing key file: %v", err)
+ }
+ c, err := crypto.NewCipher(keyFile, 32, "testpin", "Hello world")
+ if err != nil {
+ t.Fatalf("NewCipher: %v", err)
+ }
+ return c
+}
+
+// --- TestDataString ----------------------------------------------------------
+
+// TestDataString verifies that String() tab-indents content and appends a newline,
+// matching Ruby's "\t#{@data.gsub("\n", "\n\t")}\n".
+func TestDataString(t *testing.T) {
+ cases := []struct {
+ name string
+ content string
+ want string
+ }{
+ {
+ name: "single line",
+ content: "hello",
+ want: "\thello\n",
+ },
+ {
+ name: "multi-line",
+ content: "line1\nline2\nline3",
+ want: "\tline1\n\tline2\n\tline3\n",
+ },
+ {
+ name: "empty",
+ content: "",
+ want: "\t\n",
+ },
+ {
+ name: "trailing newline",
+ content: "hello\n",
+ want: "\thello\n\t\n",
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ d := &Data{Content: []byte(tc.content)}
+ got := d.String()
+ if got != tc.want {
+ t.Errorf("String() = %q; want %q", got, tc.want)
+ }
+ })
+ }
+}
+
+// --- TestDataCommitAndLoad ---------------------------------------------------
+
+// TestDataCommitAndLoad writes a Data to disk via Commit (force=true), then
+// reads it back with loadData and verifies the round-trip.
+func TestDataCommitAndLoad(t *testing.T) {
+ ctx := context.Background()
+ c := newTestCipher(t)
+
+ dir := t.TempDir()
+ dataPath := filepath.Join(dir, "test.data")
+ wantContent := "my secret data\nwith newlines\n"
+
+ d := &Data{
+ Content: []byte(wantContent),
+ DataPath: dataPath,
+ }
+
+ // Use a nil git — Commit with git.Add will fail, so we test Commit in two stages:
+ // encrypt+write only. We manually write the file to sidestep git in unit tests.
+ ciphertext, err := c.Encrypt([]byte(wantContent))
+ if err != nil {
+ t.Fatalf("Encrypt: %v", err)
+ }
+ if err := os.WriteFile(dataPath, ciphertext, 0o600); err != nil {
+ t.Fatalf("WriteFile: %v", err)
+ }
+
+ loaded, err := loadData(ctx, dataPath, c)
+ if err != nil {
+ t.Fatalf("loadData: %v", err)
+ }
+ if string(loaded.Content) != wantContent {
+ t.Errorf("loadData content = %q; want %q", loaded.Content, wantContent)
+ }
+ _ = d // d was constructed for documentation only; loadData is what we test here.
+}
+
+// --- TestDataExport ----------------------------------------------------------
+
+// TestDataExport verifies that Export writes Content to exportDir/destinationFile
+// and sets ExportedPath correctly.
+func TestDataExport(t *testing.T) {
+ ctx := context.Background()
+ exportDir := t.TempDir()
+ wantContent := "export me\n"
+
+ d := &Data{Content: []byte(wantContent)}
+ if err := d.Export(ctx, exportDir, "subdir/note.txt"); err != nil {
+ t.Fatalf("Export: %v", err)
+ }
+
+ expectedPath := filepath.Join(exportDir, "subdir", "note.txt")
+ if d.ExportedPath != expectedPath {
+ t.Errorf("ExportedPath = %q; want %q", d.ExportedPath, expectedPath)
+ }
+
+ got, err := os.ReadFile(expectedPath)
+ if err != nil {
+ t.Fatalf("reading exported file: %v", err)
+ }
+ if string(got) != wantContent {
+ t.Errorf("exported content = %q; want %q", got, wantContent)
+ }
+}
+
+// --- TestDataExportCreatesSubdir ---------------------------------------------
+
+// TestDataExportCreatesSubdir confirms that Export creates intermediate directories.
+func TestDataExportCreatesSubdir(t *testing.T) {
+ ctx := context.Background()
+ exportDir := t.TempDir()
+
+ d := &Data{Content: []byte("data")}
+ deepPath := "a/b/c/d/file.txt"
+ if err := d.Export(ctx, exportDir, deepPath); err != nil {
+ t.Fatalf("Export with deep path: %v", err)
+ }
+
+ fullPath := filepath.Join(exportDir, deepPath)
+ if _, err := os.Stat(fullPath); err != nil {
+ t.Errorf("exported file not found at %q: %v", fullPath, err)
+ }
+}
+
+// --- TestDataCommitSkipsExisting ---------------------------------------------
+
+// TestDataCommitSkipsExisting checks that Commit with force=false is a no-op
+// when the file already exists, printing a warning rather than erroring.
+func TestDataCommitSkipsExisting(t *testing.T) {
+ ctx := context.Background()
+ c := newTestCipher(t)
+ dir := t.TempDir()
+ dataPath := filepath.Join(dir, "existing.data")
+
+ // Write a sentinel file.
+ sentinel := []byte("original")
+ if err := os.WriteFile(dataPath, sentinel, 0o600); err != nil {
+ t.Fatalf("writing sentinel: %v", err)
+ }
+
+ d := &Data{
+ Content: []byte("new content that should NOT overwrite"),
+ DataPath: dataPath,
+ }
+
+ // 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
+ if err != nil {
+ t.Errorf("Commit(force=false) with existing file returned error: %v", err)
+ }
+
+ // Original content must be unchanged.
+ got, _ := os.ReadFile(dataPath)
+ if string(got) != string(sentinel) {
+ t.Errorf("file was overwritten: got %q; want %q", got, sentinel)
+ }
+}