v0.21.0 73bf644

delete_session_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close