1package usecase_test
2
3import (
4 "testing"
5
6 "github.com/bnema/dumber/internal/application/usecase"
7 "github.com/bnema/dumber/internal/domain/autocomplete"
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 TestAutocompleteUseCase_GetSuggestion_EmptyInput(t *testing.T) {
16 ctx := testContext()
17 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
18
19 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: ""})
20
21 require.NotNil(t, output)
22 assert.False(t, output.Found)
23 assert.Nil(t, output.Suggestion)
24}
25
26func TestAutocompleteUseCase_GetSuggestion_NilDependencies(t *testing.T) {
27 ctx := testContext()
28 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
29
30 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
31
32 require.NotNil(t, output)
33 assert.False(t, output.Found)
34}
35
36func TestAutocompleteUseCase_GetSuggestion_FavoritesPriority(t *testing.T) {
37 ctx := testContext()
38
39 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
40 folderRepo := repomocks.NewMockFolderRepository(t)
41 tagRepo := repomocks.NewMockTagRepository(t)
42
43 favorites := []*entity.Favorite{
44 {ID: 1, URL: "https://github.com/user/repo", Title: "My Repo"},
45 {ID: 2, URL: "https://google.com", Title: "Google"},
46 }
47 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
48
49 favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
50 uc := usecase.NewAutocompleteUseCase(nil, favoritesUC, nil)
51
52 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
53
54 require.NotNil(t, output)
55 assert.True(t, output.Found)
56 assert.NotNil(t, output.Suggestion)
57 assert.Equal(t, autocomplete.SourceFavorite, output.Suggestion.Source)
58 assert.Equal(t, "github.com", output.Suggestion.FullText)
59 assert.Equal(t, ".com", output.Suggestion.Suffix)
60 assert.Equal(t, "My Repo", output.Suggestion.Title)
61}
62
63func TestAutocompleteUseCase_GetSuggestion_HistoryFallback(t *testing.T) {
64 ctx := testContext()
65
66 // Favorites returns empty
67 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
68 folderRepo := repomocks.NewMockFolderRepository(t)
69 tagRepo := repomocks.NewMockTagRepository(t)
70 favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
71
72 // History returns a match
73 historyRepo := repomocks.NewMockHistoryRepository(t)
74 historyEntries := []*entity.HistoryEntry{
75 {URL: "https://news.ycombinator.com", Title: "Hacker News"},
76 }
77 historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return(historyEntries, nil)
78
79 favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
80 historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
81 uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
82
83 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "news"})
84
85 require.NotNil(t, output)
86 assert.True(t, output.Found)
87 assert.NotNil(t, output.Suggestion)
88 assert.Equal(t, autocomplete.SourceHistory, output.Suggestion.Source)
89 assert.Equal(t, "news.ycombinator.com", output.Suggestion.FullText)
90 assert.Equal(t, ".ycombinator.com", output.Suggestion.Suffix)
91}
92
93func TestAutocompleteUseCase_GetSuggestion_BangShortcut(t *testing.T) {
94 ctx := testContext()
95
96 shortcuts := map[string]usecase.SearchShortcut{
97 "gh": {URL: "https://github.com/search?q=%s", Description: "GitHub search"},
98 "ddg": {URL: "https://duckduckgo.com/?q=%s", Description: "DuckDuckGo"},
99 }
100 shortcutsUC := usecase.NewSearchShortcutsUseCase(shortcuts)
101 uc := usecase.NewAutocompleteUseCase(nil, nil, shortcutsUC)
102
103 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "!g"})
104
105 require.NotNil(t, output)
106 assert.True(t, output.Found)
107 assert.NotNil(t, output.Suggestion)
108 assert.Equal(t, autocomplete.SourceBangShortcut, output.Suggestion.Source)
109 assert.Equal(t, "!gh", output.Suggestion.FullText)
110 assert.Equal(t, "h", output.Suggestion.Suffix)
111 assert.Equal(t, "GitHub search", output.Suggestion.Title)
112}
113
114func TestAutocompleteUseCase_GetSuggestion_BangWithoutPrefix(t *testing.T) {
115 ctx := testContext()
116
117 shortcuts := map[string]usecase.SearchShortcut{
118 "gh": {URL: "https://github.com/search?q=%s", Description: "GitHub search"},
119 }
120 shortcutsUC := usecase.NewSearchShortcutsUseCase(shortcuts)
121 uc := usecase.NewAutocompleteUseCase(nil, nil, shortcutsUC)
122
123 // Input without ! should not match bang shortcuts
124 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "gh"})
125
126 require.NotNil(t, output)
127 assert.False(t, output.Found)
128}
129
130func TestAutocompleteUseCase_GetSuggestion_NoMatch(t *testing.T) {
131 ctx := testContext()
132
133 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
134 folderRepo := repomocks.NewMockFolderRepository(t)
135 tagRepo := repomocks.NewMockTagRepository(t)
136 favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
137
138 historyRepo := repomocks.NewMockHistoryRepository(t)
139 historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return([]*entity.HistoryEntry{}, nil)
140
141 favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
142 historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
143 uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
144
145 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "xyz123"})
146
147 require.NotNil(t, output)
148 assert.False(t, output.Found)
149}
150
151func TestAutocompleteUseCase_ResolveCompletion_VisibleURLsOrder(t *testing.T) {
152 ctx := testContext()
153 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
154
155 urls := []string{
156 "https://bnema.dev",
157 "https://bnema.io",
158 }
159
160 suggestion := uc.ResolveCompletion(ctx, "bnem", usecase.CompletionOptions{
161 VisibleURLs: urls,
162 AllowBangs: false,
163 })
164
165 require.NotNil(t, suggestion)
166 assert.Equal(t, "bnema.dev", suggestion.FullText)
167 assert.Equal(t, "a.dev", suggestion.Suffix)
168}
169
170func TestAutocompleteUseCase_ResolveCompletion_PrefersShortHostForGoo(t *testing.T) {
171 ctx := testContext()
172 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
173
174 urls := []string{
175 "https://www.google.com/url?q=https://dashboard.stripe.com",
176 "https://google.com",
177 }
178
179 suggestion := uc.ResolveCompletion(ctx, "goo", usecase.CompletionOptions{
180 VisibleURLs: urls,
181 AllowBangs: false,
182 })
183
184 require.NotNil(t, suggestion)
185 assert.Equal(t, "google.com", suggestion.FullText)
186 assert.Equal(t, "gle.com", suggestion.Suffix)
187}
188
189func TestAutocompleteUseCase_GetSuggestion_FavoritesCached(t *testing.T) {
190 ctx := testContext()
191
192 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
193 folderRepo := repomocks.NewMockFolderRepository(t)
194 tagRepo := repomocks.NewMockTagRepository(t)
195
196 favorites := []*entity.Favorite{
197 {ID: 1, URL: "https://github.com/user/repo", Title: "Repo"},
198 }
199 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil).Once()
200
201 favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
202 uc := usecase.NewAutocompleteUseCase(nil, favoritesUC, nil)
203
204 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
205 require.NotNil(t, output)
206 assert.True(t, output.Found)
207
208 output = uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
209 require.NotNil(t, output)
210 assert.True(t, output.Found)
211}
212
213func TestAutocompleteUseCase_GetSuggestion_HistoryCached(t *testing.T) {
214 ctx := testContext()
215
216 historyRepo := repomocks.NewMockHistoryRepository(t)
217 historyEntries := []*entity.HistoryEntry{
218 {URL: "https://news.ycombinator.com", Title: "Hacker News"},
219 }
220 historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return(historyEntries, nil).Once()
221
222 historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
223 uc := usecase.NewAutocompleteUseCase(historyUC, nil, nil)
224
225 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "news"})
226 require.NotNil(t, output)
227 assert.True(t, output.Found)
228
229 output = uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "news"})
230 require.NotNil(t, output)
231 assert.True(t, output.Found)
232}
233
234func TestAutocompleteUseCase_GetSuggestionForURL_EmptyInput(t *testing.T) {
235 ctx := testContext()
236 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
237
238 tests := []struct {
239 name string
240 input string
241 targetURL string
242 }{
243 {name: "empty input", input: "", targetURL: "https://example.com"},
244 {name: "empty URL", input: "exa", targetURL: ""},
245 {name: "both empty", input: "", targetURL: ""},
246 }
247
248 for _, tt := range tests {
249 t.Run(tt.name, func(t *testing.T) {
250 output := uc.GetSuggestionForURL(ctx, tt.input, tt.targetURL)
251 require.NotNil(t, output)
252 assert.False(t, output.Found)
253 })
254 }
255}
256
257func TestAutocompleteUseCase_GetSuggestionForURL_Match(t *testing.T) {
258 ctx := testContext()
259 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
260
261 tests := []struct {
262 name string
263 input string
264 targetURL string
265 wantSuffix string
266 wantMatchedURL string
267 }{
268 {
269 name: "prefix match without protocol",
270 input: "github",
271 targetURL: "https://github.com/user/repo",
272 wantSuffix: ".com/user/repo",
273 wantMatchedURL: "github.com/user/repo",
274 },
275 {
276 name: "prefix match with protocol",
277 input: "https://github",
278 targetURL: "https://github.com/user/repo",
279 wantSuffix: ".com/user/repo",
280 wantMatchedURL: "https://github.com/user/repo",
281 },
282 {
283 name: "case insensitive match",
284 input: "GITHUB",
285 targetURL: "https://github.com",
286 wantSuffix: ".com",
287 wantMatchedURL: "github.com",
288 },
289 }
290
291 for _, tt := range tests {
292 t.Run(tt.name, func(t *testing.T) {
293 output := uc.GetSuggestionForURL(ctx, tt.input, tt.targetURL)
294 require.NotNil(t, output)
295 assert.True(t, output.Found)
296 require.NotNil(t, output.Suggestion)
297 assert.Equal(t, tt.wantSuffix, output.Suggestion.Suffix)
298 assert.Equal(t, tt.wantMatchedURL, output.Suggestion.FullText)
299 })
300 }
301}
302
303func TestAutocompleteUseCase_GetSuggestionForURL_NoMatch(t *testing.T) {
304 ctx := testContext()
305 uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
306
307 output := uc.GetSuggestionForURL(ctx, "xyz", "https://github.com")
308
309 require.NotNil(t, output)
310 assert.False(t, output.Found)
311}
312
313func TestAutocompleteUseCase_GetSuggestion_FavoritesOverHistory(t *testing.T) {
314 ctx := testContext()
315
316 // Both favorites and history have matches - favorites should win
317 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
318 folderRepo := repomocks.NewMockFolderRepository(t)
319 tagRepo := repomocks.NewMockTagRepository(t)
320 favorites := []*entity.Favorite{
321 {ID: 1, URL: "https://github.com/favorite", Title: "Favorite Repo"},
322 }
323 favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
324
325 // History would also match, but should not be called because favorites matched
326 historyRepo := repomocks.NewMockHistoryRepository(t)
327 // Note: no EXPECT on historyRepo - it shouldn't be called
328
329 favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
330 historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
331 uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
332
333 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
334
335 require.NotNil(t, output)
336 assert.True(t, output.Found)
337 assert.Equal(t, autocomplete.SourceFavorite, output.Suggestion.Source)
338 assert.Equal(t, "Favorite Repo", output.Suggestion.Title)
339}
340
341func TestAutocompleteUseCase_GetSuggestion_HistoryNotPrefixMatch(t *testing.T) {
342 ctx := testContext()
343
344 // Favorites returns empty
345 favoriteRepo := repomocks.NewMockFavoriteRepository(t)
346 folderRepo := repomocks.NewMockFolderRepository(t)
347 tagRepo := repomocks.NewMockTagRepository(t)
348 favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
349
350 // History returns results but none are prefix matches
351 historyRepo := repomocks.NewMockHistoryRepository(t)
352 historyEntries := []*entity.HistoryEntry{
353 // URL doesn't start with "git", so no prefix match
354 {URL: "https://example.com/git", Title: "Example"},
355 }
356 historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return(historyEntries, nil)
357
358 favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
359 historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
360 uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
361
362 output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "git"})
363
364 require.NotNil(t, output)
365 assert.False(t, output.Found)
366}