v2.31.0-dev3 cbca441

service_test.go RawHistoryFolder

1package preview
2
3import (
4	"context"
5	"testing"
6	"time"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/require"
10
11	"github.com/bnema/gordon/internal/domain"
12)
13
14type fakeStore struct {
15	previews []domain.PreviewRoute
16}
17
18func (f *fakeStore) Load(_ context.Context) ([]domain.PreviewRoute, error) {
19	return f.previews, nil
20}
21func (f *fakeStore) Save(_ context.Context, p []domain.PreviewRoute) error {
22	f.previews = p
23	return nil
24}
25
26func TestPreviewService_Add(t *testing.T) {
27	store := &fakeStore{}
28	svc := NewService(store, 48*time.Hour)
29
30	p := domain.PreviewRoute{
31		Domain:    "myapp--feat.example.com",
32		Name:      "feat",
33		BaseRoute: "myapp.example.com",
34		Image:     "myapp:preview-feat",
35	}
36	err := svc.Add(t.Context(), p)
37	require.NoError(t, err)
38
39	all, err := svc.List(t.Context())
40	require.NoError(t, err)
41	assert.Len(t, all, 1)
42	assert.Equal(t, "feat", all[0].Name)
43}
44
45func TestPreviewService_Delete(t *testing.T) {
46	store := &fakeStore{
47		previews: []domain.PreviewRoute{
48			{Name: "feat", Domain: "myapp--feat.example.com"},
49		},
50	}
51	svc := NewService(store, 48*time.Hour)
52	require.NoError(t, svc.Load(t.Context()))
53
54	err := svc.Delete(t.Context(), "feat")
55	require.NoError(t, err)
56
57	all, err := svc.List(t.Context())
58	require.NoError(t, err)
59	assert.Empty(t, all)
60}
61
62func TestPreviewService_Get(t *testing.T) {
63	store := &fakeStore{
64		previews: []domain.PreviewRoute{
65			{Name: "feat", Domain: "myapp--feat.example.com"},
66		},
67	}
68	svc := NewService(store, 48*time.Hour)
69	require.NoError(t, svc.Load(t.Context()))
70
71	p, err := svc.Get(t.Context(), "feat")
72	require.NoError(t, err)
73	assert.Equal(t, "myapp--feat.example.com", p.Domain)
74
75	_, err = svc.Get(t.Context(), "nonexistent")
76	assert.Error(t, err)
77}
78
79func TestPreviewService_Extend(t *testing.T) {
80	now := time.Now()
81	store := &fakeStore{
82		previews: []domain.PreviewRoute{
83			{Name: "feat", ExpiresAt: now.Add(1 * time.Hour)},
84		},
85	}
86	svc := NewService(store, 48*time.Hour)
87	require.NoError(t, svc.Load(t.Context()))
88
89	err := svc.Extend(t.Context(), "feat", 24*time.Hour)
90	require.NoError(t, err)
91
92	p, err := svc.Get(t.Context(), "feat")
93	require.NoError(t, err)
94	assert.True(t, p.ExpiresAt.After(now.Add(23*time.Hour)))
95}
96
97func TestPreviewService_GetExpired(t *testing.T) {
98	store := &fakeStore{
99		previews: []domain.PreviewRoute{
100			{Name: "expired", ExpiresAt: time.Now().Add(-1 * time.Hour)},
101			{Name: "active", ExpiresAt: time.Now().Add(1 * time.Hour)},
102		},
103	}
104	svc := NewService(store, 48*time.Hour)
105	require.NoError(t, svc.Load(t.Context()))
106
107	expired := svc.GetExpired()
108	assert.Len(t, expired, 1)
109	assert.Equal(t, "expired", expired[0].Name)
110}
111
112func TestPreviewService_Update(t *testing.T) {
113	store := &fakeStore{
114		previews: []domain.PreviewRoute{
115			{Name: "feat", Domain: "myapp--feat.example.com", Status: domain.PreviewStatusDeploying},
116		},
117	}
118	svc := NewService(store, 48*time.Hour)
119	require.NoError(t, svc.Load(t.Context()))
120
121	updated := domain.PreviewRoute{
122		Name:       "feat",
123		Domain:     "myapp--feat.example.com",
124		Status:     domain.PreviewStatusRunning,
125		Containers: []string{"gordon-myapp--feat.example.com"},
126	}
127	err := svc.Update(t.Context(), updated)
128	require.NoError(t, err)
129
130	p, err := svc.Get(t.Context(), "feat")
131	require.NoError(t, err)
132	assert.Equal(t, domain.PreviewStatusRunning, p.Status)
133	assert.Equal(t, []string{"gordon-myapp--feat.example.com"}, p.Containers)
134}
135
136func TestPreviewService_Update_NotFound(t *testing.T) {
137	store := &fakeStore{}
138	svc := NewService(store, 48*time.Hour)
139
140	err := svc.Update(t.Context(), domain.PreviewRoute{Name: "nonexistent"})
141	assert.ErrorIs(t, err, domain.ErrPreviewNotFound)
142}
143
144type fakeRuntime struct {
145	containers []*domain.Container
146}
147
148func (f *fakeRuntime) ListContainers(_ context.Context, _ bool) ([]*domain.Container, error) {
149	return f.containers, nil
150}
151
152func TestPreviewService_CollectOrphans(t *testing.T) {
153	store := &fakeStore{
154		previews: []domain.PreviewRoute{
155			{Name: "tracked", Domain: "myapp--tracked.example.com"},
156		},
157	}
158	svc := NewService(store, 48*time.Hour)
159	require.NoError(t, svc.Load(t.Context()))
160
161	runtime := &fakeRuntime{
162		containers: []*domain.Container{
163			{
164				Name:    "gordon-myapp--orphan.example.com",
165				Created: time.Now().Add(-72 * time.Hour),
166				Labels: map[string]string{
167					domain.LabelImage:  "myapp:preview-orphan",
168					domain.LabelDomain: "myapp--orphan.example.com",
169				},
170			},
171			{
172				Name:    "gordon-myapp--tracked.example.com",
173				Created: time.Now().Add(-72 * time.Hour),
174				Labels: map[string]string{
175					domain.LabelImage:  "myapp:preview-tracked",
176					domain.LabelDomain: "myapp--tracked.example.com",
177				},
178			},
179			{
180				Name:    "gordon-prod.example.com",
181				Created: time.Now().Add(-72 * time.Hour),
182				Labels: map[string]string{
183					domain.LabelImage:  "myapp:latest",
184					domain.LabelDomain: "prod.example.com",
185				},
186			},
187		},
188	}
189
190	orphans := svc.CollectOrphans(t.Context(), runtime, []string{"preview-*"}, "--")
191	assert.Len(t, orphans, 1)
192	assert.Equal(t, "gordon-myapp--orphan.example.com", orphans[0].Name)
193}
194
195func TestPreviewService_CollectOrphans_NotExpired(t *testing.T) {
196	store := &fakeStore{}
197	svc := NewService(store, 48*time.Hour)
198	require.NoError(t, svc.Load(t.Context()))
199
200	runtime := &fakeRuntime{
201		containers: []*domain.Container{
202			{
203				Name:    "gordon-myapp--recent.example.com",
204				Created: time.Now().Add(-12 * time.Hour),
205				Labels: map[string]string{
206					domain.LabelImage:  "myapp:preview-recent",
207					domain.LabelDomain: "myapp--recent.example.com",
208				},
209			},
210		},
211	}
212
213	orphans := svc.CollectOrphans(t.Context(), runtime, []string{"preview-*"}, "--")
214	assert.Empty(t, orphans)
215}

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 7

Tags 106

↑↓ moveenter openesc close