1package sqlite_test
2
3import (
4 "context"
5 "path/filepath"
6 "testing"
7 "time"
8
9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/bnema/dumber/internal/domain/favicon"
11 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
12 "github.com/bnema/dumber/internal/logging"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15)
16
17func faviconTestCtx() context.Context {
18 logger := logging.NewFromConfigValues("debug", "console")
19 return logging.WithContext(context.Background(), logger)
20}
21
22func newFaviconRepo(t *testing.T) (context.Context, port.FaviconRepository) {
23 t.Helper()
24 ctx := faviconTestCtx()
25 db, err := sqlite.NewConnection(ctx, filepath.Join(t.TempDir(), "dumber.db"))
26 require.NoError(t, err)
27 t.Cleanup(func() { require.NoError(t, db.Close()) })
28 return ctx, sqlite.NewFaviconRepository(db)
29}
30
31func TestFaviconRepositoryUpsertAndGet(t *testing.T) {
32 ctx, repo := newFaviconRepo(t)
33 updatedAt := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
34 checkedAt := updatedAt.Add(time.Hour)
35
36 meta := favicon.Metadata{
37 Key: favicon.Key("example.com"),
38 SourceURL: "https://example.com/favicon.ico",
39 PageURL: "https://example.com/page",
40 Source: favicon.SourceEngine,
41 ContentHash: "sha256:first",
42 ContentType: "image/png",
43 UpdatedAt: updatedAt,
44 LastCheckedAt: checkedAt,
45 }
46 require.NoError(t, repo.Upsert(ctx, meta))
47
48 got, err := repo.Get(ctx, favicon.Key("example.com"))
49 require.NoError(t, err)
50 require.NotNil(t, got)
51 assert.Equal(t, meta.Key, got.Key)
52 assert.Equal(t, meta.SourceURL, got.SourceURL)
53 assert.Equal(t, meta.PageURL, got.PageURL)
54 assert.Equal(t, meta.Source, got.Source)
55 assert.Equal(t, meta.ContentHash, got.ContentHash)
56 assert.Equal(t, meta.ContentType, got.ContentType)
57 assert.True(t, meta.UpdatedAt.Equal(got.UpdatedAt), "updated_at = %s", got.UpdatedAt)
58 assert.True(t, meta.LastCheckedAt.Equal(got.LastCheckedAt), "last_checked_at = %s", got.LastCheckedAt)
59
60 replacement := meta
61 replacement.SourceURL = "https://cdn.example.com/icon.png"
62 replacement.Source = favicon.SourcePageDiscovery
63 replacement.ContentHash = "sha256:second"
64 replacement.UpdatedAt = updatedAt.Add(2 * time.Hour)
65 replacement.LastCheckedAt = checkedAt.Add(2 * time.Hour)
66 require.NoError(t, repo.Upsert(ctx, replacement))
67
68 got, err = repo.Get(ctx, favicon.Key("example.com"))
69 require.NoError(t, err)
70 require.NotNil(t, got)
71 assert.Equal(t, replacement.SourceURL, got.SourceURL)
72 assert.Equal(t, replacement.Source, got.Source)
73 assert.Equal(t, replacement.ContentHash, got.ContentHash)
74 assert.True(t, replacement.UpdatedAt.Equal(got.UpdatedAt))
75 assert.True(t, replacement.LastCheckedAt.Equal(got.LastCheckedAt))
76}
77
78func TestFaviconRepositoryGetNotFoundReturnsNil(t *testing.T) {
79 ctx, repo := newFaviconRepo(t)
80
81 got, err := repo.Get(ctx, favicon.Key("missing.example"))
82 require.NoError(t, err)
83 assert.Nil(t, got)
84}
85
86func TestFaviconRepositoryFindFirstUsesCallerCandidateOrder(t *testing.T) {
87 ctx, repo := newFaviconRepo(t)
88 now := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
89 require.NoError(t, repo.Upsert(ctx, favicon.Metadata{Key: favicon.Key("example.com"), Source: favicon.SourceEngine, ContentHash: "parent", UpdatedAt: now, LastCheckedAt: now}))
90 require.NoError(t, repo.Upsert(ctx, favicon.Metadata{Key: favicon.Key("docs.example.com"), Source: favicon.SourceEngine, ContentHash: "child", UpdatedAt: now, LastCheckedAt: now}))
91
92 got, err := repo.FindFirst(ctx, []favicon.Key{favicon.Key("missing.example.com"), favicon.Key("example.com"), favicon.Key("docs.example.com")})
93 require.NoError(t, err)
94 require.NotNil(t, got)
95 assert.Equal(t, favicon.Key("example.com"), got.Key)
96 assert.Equal(t, "parent", got.ContentHash)
97
98 got, err = repo.FindFirst(ctx, nil)
99 require.NoError(t, err)
100 assert.Nil(t, got)
101}
102
103func TestFaviconRepositoryUpdateLastChecked(t *testing.T) {
104 ctx, repo := newFaviconRepo(t)
105 updatedAt := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
106 checkedAt := updatedAt.Add(24 * time.Hour)
107 meta := favicon.Metadata{Key: favicon.Key("example.com"), Source: favicon.SourceEngine, ContentHash: "old", UpdatedAt: updatedAt, LastCheckedAt: updatedAt}
108 require.NoError(t, repo.Upsert(ctx, meta))
109
110 require.NoError(t, repo.UpdateLastChecked(ctx, favicon.Key("example.com"), "new", checkedAt))
111
112 got, err := repo.Get(ctx, favicon.Key("example.com"))
113 require.NoError(t, err)
114 require.NotNil(t, got)
115 assert.Equal(t, "new", got.ContentHash)
116 assert.True(t, updatedAt.Equal(got.UpdatedAt), "updated_at should not change")
117 assert.True(t, checkedAt.Equal(got.LastCheckedAt))
118}
119
120func TestFaviconRepositoryDelete(t *testing.T) {
121 ctx, repo := newFaviconRepo(t)
122 now := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
123 require.NoError(t, repo.Upsert(ctx, favicon.Metadata{Key: favicon.Key("example.com"), Source: favicon.SourceEngine, ContentHash: "hash", UpdatedAt: now, LastCheckedAt: now}))
124
125 require.NoError(t, repo.Delete(ctx, favicon.Key("example.com")))
126
127 got, err := repo.Get(ctx, favicon.Key("example.com"))
128 require.NoError(t, err)
129 assert.Nil(t, got)
130}