1package usecase_test
2
3import (
4 "errors"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9
10 "github.com/bnema/dumber/internal/application/usecase"
11 "github.com/bnema/dumber/internal/domain/entity"
12 repomocks "github.com/bnema/dumber/internal/domain/repository/mocks"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15)
16
17func TestDeleteSessionUseCase_Execute_Success(t *testing.T) {
18 ctx := testContext()
19
20 stateRepo := repomocks.NewMockSessionStateRepository(t)
21 sessionRepo := repomocks.NewMockSessionRepository(t)
22
23 sessionID := entity.SessionID("20251224_120000_delete")
24
25 endedAt := time.Now().Add(-time.Hour)
26 session := &entity.Session{
27 ID: sessionID,
28 Type: entity.SessionTypeBrowser,
29 StartedAt: time.Now().Add(-2 * time.Hour),
30 EndedAt: &endedAt,
31 }
32
33 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(session, nil)
34 stateRepo.EXPECT().DeleteSnapshot(ctx, sessionID).Return(nil)
35 sessionRepo.EXPECT().Delete(ctx, sessionID).Return(nil)
36
37 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
38
39 err := uc.Execute(ctx, usecase.DeleteSessionInput{
40 SessionID: sessionID,
41 CurrentSessionID: "other-session",
42 })
43 require.NoError(t, err)
44}
45
46func TestDeleteSessionUseCase_Execute_CannotDeleteCurrentSession(t *testing.T) {
47 ctx := testContext()
48
49 stateRepo := repomocks.NewMockSessionStateRepository(t)
50 sessionRepo := repomocks.NewMockSessionRepository(t)
51
52 sessionID := entity.SessionID("20251224_120000_current")
53
54 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
55
56 err := uc.Execute(ctx, usecase.DeleteSessionInput{
57 SessionID: sessionID,
58 CurrentSessionID: sessionID, // Same as current
59 })
60 require.Error(t, err)
61 assert.ErrorIs(t, err, usecase.ErrCannotDeleteCurrentSession)
62}
63
64func TestDeleteSessionUseCase_Execute_SessionNotFound(t *testing.T) {
65 ctx := testContext()
66
67 stateRepo := repomocks.NewMockSessionStateRepository(t)
68 sessionRepo := repomocks.NewMockSessionRepository(t)
69
70 sessionID := entity.SessionID("20251224_120000_notfound")
71
72 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(nil, nil)
73
74 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
75
76 err := uc.Execute(ctx, usecase.DeleteSessionInput{
77 SessionID: sessionID,
78 CurrentSessionID: "other-session",
79 })
80 require.Error(t, err)
81 assert.ErrorIs(t, err, usecase.ErrSessionNotFound)
82}
83
84func TestDeleteSessionUseCase_Execute_CannotDeleteActiveSession(t *testing.T) {
85 ctx := testContext()
86
87 stateRepo := repomocks.NewMockSessionStateRepository(t)
88 sessionRepo := repomocks.NewMockSessionRepository(t)
89
90 sessionID := entity.SessionID("20251224_120000_active")
91
92 // Session with nil EndedAt (still active)
93 session := &entity.Session{
94 ID: sessionID,
95 Type: entity.SessionTypeBrowser,
96 StartedAt: time.Now().Add(-time.Hour),
97 EndedAt: nil, // Active
98 }
99
100 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(session, nil)
101
102 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
103
104 err := uc.Execute(ctx, usecase.DeleteSessionInput{
105 SessionID: sessionID,
106 CurrentSessionID: "other-session",
107 })
108 require.Error(t, err)
109 assert.ErrorIs(t, err, usecase.ErrCannotDeleteActiveSession)
110}
111
112func TestDeleteSessionUseCase_Execute_CannotDeleteLockedSession(t *testing.T) {
113 ctx := testContext()
114
115 stateRepo := repomocks.NewMockSessionStateRepository(t)
116 sessionRepo := repomocks.NewMockSessionRepository(t)
117
118 // Create a temporary lock directory and lock file
119 lockDir := t.TempDir()
120 sessionID := entity.SessionID("20251224_120000_locked")
121 lockPath := filepath.Join(lockDir, "session_"+string(sessionID)+".lock")
122 f, err := os.Create(lockPath)
123 require.NoError(t, err)
124 f.Close()
125
126 endedAt := time.Now().Add(-time.Hour)
127 session := &entity.Session{
128 ID: sessionID,
129 Type: entity.SessionTypeBrowser,
130 StartedAt: time.Now().Add(-2 * time.Hour),
131 EndedAt: &endedAt, // Ended, but has lock file
132 }
133
134 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(session, nil)
135
136 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, lockDir)
137
138 err = uc.Execute(ctx, usecase.DeleteSessionInput{
139 SessionID: sessionID,
140 CurrentSessionID: "other-session",
141 })
142 require.Error(t, err)
143 assert.ErrorIs(t, err, usecase.ErrCannotDeleteActiveSession)
144}
145
146func TestDeleteSessionUseCase_Execute_FindByIDError(t *testing.T) {
147 ctx := testContext()
148
149 stateRepo := repomocks.NewMockSessionStateRepository(t)
150 sessionRepo := repomocks.NewMockSessionRepository(t)
151
152 sessionID := entity.SessionID("20251224_120000_dberror")
153
154 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(nil, errors.New("db error"))
155
156 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
157
158 err := uc.Execute(ctx, usecase.DeleteSessionInput{
159 SessionID: sessionID,
160 CurrentSessionID: "other-session",
161 })
162 require.Error(t, err)
163 assert.Contains(t, err.Error(), "db error")
164}
165
166func TestDeleteSessionUseCase_Execute_DeleteSnapshotErrorIgnored(t *testing.T) {
167 ctx := testContext()
168
169 stateRepo := repomocks.NewMockSessionStateRepository(t)
170 sessionRepo := repomocks.NewMockSessionRepository(t)
171
172 sessionID := entity.SessionID("20251224_120000_nosnapshot")
173
174 endedAt := time.Now().Add(-time.Hour)
175 session := &entity.Session{
176 ID: sessionID,
177 Type: entity.SessionTypeBrowser,
178 StartedAt: time.Now().Add(-2 * time.Hour),
179 EndedAt: &endedAt,
180 }
181
182 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(session, nil)
183 // Snapshot deletion fails (e.g., no snapshot exists) - should be ignored
184 stateRepo.EXPECT().DeleteSnapshot(ctx, sessionID).Return(errors.New("no rows"))
185 sessionRepo.EXPECT().Delete(ctx, sessionID).Return(nil)
186
187 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
188
189 err := uc.Execute(ctx, usecase.DeleteSessionInput{
190 SessionID: sessionID,
191 CurrentSessionID: "other-session",
192 })
193 // Should succeed even though DeleteSnapshot failed
194 require.NoError(t, err)
195}
196
197func TestDeleteSessionUseCase_Execute_DeleteSessionError(t *testing.T) {
198 ctx := testContext()
199
200 stateRepo := repomocks.NewMockSessionStateRepository(t)
201 sessionRepo := repomocks.NewMockSessionRepository(t)
202
203 sessionID := entity.SessionID("20251224_120000_deleteerror")
204
205 endedAt := time.Now().Add(-time.Hour)
206 session := &entity.Session{
207 ID: sessionID,
208 Type: entity.SessionTypeBrowser,
209 StartedAt: time.Now().Add(-2 * time.Hour),
210 EndedAt: &endedAt,
211 }
212
213 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(session, nil)
214 stateRepo.EXPECT().DeleteSnapshot(ctx, sessionID).Return(nil)
215 sessionRepo.EXPECT().Delete(ctx, sessionID).Return(errors.New("delete failed"))
216
217 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
218
219 err := uc.Execute(ctx, usecase.DeleteSessionInput{
220 SessionID: sessionID,
221 CurrentSessionID: "other-session",
222 })
223 require.Error(t, err)
224 assert.Contains(t, err.Error(), "delete failed")
225}
226
227func TestDeleteSessionUseCase_Execute_EmptyCurrentSessionAllowed(t *testing.T) {
228 ctx := testContext()
229
230 stateRepo := repomocks.NewMockSessionStateRepository(t)
231 sessionRepo := repomocks.NewMockSessionRepository(t)
232
233 sessionID := entity.SessionID("20251224_120000_nocurrent")
234
235 endedAt := time.Now().Add(-time.Hour)
236 session := &entity.Session{
237 ID: sessionID,
238 Type: entity.SessionTypeBrowser,
239 StartedAt: time.Now().Add(-2 * time.Hour),
240 EndedAt: &endedAt,
241 }
242
243 sessionRepo.EXPECT().FindByID(ctx, sessionID).Return(session, nil)
244 stateRepo.EXPECT().DeleteSnapshot(ctx, sessionID).Return(nil)
245 sessionRepo.EXPECT().Delete(ctx, sessionID).Return(nil)
246
247 uc := usecase.NewDeleteSessionUseCase(stateRepo, sessionRepo, "")
248
249 // CurrentSessionID is empty (e.g., CLI context)
250 err := uc.Execute(ctx, usecase.DeleteSessionInput{
251 SessionID: sessionID,
252 CurrentSessionID: "",
253 })
254 require.NoError(t, err)
255}