v2.30.0-pre 7341e0e

integration_test.go RawHistoryFolder

1package preview
2
3import (
4	"context"
5	"fmt"
6	"testing"
7	"time"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11
12	"github.com/bnema/gordon/internal/domain"
13)
14
15type fakeDeployer struct {
16	deployFn func(ctx context.Context, route domain.Route) (*domain.Container, error)
17}
18
19func (f *fakeDeployer) Deploy(ctx context.Context, route domain.Route) (*domain.Container, error) {
20	return f.deployFn(ctx, route)
21}
22
23type fakeRouteManager struct {
24	addRouteFn    func(ctx context.Context, route domain.Route) error
25	removeRouteFn func(ctx context.Context, domain string) error
26	getRouteFn    func(ctx context.Context, domain string) (*domain.Route, error)
27}
28
29func (f *fakeRouteManager) AddRoute(ctx context.Context, route domain.Route) error {
30	if f.addRouteFn != nil {
31		return f.addRouteFn(ctx, route)
32	}
33	return nil
34}
35
36func (f *fakeRouteManager) RemoveRoute(ctx context.Context, d string) error {
37	if f.removeRouteFn != nil {
38		return f.removeRouteFn(ctx, d)
39	}
40	return nil
41}
42
43func (f *fakeRouteManager) GetRoute(ctx context.Context, d string) (*domain.Route, error) {
44	if f.getRouteFn != nil {
45		return f.getRouteFn(ctx, d)
46	}
47	return nil, fmt.Errorf("route %q: %w", d, domain.ErrRouteNotFound)
48}
49
50func (f *fakeRouteManager) GetVolumeConfig() (bool, string, bool) {
51	return true, "gordon", false
52}
53
54func TestPreviewLifecycle_Integration(t *testing.T) {
55	store := &fakeStore{}
56	svc := NewService(store, 2*time.Second)
57
58	ctx := t.Context()
59
60	// Create
61	err := svc.CreatePreview(ctx, CreatePreviewRequest{
62		Name:      "test-feat",
63		Domain:    "myapp--test-feat.example.com",
64		BaseRoute: "myapp.example.com",
65		Image:     "myapp:preview-test-feat",
66		HTTPS:     true,
67		PreviewConfig: domain.PreviewConfig{
68			TTL:       2 * time.Second,
69			Separator: "--",
70			DataCopy:  false,
71		},
72	})
73	require.NoError(t, err)
74
75	// Verify created
76	all, err := svc.List(ctx)
77	require.NoError(t, err)
78	require.Len(t, all, 1)
79	assert.Equal(t, "test-feat", all[0].Name)
80	assert.Equal(t, "myapp--test-feat.example.com", all[0].Domain)
81	assert.Equal(t, "myapp.example.com", all[0].BaseRoute)
82
83	// Extend
84	err = svc.Extend(ctx, "test-feat", 1*time.Hour)
85	require.NoError(t, err)
86
87	p, err := svc.Get(ctx, "test-feat")
88	require.NoError(t, err)
89	assert.True(t, p.ExpiresAt.After(time.Now().Add(55*time.Minute)))
90
91	// Delete
92	err = svc.Delete(ctx, "test-feat")
93	require.NoError(t, err)
94
95	all, err = svc.List(ctx)
96	require.NoError(t, err)
97	assert.Empty(t, all)
98}
99
100func TestPreviewLifecycle_CleanupExpired(t *testing.T) {
101	store := &fakeStore{}
102	svc := NewService(store, 1*time.Millisecond)
103
104	ctx := t.Context()
105
106	// Create with very short TTL
107	err := svc.CreatePreview(ctx, CreatePreviewRequest{
108		Name:      "short-lived",
109		Domain:    "myapp--short-lived.example.com",
110		BaseRoute: "myapp.example.com",
111		Image:     "myapp:preview-short-lived",
112		HTTPS:     true,
113		PreviewConfig: domain.PreviewConfig{
114			TTL:       1 * time.Millisecond,
115			Separator: "--",
116			DataCopy:  false,
117		},
118	})
119	require.NoError(t, err)
120
121	// Wait for expiry
122	time.Sleep(10 * time.Millisecond)
123
124	// Cleanup should find it
125	expired := svc.CleanupExpired(ctx)
126	assert.Len(t, expired, 1)
127	assert.Equal(t, "short-lived", expired[0].Name)
128
129	// Should be gone
130	all, err := svc.List(ctx)
131	require.NoError(t, err)
132	assert.Empty(t, all)
133}
134
135type fakeEnvLoader struct {
136	loadEnvFn func(ctx context.Context, domain string) ([]string, error)
137}
138
139func (f *fakeEnvLoader) LoadEnv(ctx context.Context, domain string) ([]string, error) {
140	if f.loadEnvFn != nil {
141		return f.loadEnvFn(ctx, domain)
142	}
143	return nil, nil
144}
145
146func (f *fakeEnvLoader) CreateEnvFile(_ context.Context, _ string) error { return nil }
147func (f *fakeEnvLoader) EnvFileExists(_ string) (bool, error)            { return false, nil }
148
149func TestPreviewLifecycle_WithEnvInheritance(t *testing.T) {
150	store := &fakeStore{}
151	var deployedRoute domain.Route
152
153	mockDeployer := &fakeDeployer{
154		deployFn: func(_ context.Context, route domain.Route) (*domain.Container, error) {
155			deployedRoute = route
156			return &domain.Container{Name: "gordon-" + route.Domain}, nil
157		},
158	}
159	mockRouteManager := &fakeRouteManager{}
160	mockEnvLoader := &fakeEnvLoader{
161		loadEnvFn: func(_ context.Context, d string) ([]string, error) {
162			if d == "myapp.example.com" {
163				return []string{"DB_HOST=prod-db", "API_KEY=secret123"}, nil
164			}
165			return nil, nil
166		},
167	}
168
169	svc := NewService(store, 2*time.Second).
170		WithDeployer(mockDeployer).
171		WithRouteManager(mockRouteManager).
172		WithEnvLoader(mockEnvLoader).
173		WithRegistryDomain("reg.example.com")
174
175	ctx := t.Context()
176	err := svc.CreatePreview(ctx, CreatePreviewRequest{
177		Name:      "test-feat",
178		Domain:    "myapp--test-feat.example.com",
179		BaseRoute: "myapp.example.com",
180		Image:     "myapp:preview-test-feat",
181		HTTPS:     true,
182		PreviewConfig: domain.PreviewConfig{
183			TTL:       2 * time.Second,
184			Separator: "--",
185			EnvCopy:   true,
186		},
187	})
188	require.NoError(t, err)
189
190	// Deployer should receive base route's env vars via Route.Env
191	assert.Equal(t, []string{"DB_HOST=prod-db", "API_KEY=secret123"}, deployedRoute.Env)
192}
193
194func TestPreviewLifecycle_WithDeploy(t *testing.T) {
195	store := &fakeStore{}
196	deployed := false
197	routeAdded := ""
198
199	mockDeployer := &fakeDeployer{
200		deployFn: func(_ context.Context, route domain.Route) (*domain.Container, error) {
201			deployed = true
202			return &domain.Container{Name: "gordon-" + route.Domain}, nil
203		},
204	}
205	mockRouteManager := &fakeRouteManager{
206		addRouteFn: func(_ context.Context, route domain.Route) error {
207			routeAdded = route.Domain
208			return nil
209		},
210	}
211
212	svc := NewService(store, 2*time.Second).
213		WithDeployer(mockDeployer).
214		WithRouteManager(mockRouteManager).
215		WithRegistryDomain("reg.example.com")
216
217	ctx := t.Context()
218	err := svc.CreatePreview(ctx, CreatePreviewRequest{
219		Name:      "test-feat",
220		Domain:    "myapp--test-feat.example.com",
221		BaseRoute: "myapp.example.com",
222		Image:     "myapp:preview-test-feat",
223		HTTPS:     true,
224		PreviewConfig: domain.PreviewConfig{
225			TTL:       2 * time.Second,
226			Separator: "--",
227			DataCopy:  false,
228		},
229	})
230	require.NoError(t, err)
231
232	assert.True(t, deployed, "deployer should have been called")
233	assert.Equal(t, "myapp--test-feat.example.com", routeAdded)
234
235	all, err := svc.List(ctx)
236	require.NoError(t, err)
237	require.Len(t, all, 1)
238	assert.Equal(t, domain.PreviewStatusRunning, all[0].Status)
239	assert.Equal(t, []string{"gordon-myapp--test-feat.example.com"}, all[0].Containers)
240}

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