1package usecase_test
2
3import (
4 "errors"
5 "testing"
6
7 "github.com/bnema/dumber/internal/application/usecase"
8 "github.com/bnema/dumber/internal/domain/entity"
9 repomocks "github.com/bnema/dumber/internal/domain/repository/mocks"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/mock"
12 "github.com/stretchr/testify/require"
13)
14
15func TestManageFavoritesUseCase_GetAllURLs_ReturnsURLSet(t *testing.T) {
16 ctx := testContext()
17
18 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
19 folderRepo := repomocks.NewMockFolderRepository(t)
20 tagRepo := repomocks.NewMockTagRepository(t)
21
22 favorites := []*entity.Favorite{
23 {ID: 1, URL: "https://example.com"},
24 {ID: 2, URL: "https://github.com"},
25 {ID: 3, URL: "https://google.com"},
26 }
27
28 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
29
30 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
31
32 urls, err := uc.GetAllURLs(ctx)
33 require.NoError(t, err)
34 require.NotNil(t, urls)
35
36 assert.Len(t, urls, 3)
37 _, hasExample := urls["https://example.com"]
38 _, hasGithub := urls["https://github.com"]
39 _, hasGoogle := urls["https://google.com"]
40 assert.True(t, hasExample)
41 assert.True(t, hasGithub)
42 assert.True(t, hasGoogle)
43}
44
45func TestManageFavoritesUseCase_GetAllURLs_ReturnsEmptySetWhenNoFavorites(t *testing.T) {
46 ctx := testContext()
47
48 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
49 folderRepo := repomocks.NewMockFolderRepository(t)
50 tagRepo := repomocks.NewMockTagRepository(t)
51
52 favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
53
54 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
55
56 urls, err := uc.GetAllURLs(ctx)
57 require.NoError(t, err)
58 require.NotNil(t, urls)
59 assert.Empty(t, urls)
60}
61
62func TestManageFavoritesUseCase_GetAllURLs_ReturnsErrorOnRepoFailure(t *testing.T) {
63 ctx := testContext()
64
65 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
66 folderRepo := repomocks.NewMockFolderRepository(t)
67 tagRepo := repomocks.NewMockTagRepository(t)
68
69 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(nil, errors.New("db connection failed"))
70
71 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
72
73 urls, err := uc.GetAllURLs(ctx)
74 require.Error(t, err)
75 assert.Nil(t, urls)
76 assert.Contains(t, err.Error(), "failed to get favorites")
77}
78
79func TestManageFavoritesUseCase_Toggle_AddsWhenNotFavorite(t *testing.T) {
80 ctx := testContext()
81
82 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
83 folderRepo := repomocks.NewMockFolderRepository(t)
84 tagRepo := repomocks.NewMockTagRepository(t)
85
86 // URL is not a favorite
87 favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil)
88 favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(f *entity.Favorite) bool {
89 return f.URL == "https://example.com" && f.Title == "Example"
90 })).Return(nil)
91
92 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
93
94 result, err := uc.Toggle(ctx, "https://example.com", "Example")
95 require.NoError(t, err)
96 require.NotNil(t, result)
97
98 assert.True(t, result.Added)
99 assert.Equal(t, "https://example.com", result.URL)
100 assert.Equal(t, "Example", result.Title)
101 assert.Equal(t, "Favorite added", result.Message)
102}
103
104func TestManageFavoritesUseCase_Toggle_RemovesWhenAlreadyFavorite(t *testing.T) {
105 ctx := testContext()
106
107 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
108 folderRepo := repomocks.NewMockFolderRepository(t)
109 tagRepo := repomocks.NewMockTagRepository(t)
110
111 existingFav := &entity.Favorite{
112 ID: 42,
113 URL: "https://example.com",
114 Title: "Example Site",
115 }
116
117 // URL is already a favorite
118 favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil)
119 favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(nil)
120
121 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
122
123 result, err := uc.Toggle(ctx, "https://example.com", "Example")
124 require.NoError(t, err)
125 require.NotNil(t, result)
126
127 assert.False(t, result.Added)
128 assert.Equal(t, "https://example.com", result.URL)
129 assert.Equal(t, "Example Site", result.Title) // Uses existing title
130 assert.Equal(t, "Favorite removed", result.Message)
131}
132
133func TestManageFavoritesUseCase_Toggle_EmptyURLReturnsError(t *testing.T) {
134 ctx := testContext()
135
136 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
137 folderRepo := repomocks.NewMockFolderRepository(t)
138 tagRepo := repomocks.NewMockTagRepository(t)
139
140 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
141
142 result, err := uc.Toggle(ctx, "", "Title")
143 require.Error(t, err)
144 assert.Nil(t, result)
145 assert.Contains(t, err.Error(), "URL cannot be empty")
146}
147
148func TestManageFavoritesUseCase_Toggle_FindByURLErrorReturnsError(t *testing.T) {
149 ctx := testContext()
150
151 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
152 folderRepo := repomocks.NewMockFolderRepository(t)
153 tagRepo := repomocks.NewMockTagRepository(t)
154
155 favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, errors.New("db error"))
156
157 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
158
159 result, err := uc.Toggle(ctx, "https://example.com", "Example")
160 require.Error(t, err)
161 assert.Nil(t, result)
162 assert.Contains(t, err.Error(), "failed to check existing favorite")
163}
164
165func TestManageFavoritesUseCase_Toggle_SaveErrorReturnsError(t *testing.T) {
166 ctx := testContext()
167
168 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
169 folderRepo := repomocks.NewMockFolderRepository(t)
170 tagRepo := repomocks.NewMockTagRepository(t)
171
172 favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil)
173 favoriteRepo.EXPECT().Save(mock.Anything, mock.Anything).Return(errors.New("db error"))
174
175 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
176
177 result, err := uc.Toggle(ctx, "https://example.com", "Example")
178 require.Error(t, err)
179 assert.Nil(t, result)
180 assert.Contains(t, err.Error(), "failed to add favorite")
181}
182
183func TestManageFavoritesUseCase_Toggle_DeleteErrorReturnsError(t *testing.T) {
184 ctx := testContext()
185
186 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
187 folderRepo := repomocks.NewMockFolderRepository(t)
188 tagRepo := repomocks.NewMockTagRepository(t)
189
190 existingFav := &entity.Favorite{ID: 42, URL: "https://example.com", Title: "Example"}
191 favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil)
192 favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(errors.New("db error"))
193
194 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
195
196 result, err := uc.Toggle(ctx, "https://example.com", "Example")
197 require.Error(t, err)
198 assert.Nil(t, result)
199 assert.Contains(t, err.Error(), "failed to remove favorite")
200}
201
202func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *testing.T) {
203 ctx := testContext()
204
205 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
206 folderRepo := repomocks.NewMockFolderRepository(t)
207 tagRepo := repomocks.NewMockTagRepository(t)
208
209 favorites := []*entity.Favorite{
210 {ID: 1, URL: "https://example.com/landing", Title: "GitHub docs mirror", Position: 0},
211 {ID: 2, URL: "https://github.com/bnema/dumber", Title: "Project repo", Position: 1},
212 {ID: 3, URL: "https://gitlab.com/team/project", Title: "GitLab", Position: 2},
213 }
214 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
215
216 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
217
218 results, err := uc.FilterForOmnibox(ctx, "git")
219 require.NoError(t, err)
220 require.Len(t, results, 3)
221 assert.Equal(t, "https://github.com/bnema/dumber", results[0].URL)
222 assert.Equal(t, "https://gitlab.com/team/project", results[1].URL)
223 assert.Equal(t, "https://example.com/landing", results[2].URL)
224}
225
226func TestManageFavoritesUseCase_FilterForOmnibox_EmptyQueryKeepsRepositoryOrder(t *testing.T) {
227 ctx := testContext()
228
229 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
230 folderRepo := repomocks.NewMockFolderRepository(t)
231 tagRepo := repomocks.NewMockTagRepository(t)
232
233 favorites := []*entity.Favorite{
234 {ID: 1, URL: "https://first.example"},
235 {ID: 2, URL: "https://second.example"},
236 }
237 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
238
239 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
240
241 results, err := uc.FilterForOmnibox(ctx, "")
242 require.NoError(t, err)
243 require.Len(t, results, 2)
244 assert.Equal(t, "https://first.example", results[0].URL)
245 assert.Equal(t, "https://second.example", results[1].URL)
246}
247
248func TestManageFavoritesUseCase_GetAllURLs_UsesCache(t *testing.T) {
249 ctx := testContext()
250
251 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
252 folderRepo := repomocks.NewMockFolderRepository(t)
253 tagRepo := repomocks.NewMockTagRepository(t)
254
255 favorites := []*entity.Favorite{
256 {ID: 1, URL: "https://example.com"},
257 }
258 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil).Once()
259
260 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
261
262 urls, err := uc.GetAllURLs(ctx)
263 require.NoError(t, err)
264 require.Contains(t, urls, "https://example.com")
265
266 urls, err = uc.GetAllURLs(ctx)
267 require.NoError(t, err)
268 require.Contains(t, urls, "https://example.com")
269}
270
271func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) {
272 ctx := testContext()
273
274 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
275 folderRepo := repomocks.NewMockFolderRepository(t)
276 tagRepo := repomocks.NewMockTagRepository(t)
277
278 initialFavorites := []*entity.Favorite{
279 {ID: 1, URL: "https://cached.example"},
280 }
281 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(initialFavorites, nil).Once()
282 favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://new.example").Return(nil, nil)
283 favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(f *entity.Favorite) bool {
284 return f.URL == "https://new.example"
285 })).Return(nil)
286 updatedFavorites := []*entity.Favorite{
287 {ID: 1, URL: "https://cached.example"},
288 {ID: 2, URL: "https://new.example"},
289 }
290 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once()
291
292 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
293
294 urls, err := uc.GetAllURLs(ctx)
295 require.NoError(t, err)
296 require.Contains(t, urls, "https://cached.example")
297
298 _, err = uc.Toggle(ctx, "https://new.example", "New")
299 require.NoError(t, err)
300
301 urls, err = uc.GetAllURLs(ctx)
302 require.NoError(t, err)
303 require.Contains(t, urls, "https://new.example")
304}
305
306func TestManageFavoritesUseCase_DeleteFolder_InvalidatesFavoritesCache(t *testing.T) {
307 ctx := testContext()
308
309 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
310 folderRepo := repomocks.NewMockFolderRepository(t)
311 tagRepo := repomocks.NewMockTagRepository(t)
312
313 folderID := entity.FolderID(9)
314 initialFolderID := folderID
315 cachedFavorites := []*entity.Favorite{
316 {ID: 1, URL: "https://example.com", FolderID: &initialFolderID},
317 }
318 updatedFavorites := []*entity.Favorite{
319 {ID: 1, URL: "https://example.com", FolderID: nil},
320 }
321
322 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(cachedFavorites, nil).Once()
323 folderRepo.EXPECT().FindByID(mock.Anything, folderID).Return(&entity.Folder{ID: folderID}, nil).Once()
324 favoriteRepo.EXPECT().GetByFolder(mock.Anything, &folderID).Return(cachedFavorites, nil).Once()
325 favoriteRepo.EXPECT().SetFolder(mock.Anything, entity.FavoriteID(1), (*entity.FolderID)(nil)).Return(nil).Once()
326 folderRepo.EXPECT().GetChildren(mock.Anything, &folderID).Return([]*entity.Folder{}, nil).Once()
327 folderRepo.EXPECT().Delete(mock.Anything, folderID).Return(nil).Once()
328 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once()
329
330 uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
331
332 favoritesBefore, err := uc.GetAll(ctx)
333 require.NoError(t, err)
334 require.Len(t, favoritesBefore, 1)
335 require.NotNil(t, favoritesBefore[0].FolderID)
336
337 err = uc.DeleteFolder(ctx, folderID)
338 require.NoError(t, err)
339
340 favoritesAfter, err := uc.GetAll(ctx)
341 require.NoError(t, err)
342 require.Len(t, favoritesAfter, 1)
343 assert.Nil(t, favoritesAfter[0].FolderID)
344}