v2.8.0 6aced72

store_test.go RawHistoryFolder

1package domainsecrets
2
3import (
4	"errors"
5	"os"
6	"path/filepath"
7	"testing"
8
9	"github.com/bnema/zerowrap"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12
13	"github.com/bnema/gordon/internal/domain"
14)
15
16func testLogger() zerowrap.Logger {
17	return zerowrap.Default()
18}
19
20func TestFileStore_PathTraversal(t *testing.T) {
21	tmpDir, err := os.MkdirTemp("", "domainsecrets-test-*")
22	require.NoError(t, err)
23	defer os.RemoveAll(tmpDir)
24
25	store, err := NewFileStore(tmpDir, testLogger())
26	require.NoError(t, err)
27
28	tests := []struct {
29		name    string
30		domain  string
31		wantErr bool
32	}{
33		// Valid domains
34		{"simple domain", "example.com", false},
35		{"subdomain", "sub.example.com", false},
36		{"domain with port", "example.com:8080", false},
37		{"domain with path", "example.com/path", false},
38		{"hyphenated domain", "my-app.example.com", false},
39		{"single char", "a", false},
40
41		// Path traversal attempts - should fail
42		{"path traversal with dots", "../../../etc/passwd", true},
43		{"path traversal in middle", "example.com/../../../etc/passwd", true},
44		{"double dots only", "..", true},
45		{"path traversal at end", "example.com/..", true},
46
47		// Invalid format - should fail
48		{"empty domain", "", true},
49		{"starts with dot", ".example.com", true},
50		{"ends with dot", "example.com.", true},
51		{"special chars semicolon", "example;rm -rf /", true},
52		{"null byte", "example\x00.com", true},
53		{"newline", "example\n.com", true},
54	}
55
56	for _, tt := range tests {
57		t.Run(tt.name, func(t *testing.T) {
58			// Test ListKeys
59			_, err := store.ListKeys(tt.domain)
60			if tt.wantErr {
61				require.Error(t, err)
62				assert.True(t, errors.Is(err, domain.ErrPathTraversal), "expected ErrPathTraversal, got: %v", err)
63			} else {
64				// No error, or file not found (which is fine)
65				if err != nil {
66					assert.False(t, errors.Is(err, domain.ErrPathTraversal), "unexpected ErrPathTraversal")
67				}
68			}
69
70			// Test Set
71			err = store.Set(tt.domain, map[string]string{"KEY": "value"})
72			if tt.wantErr {
73				require.Error(t, err)
74				assert.True(t, errors.Is(err, domain.ErrPathTraversal), "expected ErrPathTraversal, got: %v", err)
75			} else {
76				assert.NoError(t, err)
77			}
78
79			// Test GetAll
80			_, err = store.GetAll(tt.domain)
81			if tt.wantErr {
82				require.Error(t, err)
83				assert.True(t, errors.Is(err, domain.ErrPathTraversal), "expected ErrPathTraversal, got: %v", err)
84			}
85
86			// Test Delete
87			err = store.Delete(tt.domain, "KEY")
88			if tt.wantErr {
89				require.Error(t, err)
90				assert.True(t, errors.Is(err, domain.ErrPathTraversal), "expected ErrPathTraversal, got: %v", err)
91			}
92		})
93	}
94}
95
96func TestFileStore_PathContainment(t *testing.T) {
97	tmpDir, err := os.MkdirTemp("", "domainsecrets-test-*")
98	require.NoError(t, err)
99	defer os.RemoveAll(tmpDir)
100
101	store, err := NewFileStore(tmpDir, testLogger())
102	require.NoError(t, err)
103
104	// Valid domain - file should be created inside tmpDir
105	err = store.Set("example.com", map[string]string{"KEY": "value"})
106	require.NoError(t, err)
107
108	// Verify file exists inside tmpDir
109	expectedFile := filepath.Join(tmpDir, "example_com.env")
110	_, err = os.Stat(expectedFile)
111	assert.NoError(t, err, "expected env file to exist at %s", expectedFile)
112
113	// Verify no file was created outside tmpDir
114	entries, err := filepath.Glob(tmpDir + "/../*")
115	require.NoError(t, err)
116	for _, entry := range entries {
117		if filepath.Base(entry) != filepath.Base(tmpDir) {
118			// Check it's not our test file escaped
119			assert.NotContains(t, entry, ".env", "unexpected .env file outside tmpDir: %s", entry)
120		}
121	}
122}
123
124func TestFileStore_ValidDomainOperations(t *testing.T) {
125	tmpDir, err := os.MkdirTemp("", "domainsecrets-test-*")
126	require.NoError(t, err)
127	defer os.RemoveAll(tmpDir)
128
129	store, err := NewFileStore(tmpDir, testLogger())
130	require.NoError(t, err)
131
132	domain := "test.example.com"
133
134	// Set secrets
135	err = store.Set(domain, map[string]string{
136		"DB_HOST":     "localhost",
137		"DB_PASSWORD": "secret123",
138	})
139	require.NoError(t, err)
140
141	// Get all secrets
142	secrets, err := store.GetAll(domain)
143	require.NoError(t, err)
144	assert.Equal(t, "localhost", secrets["DB_HOST"])
145	assert.Equal(t, "secret123", secrets["DB_PASSWORD"])
146
147	// List keys
148	keys, err := store.ListKeys(domain)
149	require.NoError(t, err)
150	assert.Len(t, keys, 2)
151	assert.Contains(t, keys, "DB_HOST")
152	assert.Contains(t, keys, "DB_PASSWORD")
153
154	// Delete a key
155	err = store.Delete(domain, "DB_PASSWORD")
156	require.NoError(t, err)
157
158	// Verify deletion
159	secrets, err = store.GetAll(domain)
160	require.NoError(t, err)
161	assert.Len(t, secrets, 1)
162	assert.Equal(t, "localhost", secrets["DB_HOST"])
163	_, exists := secrets["DB_PASSWORD"]
164	assert.False(t, exists)
165}
166
167func TestGetEnvFilePath(t *testing.T) {
168	tmpDir, err := os.MkdirTemp("", "domainsecrets-test-*")
169	require.NoError(t, err)
170	defer os.RemoveAll(tmpDir)
171
172	store, err := NewFileStore(tmpDir, testLogger())
173	require.NoError(t, err)
174
175	tests := []struct {
176		domain       string
177		wantEmpty    bool
178		wantFilename string
179	}{
180		{"example.com", false, "example_com.env"},
181		{"sub.example.com", false, "sub_example_com.env"},
182		{"example.com:8080", false, "example_com_8080.env"},
183		{"example.com/path", false, "example_com_path.env"},
184		{"../evil", true, ""},
185		{"", true, ""},
186	}
187
188	for _, tt := range tests {
189		t.Run(tt.domain, func(t *testing.T) {
190			path := store.getEnvFilePath(tt.domain)
191
192			if tt.wantEmpty {
193				assert.Empty(t, path)
194			} else {
195				assert.NotEmpty(t, path)
196				assert.Equal(t, filepath.Join(tmpDir, tt.wantFilename), path)
197			}
198		})
199	}
200}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close