v0.27.0 dcb4c51

service_test.go RawHistoryFolder

1package snapshot
2
3import (
4	"context"
5	"errors"
6	"testing"
7	"time"
8
9	"github.com/bnema/dumber/internal/application/usecase"
10	"github.com/bnema/dumber/internal/domain/entity"
11	repomocks "github.com/bnema/dumber/internal/domain/repository/mocks"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/mock"
14	"github.com/stretchr/testify/require"
15)
16
17type testProvider struct {
18	sessionID entity.SessionID
19	tabList   *entity.TabList
20}
21
22func (p *testProvider) GetTabList() *entity.TabList {
23	return p.tabList
24}
25
26func (p *testProvider) GetSessionID() entity.SessionID {
27	return p.sessionID
28}
29
30func TestService_SaveSnapshot_RetriesTransientFKAndSucceeds(t *testing.T) {
31	repo := repomocks.NewMockSessionStateRepository(t)
32	calls := 0
33	repo.EXPECT().
34		SaveSnapshot(mock.Anything, mock.AnythingOfType("*entity.SessionState")).
35		RunAndReturn(func(_ context.Context, _ *entity.SessionState) error {
36			calls++
37			if calls == 1 {
38				return errors.New("FOREIGN KEY constraint failed")
39			}
40			return nil
41		})
42
43	uc := usecase.NewSnapshotSessionUseCase(repo)
44	svc := NewService(uc, &testProvider{
45		sessionID: "20260207_120000_fk_retry_ok",
46		tabList:   entity.NewTabList(),
47	}, 1)
48	svc.retryDelay = time.Millisecond
49	svc.ready = true
50	svc.dirty = true
51
52	err := svc.saveSnapshot(context.Background())
53	require.NoError(t, err)
54	assert.Equal(t, 2, calls)
55	assert.False(t, svc.dirty)
56}
57
58func TestService_SaveSnapshot_RetriesTransientFKAndFails(t *testing.T) {
59	repo := repomocks.NewMockSessionStateRepository(t)
60	calls := 0
61	fkErr := errors.New("save session snapshot: FOREIGN KEY constraint failed")
62	repo.EXPECT().
63		SaveSnapshot(mock.Anything, mock.AnythingOfType("*entity.SessionState")).
64		RunAndReturn(func(_ context.Context, _ *entity.SessionState) error {
65			calls++
66			return fkErr
67		})
68
69	uc := usecase.NewSnapshotSessionUseCase(repo)
70	svc := NewService(uc, &testProvider{
71		sessionID: "20260207_120000_fk_retry_fail",
72		tabList:   entity.NewTabList(),
73	}, 1)
74	svc.retryDelay = time.Millisecond
75	svc.ready = true
76	svc.dirty = true
77
78	err := svc.saveSnapshot(context.Background())
79	require.Error(t, err)
80	require.ErrorIs(t, err, fkErr)
81	assert.Equal(t, svc.retries+1, calls)
82	assert.True(t, svc.dirty)
83}
84
85func TestService_SaveSnapshot_DoesNotRetryNonFKError(t *testing.T) {
86	repo := repomocks.NewMockSessionStateRepository(t)
87	calls := 0
88	nonFKErr := errors.New("database is read-only")
89	repo.EXPECT().
90		SaveSnapshot(mock.Anything, mock.AnythingOfType("*entity.SessionState")).
91		RunAndReturn(func(_ context.Context, _ *entity.SessionState) error {
92			calls++
93			return nonFKErr
94		})
95
96	uc := usecase.NewSnapshotSessionUseCase(repo)
97	svc := NewService(uc, &testProvider{
98		sessionID: "20260207_120000_non_fk",
99		tabList:   entity.NewTabList(),
100	}, 1)
101	svc.retryDelay = time.Millisecond
102	svc.ready = true
103	svc.dirty = true
104
105	err := svc.saveSnapshot(context.Background())
106	require.Error(t, err)
107	require.ErrorIs(t, err, nonFKErr)
108	assert.Equal(t, 1, calls)
109	assert.True(t, svc.dirty)
110}
111
112func TestService_SetReady_SavesPendingDirtySnapshot(t *testing.T) {
113	repo := repomocks.NewMockSessionStateRepository(t)
114	saved := make(chan struct{}, 1)
115	repo.EXPECT().
116		SaveSnapshot(mock.Anything, mock.AnythingOfType("*entity.SessionState")).
117		RunAndReturn(func(_ context.Context, _ *entity.SessionState) error {
118			saved <- struct{}{}
119			return nil
120		})
121
122	uc := usecase.NewSnapshotSessionUseCase(repo)
123	svc := NewService(uc, &testProvider{
124		sessionID: "20260207_120000_ready_flush",
125		tabList:   entity.NewTabList(),
126	}, 1)
127	svc.Start(context.Background())
128	svc.dirty = true
129
130	svc.SetReady()
131
132	select {
133	case <-saved:
134	case <-time.After(500 * time.Millisecond):
135		t.Fatal("expected pending snapshot to be saved after SetReady")
136	}
137	svc.mu.Lock()
138	dirty := svc.dirty
139	svc.mu.Unlock()
140	assert.False(t, dirty)
141}

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