1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "testing"
8
9 "github.com/bnema/dumber/internal/domain/entity"
10 _ "github.com/bnema/purego-sqlite/driver"
11)
12
13func openFavoriteRepoTestDB(t *testing.T) *sql.DB {
14 t.Helper()
15 db, err := sql.Open("sqlite3", ":memory:")
16 if err != nil {
17 t.Fatalf("open sqlite: %v", err)
18 }
19 db.SetMaxOpenConns(1)
20 t.Cleanup(func() { _ = db.Close() })
21 if err := RunMigrations(context.Background(), db); err != nil {
22 t.Fatalf("run migrations: %v", err)
23 }
24 return db
25}
26
27func seedFavoriteWithTags(t *testing.T, db *sql.DB) (entity.FavoriteID, entity.TagID) {
28 t.Helper()
29 mustExec(t, db, `INSERT INTO favorites(id, url, title, shortcut_key, position) VALUES (1, 'https://one.example', 'One', 1, 1), (2, 'https://two.example', 'Two', NULL, 2)`)
30 mustExec(t, db, `INSERT INTO favorite_tags(id, name, color) VALUES (1, 'research', '#111111'), (2, 'engines', '#222222')`)
31 mustExec(t, db, `INSERT INTO favorite_tag_assignments(favorite_id, tag_id) VALUES (1, 1), (1, 2), (2, 1)`)
32 return 1, 1
33}
34
35func TestFavoriteRepositoryGetAllHydratesTags(t *testing.T) {
36 db := openFavoriteRepoTestDB(t)
37 seedFavoriteWithTags(t, db)
38 repo := NewFavoriteRepository(db)
39
40 favorites, err := repo.GetAll(context.Background())
41 if err != nil {
42 t.Fatalf("GetAll: %v", err)
43 }
44 if len(favorites) != 2 {
45 t.Fatalf("expected 2 favorites, got %d", len(favorites))
46 }
47 assertTagNames(t, favorites[0], "engines", "research")
48 assertTagNames(t, favorites[1], "research")
49}
50
51func TestFavoriteRepositoryFindByIDHydratesTags(t *testing.T) {
52 db := openFavoriteRepoTestDB(t)
53 favID, _ := seedFavoriteWithTags(t, db)
54 repo := NewFavoriteRepository(db)
55
56 fav, err := repo.FindByID(context.Background(), favID)
57 if err != nil {
58 t.Fatalf("FindByID: %v", err)
59 }
60 assertTagNames(t, fav, "engines", "research")
61}
62
63func TestFavoriteRepositoryFindByURLHydratesTags(t *testing.T) {
64 db := openFavoriteRepoTestDB(t)
65 seedFavoriteWithTags(t, db)
66 repo := NewFavoriteRepository(db)
67
68 fav, err := repo.FindByURL(context.Background(), "https://one.example")
69 if err != nil {
70 t.Fatalf("FindByURL: %v", err)
71 }
72 assertTagNames(t, fav, "engines", "research")
73}
74
75func TestFavoriteRepositoryGetByTagHydratesTags(t *testing.T) {
76 db := openFavoriteRepoTestDB(t)
77 _, tagID := seedFavoriteWithTags(t, db)
78 repo := NewFavoriteRepository(db)
79
80 favorites, err := repo.GetByTag(context.Background(), tagID)
81 if err != nil {
82 t.Fatalf("GetByTag: %v", err)
83 }
84 if len(favorites) != 2 {
85 t.Fatalf("expected 2 tagged favorites, got %d", len(favorites))
86 }
87 assertTagNames(t, favorites[0], "engines", "research")
88 assertTagNames(t, favorites[1], "research")
89}
90
91func TestFavoriteRepositoryGetByShortcutHydratesTags(t *testing.T) {
92 db := openFavoriteRepoTestDB(t)
93 seedFavoriteWithTags(t, db)
94 repo := NewFavoriteRepository(db)
95
96 fav, err := repo.GetByShortcut(context.Background(), 1)
97 if err != nil {
98 t.Fatalf("GetByShortcut: %v", err)
99 }
100 assertTagNames(t, fav, "engines", "research")
101}
102
103func TestFavoriteRepositoryNoTagsHydratesEmptyTags(t *testing.T) {
104 db := openFavoriteRepoTestDB(t)
105 mustExec(t, db, `INSERT INTO favorites(id, url, title, position) VALUES (1, 'https://none.example', 'None', 1)`)
106 repo := NewFavoriteRepository(db)
107
108 fav, err := repo.FindByID(context.Background(), 1)
109 if err != nil {
110 t.Fatalf("FindByID: %v", err)
111 }
112 if fav == nil {
113 t.Fatal("expected favorite")
114 }
115 if fav.Tags == nil || len(fav.Tags) != 0 {
116 t.Fatalf("expected non-nil empty Tags, got %#v", fav.Tags)
117 }
118}
119
120func TestFavoriteRepositorySaveUpdatesShortcutKey(t *testing.T) {
121 db := openFavoriteRepoTestDB(t)
122 mustExec(t, db, `INSERT INTO favorites(id, url, title, position) VALUES (1, 'https://shortcut.example', 'Shortcut', 1)`)
123 repo := NewFavoriteRepository(db)
124
125 fav, err := repo.FindByID(context.Background(), 1)
126 if err != nil {
127 t.Fatalf("FindByID: %v", err)
128 }
129 key := 5
130 fav.ShortcutKey = &key
131 err = repo.Save(context.Background(), fav)
132 if err != nil {
133 t.Fatalf("Save: %v", err)
134 }
135
136 updated, err := repo.GetByShortcut(context.Background(), 5)
137 if err != nil {
138 t.Fatalf("GetByShortcut: %v", err)
139 }
140 if updated == nil || updated.ID != fav.ID {
141 t.Fatalf("expected favorite %d by shortcut, got %#v", fav.ID, updated)
142 }
143
144 updated.ShortcutKey = nil
145 err = repo.Save(context.Background(), updated)
146 if err != nil {
147 t.Fatalf("Save clear shortcut: %v", err)
148 }
149 cleared, err := repo.FindByID(context.Background(), updated.ID)
150 if err != nil {
151 t.Fatalf("FindByID after clear: %v", err)
152 }
153 if cleared.ShortcutKey != nil {
154 t.Fatalf("expected shortcut cleared, got %d", *cleared.ShortcutKey)
155 }
156}
157
158func TestFavoriteRepositoryGetAllHydratesTagsInBatches(t *testing.T) {
159 db := openFavoriteRepoTestDB(t)
160 mustExec(t, db, `INSERT INTO favorite_tags(id, name, color) VALUES (1, 'batched', '#111111')`)
161 for i := int64(1); i <= favoriteTagHydrationBatchSize+5; i++ {
162 _, err := db.Exec(
163 `INSERT INTO favorites(id, url, title, position) VALUES (?, ?, ?, ?)`,
164 i,
165 fmt.Sprintf("https://batch.example/%d", i),
166 "Batch",
167 i,
168 )
169 if err != nil {
170 t.Fatalf("insert favorite %d: %v", i, err)
171 }
172 _, err = db.Exec(`INSERT INTO favorite_tag_assignments(favorite_id, tag_id) VALUES (?, 1)`, i)
173 if err != nil {
174 t.Fatalf("insert assignment %d: %v", i, err)
175 }
176 }
177 repo := NewFavoriteRepository(db)
178
179 favorites, err := repo.GetAll(context.Background())
180 if err != nil {
181 t.Fatalf("GetAll: %v", err)
182 }
183 if len(favorites) != favoriteTagHydrationBatchSize+5 {
184 t.Fatalf("expected %d favorites, got %d", favoriteTagHydrationBatchSize+5, len(favorites))
185 }
186 assertTagNames(t, favorites[0], "batched")
187 assertTagNames(t, favorites[len(favorites)-1], "batched")
188}
189
190func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) {
191 t.Helper()
192 if fav == nil {
193 t.Fatal("favorite is nil")
194 }
195 if len(fav.Tags) != len(names) {
196 t.Fatalf("expected %d tags, got %d (%#v)", len(names), len(fav.Tags), fav.Tags)
197 }
198 for i, name := range names {
199 if fav.Tags[i].Name != name {
200 t.Fatalf("tag %d: expected %q, got %q", i, name, fav.Tags[i].Name)
201 }
202 }
203}