v2.31.0-dev3 cbca441

service_test.go RawHistoryFolder

1package registry
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"io"
8	"strings"
9	"testing"
10
11	"github.com/bnema/zerowrap"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/mock"
14	"github.com/stretchr/testify/require"
15
16	"github.com/bnema/gordon/internal/boundaries/out/mocks"
17	"github.com/bnema/gordon/internal/domain"
18)
19
20func testContext() context.Context {
21	return zerowrap.WithCtx(context.Background(), zerowrap.Default())
22}
23
24func TestService_GetManifest_Success(t *testing.T) {
25	blobStorage := mocks.NewMockBlobStorage(t)
26	manifestStorage := mocks.NewMockManifestStorage(t)
27	eventBus := mocks.NewMockEventPublisher(t)
28
29	svc := NewService(blobStorage, manifestStorage, eventBus)
30	ctx := testContext()
31
32	manifestData := []byte(`{"schemaVersion": 2}`)
33	manifestStorage.EXPECT().GetManifest("myapp", "latest").Return(manifestData, "application/vnd.docker.distribution.manifest.v2+json", nil)
34
35	result, err := svc.GetManifest(ctx, "myapp", "latest")
36
37	assert.NoError(t, err)
38	assert.NotNil(t, result)
39	assert.Equal(t, "myapp", result.Name)
40	assert.Equal(t, "latest", result.Reference)
41	assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", result.ContentType)
42	assert.Equal(t, manifestData, result.Data)
43}
44
45func TestService_GetManifest_NotFound(t *testing.T) {
46	blobStorage := mocks.NewMockBlobStorage(t)
47	manifestStorage := mocks.NewMockManifestStorage(t)
48	eventBus := mocks.NewMockEventPublisher(t)
49
50	svc := NewService(blobStorage, manifestStorage, eventBus)
51	ctx := testContext()
52
53	manifestStorage.EXPECT().GetManifest("myapp", "notexists").Return(nil, "", errors.New("manifest not found"))
54
55	result, err := svc.GetManifest(ctx, "myapp", "notexists")
56
57	assert.Error(t, err)
58	assert.Nil(t, result)
59	assert.Contains(t, err.Error(), "failed to get manifest")
60}
61
62func TestService_PutManifest_Success(t *testing.T) {
63	blobStorage := mocks.NewMockBlobStorage(t)
64	manifestStorage := mocks.NewMockManifestStorage(t)
65	eventBus := mocks.NewMockEventPublisher(t)
66
67	svc := NewService(blobStorage, manifestStorage, eventBus)
68	ctx := testContext()
69
70	manifest := &domain.Manifest{
71		Name:        "myapp",
72		Reference:   "latest",
73		ContentType: "application/vnd.docker.distribution.manifest.v2+json",
74		Data:        []byte(`{"schemaVersion": 2}`),
75	}
76
77	manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil)
78	eventBus.EXPECT().Publish(domain.EventImagePushed, mock.AnythingOfType("domain.ImagePushedPayload")).Return(nil)
79
80	digest, err := svc.PutManifest(ctx, manifest)
81
82	assert.NoError(t, err)
83	assert.NotEmpty(t, digest)
84	assert.True(t, strings.HasPrefix(digest, "sha256:"))
85}
86
87func TestService_PutManifest_StorageError(t *testing.T) {
88	blobStorage := mocks.NewMockBlobStorage(t)
89	manifestStorage := mocks.NewMockManifestStorage(t)
90	eventBus := mocks.NewMockEventPublisher(t)
91
92	svc := NewService(blobStorage, manifestStorage, eventBus)
93	ctx := testContext()
94
95	manifest := &domain.Manifest{
96		Name:        "myapp",
97		Reference:   "latest",
98		ContentType: "application/vnd.docker.distribution.manifest.v2+json",
99		Data:        []byte(`{"schemaVersion": 2}`),
100	}
101
102	manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(errors.New("storage full"))
103
104	_, err := svc.PutManifest(ctx, manifest)
105
106	assert.Error(t, err)
107	assert.Contains(t, err.Error(), "failed to store manifest")
108}
109
110func TestService_PutManifest_EventPublishError(t *testing.T) {
111	blobStorage := mocks.NewMockBlobStorage(t)
112	manifestStorage := mocks.NewMockManifestStorage(t)
113	eventBus := mocks.NewMockEventPublisher(t)
114
115	svc := NewService(blobStorage, manifestStorage, eventBus)
116	ctx := testContext()
117
118	manifest := &domain.Manifest{
119		Name:        "myapp",
120		Reference:   "latest",
121		ContentType: "application/vnd.docker.distribution.manifest.v2+json",
122		Data:        []byte(`{"schemaVersion": 2}`),
123	}
124
125	manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil)
126	eventBus.EXPECT().Publish(domain.EventImagePushed, mock.Anything).Return(errors.New("event bus error"))
127
128	// Should still succeed - event publish error is logged but doesn't fail the operation
129	_, err := svc.PutManifest(ctx, manifest)
130
131	assert.NoError(t, err)
132}
133
134func TestService_PutManifest_NilEventBus(t *testing.T) {
135	blobStorage := mocks.NewMockBlobStorage(t)
136	manifestStorage := mocks.NewMockManifestStorage(t)
137
138	// Nil event bus
139	svc := NewService(blobStorage, manifestStorage, nil)
140	ctx := testContext()
141
142	manifest := &domain.Manifest{
143		Name:        "myapp",
144		Reference:   "latest",
145		ContentType: "application/vnd.docker.distribution.manifest.v2+json",
146		Data:        []byte(`{"schemaVersion": 2}`),
147	}
148
149	manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil)
150
151	_, err := svc.PutManifest(ctx, manifest)
152
153	assert.NoError(t, err)
154}
155
156func TestService_DeleteManifest_Success(t *testing.T) {
157	blobStorage := mocks.NewMockBlobStorage(t)
158	manifestStorage := mocks.NewMockManifestStorage(t)
159	eventBus := mocks.NewMockEventPublisher(t)
160
161	svc := NewService(blobStorage, manifestStorage, eventBus)
162	ctx := testContext()
163
164	manifestStorage.EXPECT().DeleteManifest("myapp", "latest").Return(nil)
165
166	err := svc.DeleteManifest(ctx, "myapp", "latest")
167
168	assert.NoError(t, err)
169}
170
171func TestService_DeleteManifest_Error(t *testing.T) {
172	blobStorage := mocks.NewMockBlobStorage(t)
173	manifestStorage := mocks.NewMockManifestStorage(t)
174	eventBus := mocks.NewMockEventPublisher(t)
175
176	svc := NewService(blobStorage, manifestStorage, eventBus)
177	ctx := testContext()
178
179	manifestStorage.EXPECT().DeleteManifest("myapp", "latest").Return(errors.New("manifest not found"))
180
181	err := svc.DeleteManifest(ctx, "myapp", "latest")
182
183	assert.Error(t, err)
184	assert.Contains(t, err.Error(), "failed to delete manifest")
185}
186
187func TestService_GetBlob_Success(t *testing.T) {
188	blobStorage := mocks.NewMockBlobStorage(t)
189	manifestStorage := mocks.NewMockManifestStorage(t)
190	eventBus := mocks.NewMockEventPublisher(t)
191
192	svc := NewService(blobStorage, manifestStorage, eventBus)
193	ctx := testContext()
194
195	blobData := []byte("blob content")
196	blobStorage.EXPECT().GetBlob("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(io.NopCloser(bytes.NewReader(blobData)), nil)
197
198	reader, err := svc.GetBlob(ctx, "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
199
200	assert.NoError(t, err)
201	assert.NotNil(t, reader)
202
203	data, _ := io.ReadAll(reader)
204	assert.Equal(t, blobData, data)
205}
206
207func TestService_GetBlob_NotFound(t *testing.T) {
208	blobStorage := mocks.NewMockBlobStorage(t)
209	manifestStorage := mocks.NewMockManifestStorage(t)
210	eventBus := mocks.NewMockEventPublisher(t)
211
212	svc := NewService(blobStorage, manifestStorage, eventBus)
213	ctx := testContext()
214
215	blobStorage.EXPECT().GetBlob("sha256:notexists").Return(nil, errors.New("blob not found"))
216
217	reader, err := svc.GetBlob(ctx, "sha256:notexists")
218
219	assert.Error(t, err)
220	assert.Nil(t, reader)
221	assert.Contains(t, err.Error(), "failed to get blob")
222}
223
224func TestService_PutBlob_Success(t *testing.T) {
225	blobStorage := mocks.NewMockBlobStorage(t)
226	manifestStorage := mocks.NewMockManifestStorage(t)
227	eventBus := mocks.NewMockEventPublisher(t)
228
229	svc := NewService(blobStorage, manifestStorage, eventBus)
230	ctx := testContext()
231
232	blobData := []byte("blob content")
233	reader := bytes.NewReader(blobData)
234
235	blobStorage.EXPECT().PutBlob("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", mock.Anything, int64(len(blobData))).Return(nil)
236
237	err := svc.PutBlob(ctx, "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", reader, int64(len(blobData)))
238
239	assert.NoError(t, err)
240}
241
242func TestService_PutBlob_Error(t *testing.T) {
243	blobStorage := mocks.NewMockBlobStorage(t)
244	manifestStorage := mocks.NewMockManifestStorage(t)
245	eventBus := mocks.NewMockEventPublisher(t)
246
247	svc := NewService(blobStorage, manifestStorage, eventBus)
248	ctx := testContext()
249
250	blobData := []byte("blob content")
251	reader := bytes.NewReader(blobData)
252
253	blobStorage.EXPECT().PutBlob("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", mock.Anything, int64(len(blobData))).Return(errors.New("storage full"))
254
255	err := svc.PutBlob(ctx, "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", reader, int64(len(blobData)))
256
257	assert.Error(t, err)
258	assert.Contains(t, err.Error(), "failed to store blob")
259}
260
261func TestService_BlobExists(t *testing.T) {
262	blobStorage := mocks.NewMockBlobStorage(t)
263	manifestStorage := mocks.NewMockManifestStorage(t)
264	eventBus := mocks.NewMockEventPublisher(t)
265
266	svc := NewService(blobStorage, manifestStorage, eventBus)
267	ctx := testContext()
268
269	blobStorage.EXPECT().BlobExists("sha256:exists").Return(true)
270	blobStorage.EXPECT().BlobExists("sha256:notexists").Return(false)
271
272	assert.True(t, svc.BlobExists(ctx, "sha256:exists"))
273	assert.False(t, svc.BlobExists(ctx, "sha256:notexists"))
274}
275
276func TestService_StartUpload_Success(t *testing.T) {
277	blobStorage := mocks.NewMockBlobStorage(t)
278	manifestStorage := mocks.NewMockManifestStorage(t)
279	eventBus := mocks.NewMockEventPublisher(t)
280
281	svc := NewService(blobStorage, manifestStorage, eventBus)
282	ctx := testContext()
283
284	blobStorage.EXPECT().StartBlobUpload("myapp").Return("1234567890-myapp", nil)
285
286	uuid, err := svc.StartUpload(ctx, "myapp")
287
288	assert.NoError(t, err)
289	assert.Equal(t, "1234567890-myapp", uuid)
290}
291
292func TestService_StartUpload_Error(t *testing.T) {
293	blobStorage := mocks.NewMockBlobStorage(t)
294	manifestStorage := mocks.NewMockManifestStorage(t)
295	eventBus := mocks.NewMockEventPublisher(t)
296
297	svc := NewService(blobStorage, manifestStorage, eventBus)
298	ctx := testContext()
299
300	blobStorage.EXPECT().StartBlobUpload("myapp").Return("", errors.New("cannot create upload"))
301
302	uuid, err := svc.StartUpload(ctx, "myapp")
303
304	assert.Error(t, err)
305	assert.Empty(t, uuid)
306	assert.Contains(t, err.Error(), "failed to start blob upload")
307}
308
309func TestService_FinishUpload_Success(t *testing.T) {
310	blobStorage := mocks.NewMockBlobStorage(t)
311	manifestStorage := mocks.NewMockManifestStorage(t)
312	eventBus := mocks.NewMockEventPublisher(t)
313
314	svc := NewService(blobStorage, manifestStorage, eventBus)
315	ctx := testContext()
316
317	blobStorage.EXPECT().FinishBlobUpload("1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil)
318
319	err := svc.FinishUpload(ctx, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
320
321	assert.NoError(t, err)
322}
323
324func TestService_FinishUpload_Error(t *testing.T) {
325	blobStorage := mocks.NewMockBlobStorage(t)
326	manifestStorage := mocks.NewMockManifestStorage(t)
327	eventBus := mocks.NewMockEventPublisher(t)
328
329	svc := NewService(blobStorage, manifestStorage, eventBus)
330	ctx := testContext()
331
332	blobStorage.EXPECT().FinishBlobUpload("1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(errors.New("digest mismatch"))
333
334	err := svc.FinishUpload(ctx, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
335
336	assert.Error(t, err)
337	assert.Contains(t, err.Error(), "failed to finish blob upload")
338}
339
340func TestService_CancelUpload_Success(t *testing.T) {
341	blobStorage := mocks.NewMockBlobStorage(t)
342	manifestStorage := mocks.NewMockManifestStorage(t)
343	eventBus := mocks.NewMockEventPublisher(t)
344
345	svc := NewService(blobStorage, manifestStorage, eventBus)
346	ctx := testContext()
347
348	blobStorage.EXPECT().CancelBlobUpload("1234567890-myapp").Return(nil)
349
350	err := svc.CancelUpload(ctx, "1234567890-myapp")
351
352	assert.NoError(t, err)
353}
354
355func TestService_CancelUpload_Error(t *testing.T) {
356	blobStorage := mocks.NewMockBlobStorage(t)
357	manifestStorage := mocks.NewMockManifestStorage(t)
358	eventBus := mocks.NewMockEventPublisher(t)
359
360	svc := NewService(blobStorage, manifestStorage, eventBus)
361	ctx := testContext()
362
363	blobStorage.EXPECT().CancelBlobUpload("1234567890-myapp").Return(errors.New("upload not found"))
364
365	err := svc.CancelUpload(ctx, "1234567890-myapp")
366
367	assert.Error(t, err)
368	assert.Contains(t, err.Error(), "failed to cancel blob upload")
369}
370
371func TestService_ListTags_Success(t *testing.T) {
372	blobStorage := mocks.NewMockBlobStorage(t)
373	manifestStorage := mocks.NewMockManifestStorage(t)
374	eventBus := mocks.NewMockEventPublisher(t)
375
376	svc := NewService(blobStorage, manifestStorage, eventBus)
377	ctx := testContext()
378
379	manifestStorage.EXPECT().ListTags("myapp").Return([]string{
380		"latest",
381		"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
382		"v1.0",
383		"v2.0",
384	}, nil)
385
386	tags, err := svc.ListTags(ctx, "myapp")
387
388	assert.NoError(t, err)
389	assert.Equal(t, []string{"latest", "v1.0", "v2.0"}, tags)
390}
391
392func TestService_ListTags_Error(t *testing.T) {
393	blobStorage := mocks.NewMockBlobStorage(t)
394	manifestStorage := mocks.NewMockManifestStorage(t)
395	eventBus := mocks.NewMockEventPublisher(t)
396
397	svc := NewService(blobStorage, manifestStorage, eventBus)
398	ctx := testContext()
399
400	manifestStorage.EXPECT().ListTags("myapp").Return(nil, errors.New("repository not found"))
401
402	tags, err := svc.ListTags(ctx, "myapp")
403
404	assert.Error(t, err)
405	assert.Nil(t, tags)
406	assert.Contains(t, err.Error(), "failed to list tags")
407}
408
409func TestService_ListRepositories_Success(t *testing.T) {
410	blobStorage := mocks.NewMockBlobStorage(t)
411	manifestStorage := mocks.NewMockManifestStorage(t)
412	eventBus := mocks.NewMockEventPublisher(t)
413
414	svc := NewService(blobStorage, manifestStorage, eventBus)
415	ctx := testContext()
416
417	manifestStorage.EXPECT().ListRepositories().Return([]string{"myapp", "otherapp", "thirdapp"}, nil)
418
419	repos, err := svc.ListRepositories(ctx)
420
421	assert.NoError(t, err)
422	assert.Equal(t, []string{"myapp", "otherapp", "thirdapp"}, repos)
423}
424
425func TestService_ListRepositories_Error(t *testing.T) {
426	blobStorage := mocks.NewMockBlobStorage(t)
427	manifestStorage := mocks.NewMockManifestStorage(t)
428	eventBus := mocks.NewMockEventPublisher(t)
429
430	svc := NewService(blobStorage, manifestStorage, eventBus)
431	ctx := testContext()
432
433	manifestStorage.EXPECT().ListRepositories().Return(nil, errors.New("storage error"))
434
435	repos, err := svc.ListRepositories(ctx)
436
437	assert.Error(t, err)
438	assert.Nil(t, repos)
439	assert.Contains(t, err.Error(), "failed to list repositories")
440}
441
442func TestPutManifest_SkipsEventWhenDeployIntentSuppressed(t *testing.T) {
443	blobStorage := mocks.NewMockBlobStorage(t)
444	manifestStorage := mocks.NewMockManifestStorage(t)
445	eventBus := mocks.NewMockEventPublisher(t)
446
447	svc := NewService(blobStorage, manifestStorage, eventBus)
448
449	svc.SuppressDeployEvent("my-app")
450
451	manifestStorage.EXPECT().PutManifest("my-app", "latest", "application/vnd.oci.image.manifest.v1+json", mock.Anything).Return(nil)
452
453	manifest := &domain.Manifest{
454		Name:        "my-app",
455		Reference:   "latest",
456		ContentType: "application/vnd.oci.image.manifest.v1+json",
457		Data:        []byte(`{"schemaVersion":2}`),
458	}
459
460	_, err := svc.PutManifest(context.Background(), manifest)
461	require.NoError(t, err)
462
463	eventBus.AssertNotCalled(t, "Publish", mock.Anything, mock.Anything)
464}
465
466func TestSuppressDeployEvent_ClearsCorrectly(t *testing.T) {
467	blobStorage := mocks.NewMockBlobStorage(t)
468	manifestStorage := mocks.NewMockManifestStorage(t)
469	eventBus := mocks.NewMockEventPublisher(t)
470
471	svc := NewService(blobStorage, manifestStorage, eventBus)
472
473	svc.SuppressDeployEvent("my-app")
474	assert.True(t, svc.IsDeployEventSuppressed("my-app"))
475
476	svc.ClearDeployEventSuppression("my-app")
477	assert.False(t, svc.IsDeployEventSuppressed("my-app"))
478}

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