v0.23.1 6c872d5

history_repo_test.go RawHistoryFolder

1package sqlite_test
2
3import (
4	"context"
5	"path/filepath"
6	"testing"
7	"time"
8
9	"github.com/bnema/dumber/internal/domain/entity"
10	"github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
11	"github.com/bnema/dumber/internal/logging"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/require"
14)
15
16func historyTestCtx() context.Context {
17	logger := logging.NewFromConfigValues("debug", "console")
18	return logging.WithContext(context.Background(), logger)
19}
20
21func TestHistoryRepository_GetRecentSince(t *testing.T) {
22	ctx := historyTestCtx()
23	dbPath := filepath.Join(t.TempDir(), "dumber.db")
24
25	db, err := sqlite.NewConnection(ctx, dbPath)
26	require.NoError(t, err)
27	t.Cleanup(func() { _ = db.Close() })
28
29	repo := sqlite.NewHistoryRepository(db)
30
31	// Save some history entries
32	entries := []*entity.HistoryEntry{
33		{URL: "https://example.com", Title: "Example"},
34		{URL: "https://github.com", Title: "GitHub"},
35		{URL: "https://google.com", Title: "Google"},
36	}
37
38	for _, e := range entries {
39		require.NoError(t, repo.Save(ctx, e))
40	}
41
42	// Get recent since 30 days (all should be included)
43	results, err := repo.GetRecentSince(ctx, 30)
44	require.NoError(t, err)
45	require.Len(t, results, 3)
46
47	// Results should be sorted by last_visited DESC
48	// Since they were all just inserted, order may vary
49	urls := make(map[string]bool)
50	for _, r := range results {
51		urls[r.URL] = true
52	}
53	assert.True(t, urls["https://example.com"])
54	assert.True(t, urls["https://github.com"])
55	assert.True(t, urls["https://google.com"])
56}
57
58func TestHistoryRepository_GetRecentSince_EmptyResult(t *testing.T) {
59	ctx := historyTestCtx()
60	dbPath := filepath.Join(t.TempDir(), "dumber.db")
61
62	db, err := sqlite.NewConnection(ctx, dbPath)
63	require.NoError(t, err)
64	t.Cleanup(func() { _ = db.Close() })
65
66	repo := sqlite.NewHistoryRepository(db)
67
68	// Get recent with no data
69	results, err := repo.GetRecentSince(ctx, 30)
70	require.NoError(t, err)
71	assert.Empty(t, results)
72}
73
74func TestHistoryRepository_GetMostVisited(t *testing.T) {
75	ctx := historyTestCtx()
76	dbPath := filepath.Join(t.TempDir(), "dumber.db")
77
78	db, err := sqlite.NewConnection(ctx, dbPath)
79	require.NoError(t, err)
80	t.Cleanup(func() { _ = db.Close() })
81
82	repo := sqlite.NewHistoryRepository(db)
83
84	// Save entries with different visit counts
85	entry1 := &entity.HistoryEntry{URL: "https://example.com", Title: "Example"}
86	entry2 := &entity.HistoryEntry{URL: "https://github.com", Title: "GitHub"}
87	entry3 := &entity.HistoryEntry{URL: "https://google.com", Title: "Google"}
88
89	require.NoError(t, repo.Save(ctx, entry1))
90	require.NoError(t, repo.Save(ctx, entry2))
91	require.NoError(t, repo.Save(ctx, entry3))
92
93	// Increment visit counts to create different popularities
94	// github: 5 visits, google: 3 visits, example: 1 visit
95	for i := 0; i < 4; i++ {
96		require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com"))
97	}
98	for i := 0; i < 2; i++ {
99		require.NoError(t, repo.IncrementVisitCount(ctx, "https://google.com"))
100	}
101
102	// Get most visited
103	results, err := repo.GetMostVisited(ctx, 30)
104	require.NoError(t, err)
105	require.Len(t, results, 3)
106
107	// First result should be github (5 visits)
108	assert.Equal(t, "https://github.com", results[0].URL)
109	assert.Equal(t, int64(5), results[0].VisitCount)
110
111	// Second should be google (3 visits)
112	assert.Equal(t, "https://google.com", results[1].URL)
113	assert.Equal(t, int64(3), results[1].VisitCount)
114
115	// Third should be example (1 visit)
116	assert.Equal(t, "https://example.com", results[2].URL)
117	assert.Equal(t, int64(1), results[2].VisitCount)
118}
119
120func TestHistoryRepository_GetMostVisited_EmptyResult(t *testing.T) {
121	ctx := historyTestCtx()
122	dbPath := filepath.Join(t.TempDir(), "dumber.db")
123
124	db, err := sqlite.NewConnection(ctx, dbPath)
125	require.NoError(t, err)
126	t.Cleanup(func() { _ = db.Close() })
127
128	repo := sqlite.NewHistoryRepository(db)
129
130	// Get most visited with no data
131	results, err := repo.GetMostVisited(ctx, 30)
132	require.NoError(t, err)
133	assert.Empty(t, results)
134}
135
136func TestHistoryRepository_GetRecentSince_RespectsTimeFilter(t *testing.T) {
137	ctx := historyTestCtx()
138	dbPath := filepath.Join(t.TempDir(), "dumber.db")
139
140	db, err := sqlite.NewConnection(ctx, dbPath)
141	require.NoError(t, err)
142	t.Cleanup(func() { _ = db.Close() })
143
144	repo := sqlite.NewHistoryRepository(db)
145
146	// Save an entry
147	entry := &entity.HistoryEntry{URL: "https://example.com", Title: "Example"}
148	require.NoError(t, repo.Save(ctx, entry))
149
150	// Query with 1 day filter - should include recently added entry
151	results, err := repo.GetRecentSince(ctx, 1)
152	require.NoError(t, err)
153	require.Len(t, results, 1)
154
155	// Query with very large days filter - should still work
156	results, err = repo.GetRecentSince(ctx, 365)
157	require.NoError(t, err)
158	require.Len(t, results, 1)
159}
160
161func TestHistoryRepository_CRUD(t *testing.T) {
162	ctx := historyTestCtx()
163	dbPath := filepath.Join(t.TempDir(), "dumber.db")
164
165	db, err := sqlite.NewConnection(ctx, dbPath)
166	require.NoError(t, err)
167	t.Cleanup(func() { _ = db.Close() })
168
169	repo := sqlite.NewHistoryRepository(db)
170
171	// Create
172	entry := &entity.HistoryEntry{URL: "https://example.com", Title: "Example"}
173	require.NoError(t, repo.Save(ctx, entry))
174
175	// Read
176	found, err := repo.FindByURL(ctx, "https://example.com")
177	require.NoError(t, err)
178	require.NotNil(t, found)
179	assert.Equal(t, "https://example.com", found.URL)
180	assert.Equal(t, "Example", found.Title)
181	assert.Equal(t, int64(1), found.VisitCount)
182
183	// Update (save again increments visit count)
184	require.NoError(t, repo.Save(ctx, entry))
185	found2, err := repo.FindByURL(ctx, "https://example.com")
186	require.NoError(t, err)
187	assert.Equal(t, int64(2), found2.VisitCount)
188
189	// Get recent
190	recent, err := repo.GetRecent(ctx, 10, 0)
191	require.NoError(t, err)
192	require.Len(t, recent, 1)
193
194	// Delete
195	require.NoError(t, repo.Delete(ctx, found.ID))
196
197	// Verify deleted
198	deleted, err := repo.FindByURL(ctx, "https://example.com")
199	require.NoError(t, err)
200	assert.Nil(t, deleted)
201}
202
203// Ensure the test uses an isolated timestamp for time-sensitive queries
204func TestHistoryRepository_TimeFilterIntegrity(t *testing.T) {
205	ctx := historyTestCtx()
206	dbPath := filepath.Join(t.TempDir(), "dumber.db")
207
208	db, err := sqlite.NewConnection(ctx, dbPath)
209	require.NoError(t, err)
210	t.Cleanup(func() { _ = db.Close() })
211
212	repo := sqlite.NewHistoryRepository(db)
213
214	// Save entry
215	entry := &entity.HistoryEntry{URL: "https://test.com", Title: "Test"}
216	require.NoError(t, repo.Save(ctx, entry))
217
218	// Verify entry was saved with recent timestamp
219	found, err := repo.FindByURL(ctx, "https://test.com")
220	require.NoError(t, err)
221	require.NotNil(t, found)
222
223	// LastVisited should be within the last minute
224	timeDiff := time.Since(found.LastVisited)
225	assert.Less(t, timeDiff, time.Minute, "LastVisited should be recent, got %v ago", timeDiff)
226}
227
228func TestHistoryRepository_GetRecentSince_RejectsNonPositiveDays(t *testing.T) {
229	ctx := historyTestCtx()
230	dbPath := filepath.Join(t.TempDir(), "dumber.db")
231
232	db, err := sqlite.NewConnection(ctx, dbPath)
233	require.NoError(t, err)
234	t.Cleanup(func() { _ = db.Close() })
235
236	repo := sqlite.NewHistoryRepository(db)
237
238	// days = 0 should return error
239	_, err = repo.GetRecentSince(ctx, 0)
240	require.Error(t, err)
241	assert.Contains(t, err.Error(), "days must be positive")
242
243	// days < 0 should return error
244	_, err = repo.GetRecentSince(ctx, -5)
245	require.Error(t, err)
246	assert.Contains(t, err.Error(), "days must be positive")
247}
248
249func TestHistoryRepository_GetMostVisited_RejectsNonPositiveDays(t *testing.T) {
250	ctx := historyTestCtx()
251	dbPath := filepath.Join(t.TempDir(), "dumber.db")
252
253	db, err := sqlite.NewConnection(ctx, dbPath)
254	require.NoError(t, err)
255	t.Cleanup(func() { _ = db.Close() })
256
257	repo := sqlite.NewHistoryRepository(db)
258
259	// days = 0 should return error
260	_, err = repo.GetMostVisited(ctx, 0)
261	require.Error(t, err)
262	assert.Contains(t, err.Error(), "days must be positive")
263
264	// days < 0 should return error
265	_, err = repo.GetMostVisited(ctx, -5)
266	require.Error(t, err)
267	assert.Contains(t, err.Error(), "days must be positive")
268}
269
270func TestHistoryRepository_GetAllRecentHistory(t *testing.T) {
271	ctx := historyTestCtx()
272	dbPath := filepath.Join(t.TempDir(), "dumber.db")
273
274	db, err := sqlite.NewConnection(ctx, dbPath)
275	require.NoError(t, err)
276	t.Cleanup(func() { _ = db.Close() })
277
278	repo := sqlite.NewHistoryRepository(db)
279
280	// Save some entries
281	entries := []*entity.HistoryEntry{
282		{URL: "https://example.com", Title: "Example"},
283		{URL: "https://github.com", Title: "GitHub"},
284	}
285	for _, e := range entries {
286		require.NoError(t, repo.Save(ctx, e))
287	}
288
289	// Get all recent history
290	results, err := repo.GetAllRecentHistory(ctx)
291	require.NoError(t, err)
292	require.Len(t, results, 2)
293}
294
295func TestHistoryRepository_GetAllMostVisited(t *testing.T) {
296	ctx := historyTestCtx()
297	dbPath := filepath.Join(t.TempDir(), "dumber.db")
298
299	db, err := sqlite.NewConnection(ctx, dbPath)
300	require.NoError(t, err)
301	t.Cleanup(func() { _ = db.Close() })
302
303	repo := sqlite.NewHistoryRepository(db)
304
305	// Save entries with different visit counts
306	entry1 := &entity.HistoryEntry{URL: "https://example.com", Title: "Example"}
307	entry2 := &entity.HistoryEntry{URL: "https://github.com", Title: "GitHub"}
308
309	require.NoError(t, repo.Save(ctx, entry1))
310	require.NoError(t, repo.Save(ctx, entry2))
311
312	// Increment github visits
313	for i := 0; i < 3; i++ {
314		require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com"))
315	}
316
317	// Get all most visited
318	results, err := repo.GetAllMostVisited(ctx)
319	require.NoError(t, err)
320	require.Len(t, results, 2)
321
322	// First should be github (4 visits)
323	assert.Equal(t, "https://github.com", results[0].URL)
324	assert.Equal(t, int64(4), results[0].VisitCount)
325}

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 8

Tags 42

↑↓ moveenter openesc close