get_last_restorable_session_test.go RawHistoryFolder
1package usecase_test
2
3import (
4 "testing"
5 "time"
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/require"
12)
13
14func TestGetLastRestorableSessionUseCase_NoSessions(t *testing.T) {
15 ctx := testContext()
16
17 sessionRepo := repomocks.NewMockSessionRepository(t)
18 stateRepo := repomocks.NewMockSessionStateRepository(t)
19
20 sessionRepo.EXPECT().GetRecent(ctx, 10).Return([]*entity.Session{}, nil)
21
22 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
23
24 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
25 ExcludeSessionID: "current",
26 })
27
28 require.NoError(t, err)
29 require.NotNil(t, output)
30 assert.Empty(t, output.SessionID)
31 assert.Nil(t, output.State)
32}
33
34func TestGetLastRestorableSessionUseCase_OnlyCurrentSession(t *testing.T) {
35 ctx := testContext()
36
37 sessionRepo := repomocks.NewMockSessionRepository(t)
38 stateRepo := repomocks.NewMockSessionStateRepository(t)
39
40 currentID := entity.SessionID("20251225_120000_curr")
41 endedAt := time.Now()
42
43 sessions := []*entity.Session{
44 {
45 ID: currentID,
46 Type: entity.SessionTypeBrowser,
47 StartedAt: time.Now().Add(-1 * time.Hour),
48 EndedAt: &endedAt,
49 },
50 }
51
52 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
53
54 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
55
56 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
57 ExcludeSessionID: currentID,
58 })
59
60 require.NoError(t, err)
61 require.NotNil(t, output)
62 assert.Empty(t, output.SessionID)
63}
64
65func TestGetLastRestorableSessionUseCase_SkipsCLISessions(t *testing.T) {
66 ctx := testContext()
67
68 sessionRepo := repomocks.NewMockSessionRepository(t)
69 stateRepo := repomocks.NewMockSessionStateRepository(t)
70
71 endedAt := time.Now()
72 sessions := []*entity.Session{
73 {
74 ID: "20251225_120000_cli1",
75 Type: entity.SessionTypeCLI, // Not a browser session
76 StartedAt: time.Now().Add(-1 * time.Hour),
77 EndedAt: &endedAt,
78 },
79 }
80
81 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
82
83 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
84
85 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
86 ExcludeSessionID: "current",
87 })
88
89 require.NoError(t, err)
90 assert.Empty(t, output.SessionID)
91}
92
93func TestGetLastRestorableSessionUseCase_SkipsActiveSessions(t *testing.T) {
94 ctx := testContext()
95
96 sessionRepo := repomocks.NewMockSessionRepository(t)
97 stateRepo := repomocks.NewMockSessionStateRepository(t)
98
99 activeID := entity.SessionID("20251225_120000_actv")
100
101 sessions := []*entity.Session{
102 {
103 ID: activeID,
104 Type: entity.SessionTypeBrowser,
105 StartedAt: time.Now().Add(-1 * time.Hour),
106 EndedAt: nil, // Active (no ended_at)
107 },
108 }
109
110 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
111
112 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
113
114 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
115 ExcludeSessionID: "current",
116 })
117
118 require.NoError(t, err)
119 assert.Empty(t, output.SessionID)
120}
121
122func TestGetLastRestorableSessionUseCase_SkipsNoSnapshot(t *testing.T) {
123 ctx := testContext()
124
125 sessionRepo := repomocks.NewMockSessionRepository(t)
126 stateRepo := repomocks.NewMockSessionStateRepository(t)
127
128 endedAt := time.Now()
129 sessionID := entity.SessionID("20251225_120000_nosn")
130
131 sessions := []*entity.Session{
132 {
133 ID: sessionID,
134 Type: entity.SessionTypeBrowser,
135 StartedAt: time.Now().Add(-1 * time.Hour),
136 EndedAt: &endedAt,
137 },
138 }
139
140 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
141 stateRepo.EXPECT().GetSnapshot(ctx, sessionID).Return(nil, nil) // No snapshot
142
143 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
144
145 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
146 ExcludeSessionID: "current",
147 })
148
149 require.NoError(t, err)
150 assert.Empty(t, output.SessionID)
151}
152
153func TestGetLastRestorableSessionUseCase_SkipsEmptyTabs(t *testing.T) {
154 ctx := testContext()
155
156 sessionRepo := repomocks.NewMockSessionRepository(t)
157 stateRepo := repomocks.NewMockSessionStateRepository(t)
158
159 endedAt := time.Now()
160 sessionID := entity.SessionID("20251225_120000_emty")
161
162 sessions := []*entity.Session{
163 {
164 ID: sessionID,
165 Type: entity.SessionTypeBrowser,
166 StartedAt: time.Now().Add(-1 * time.Hour),
167 EndedAt: &endedAt,
168 },
169 }
170
171 emptyState := &entity.SessionState{
172 Version: entity.SessionStateVersion,
173 SessionID: sessionID,
174 Tabs: []entity.TabSnapshot{}, // No tabs
175 SavedAt: time.Now(),
176 }
177
178 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
179 stateRepo.EXPECT().GetSnapshot(ctx, sessionID).Return(emptyState, nil)
180
181 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
182
183 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
184 ExcludeSessionID: "current",
185 })
186
187 require.NoError(t, err)
188 assert.Empty(t, output.SessionID)
189}
190
191func TestGetLastRestorableSessionUseCase_ReturnsEndedSession(t *testing.T) {
192 ctx := testContext()
193
194 sessionRepo := repomocks.NewMockSessionRepository(t)
195 stateRepo := repomocks.NewMockSessionStateRepository(t)
196
197 endedAt := time.Now()
198 sessionID := entity.SessionID("20251225_120000_good")
199
200 sessions := []*entity.Session{
201 {
202 ID: sessionID,
203 Type: entity.SessionTypeBrowser,
204 StartedAt: time.Now().Add(-1 * time.Hour),
205 EndedAt: &endedAt, // Gracefully ended
206 },
207 }
208
209 validState := &entity.SessionState{
210 Version: entity.SessionStateVersion,
211 SessionID: sessionID,
212 Tabs: []entity.TabSnapshot{
213 {
214 ID: "tab-1",
215 Name: "Test Tab",
216 Workspace: entity.WorkspaceSnapshot{
217 Root: &entity.PaneNodeSnapshot{
218 Pane: &entity.PaneSnapshot{
219 ID: "pane-1",
220 URI: "https://example.com",
221 Title: "Example",
222 },
223 },
224 },
225 },
226 },
227 SavedAt: time.Now(),
228 }
229
230 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
231 stateRepo.EXPECT().GetSnapshot(ctx, sessionID).Return(validState, nil)
232
233 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
234
235 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
236 ExcludeSessionID: "current",
237 })
238
239 require.NoError(t, err)
240 require.NotNil(t, output)
241 assert.Equal(t, sessionID, output.SessionID)
242 assert.Equal(t, validState, output.State)
243}
244
245func TestGetLastRestorableSessionUseCase_ReturnsCrashedSession(t *testing.T) {
246 ctx := testContext()
247
248 sessionRepo := repomocks.NewMockSessionRepository(t)
249 stateRepo := repomocks.NewMockSessionStateRepository(t)
250
251 sessionID := entity.SessionID("20251225_120000_crsh")
252
253 // Crashed session that was cleaned up by background cleanup (EndedAt set).
254 endedAt := time.Now().Add(-30 * time.Minute)
255 sessions := []*entity.Session{
256 {
257 ID: sessionID,
258 Type: entity.SessionTypeBrowser,
259 StartedAt: time.Now().Add(-1 * time.Hour),
260 EndedAt: &endedAt,
261 },
262 }
263
264 validState := &entity.SessionState{
265 Version: entity.SessionStateVersion,
266 SessionID: sessionID,
267 Tabs: []entity.TabSnapshot{
268 {
269 ID: "tab-1",
270 Name: "Crashed Tab",
271 Workspace: entity.WorkspaceSnapshot{
272 Root: &entity.PaneNodeSnapshot{
273 Pane: &entity.PaneSnapshot{
274 ID: "pane-1",
275 URI: "https://example.com",
276 Title: "Example",
277 },
278 },
279 },
280 },
281 },
282 SavedAt: time.Now(),
283 }
284
285 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
286 stateRepo.EXPECT().GetSnapshot(ctx, sessionID).Return(validState, nil)
287
288 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
289
290 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
291 ExcludeSessionID: "current",
292 })
293
294 require.NoError(t, err)
295 require.NotNil(t, output)
296 assert.Equal(t, sessionID, output.SessionID)
297 assert.Equal(t, validState, output.State)
298}
299
300func TestGetLastRestorableSessionUseCase_ReturnsMostRecent(t *testing.T) {
301 ctx := testContext()
302
303 sessionRepo := repomocks.NewMockSessionRepository(t)
304 stateRepo := repomocks.NewMockSessionStateRepository(t)
305
306 endedAt := time.Now()
307 recentID := entity.SessionID("20251225_140000_new1")
308 olderID := entity.SessionID("20251225_120000_old1")
309
310 // Sessions ordered by started_at DESC (most recent first)
311 sessions := []*entity.Session{
312 {
313 ID: recentID,
314 Type: entity.SessionTypeBrowser,
315 StartedAt: time.Now().Add(-1 * time.Hour),
316 EndedAt: &endedAt,
317 },
318 {
319 ID: olderID,
320 Type: entity.SessionTypeBrowser,
321 StartedAt: time.Now().Add(-2 * time.Hour),
322 EndedAt: &endedAt,
323 },
324 }
325
326 recentState := &entity.SessionState{
327 Version: entity.SessionStateVersion,
328 SessionID: recentID,
329 Tabs: []entity.TabSnapshot{
330 {ID: "tab-1", Name: "Recent Tab"},
331 },
332 SavedAt: time.Now(),
333 }
334
335 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
336 stateRepo.EXPECT().GetSnapshot(ctx, recentID).Return(recentState, nil)
337 // Note: GetSnapshot for olderID should NOT be called since we return first match
338
339 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
340
341 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
342 ExcludeSessionID: "current",
343 })
344
345 require.NoError(t, err)
346 assert.Equal(t, recentID, output.SessionID)
347}
348
349func TestGetLastRestorableSessionUseCase_SkipsFirstIfNoSnapshot(t *testing.T) {
350 ctx := testContext()
351
352 sessionRepo := repomocks.NewMockSessionRepository(t)
353 stateRepo := repomocks.NewMockSessionStateRepository(t)
354
355 endedAt := time.Now()
356 noSnapID := entity.SessionID("20251225_140000_nosn")
357 goodID := entity.SessionID("20251225_120000_good")
358
359 sessions := []*entity.Session{
360 {
361 ID: noSnapID,
362 Type: entity.SessionTypeBrowser,
363 StartedAt: time.Now().Add(-1 * time.Hour),
364 EndedAt: &endedAt,
365 },
366 {
367 ID: goodID,
368 Type: entity.SessionTypeBrowser,
369 StartedAt: time.Now().Add(-2 * time.Hour),
370 EndedAt: &endedAt,
371 },
372 }
373
374 goodState := &entity.SessionState{
375 Version: entity.SessionStateVersion,
376 SessionID: goodID,
377 Tabs: []entity.TabSnapshot{
378 {ID: "tab-1", Name: "Good Tab"},
379 },
380 SavedAt: time.Now(),
381 }
382
383 sessionRepo.EXPECT().GetRecent(ctx, 10).Return(sessions, nil)
384 stateRepo.EXPECT().GetSnapshot(ctx, noSnapID).Return(nil, nil) // No snapshot
385 stateRepo.EXPECT().GetSnapshot(ctx, goodID).Return(goodState, nil)
386
387 uc := usecase.NewGetLastRestorableSessionUseCase(sessionRepo, stateRepo)
388
389 output, err := uc.Execute(ctx, usecase.GetLastRestorableSessionInput{
390 ExcludeSessionID: "current",
391 })
392
393 require.NoError(t, err)
394 assert.Equal(t, goodID, output.SessionID)
395}