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_AppendBlobChunk_PassesMaxBlobSize(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().AppendBlobChunk("myapp", "upload-id", mock.Anything, int64(4), int64(10)).Return(int64(4), nil)
318
319 length, err := svc.AppendBlobChunk(ctx, "myapp", "upload-id", strings.NewReader("data"), 4, 10)
320
321 assert.NoError(t, err)
322 assert.Equal(t, int64(4), length)
323}
324
325func TestService_AppendBlobChunk_MaxBlobSizeExceeded(t *testing.T) {
326 blobStorage := mocks.NewMockBlobStorage(t)
327 manifestStorage := mocks.NewMockManifestStorage(t)
328 eventBus := mocks.NewMockEventPublisher(t)
329
330 svc := NewService(blobStorage, manifestStorage, eventBus)
331 ctx := testContext()
332
333 blobStorage.EXPECT().AppendBlobChunk("myapp", "upload-id", mock.Anything, int64(11), int64(10)).Return(int64(0), domain.ErrBlobSizeExceeded)
334
335 _, err := svc.AppendBlobChunk(ctx, "myapp", "upload-id", strings.NewReader("too large"), 11, 10)
336
337 require.Error(t, err)
338 assert.ErrorIs(t, err, domain.ErrBlobSizeExceeded)
339}
340
341func TestService_FinishUpload_Success(t *testing.T) {
342 blobStorage := mocks.NewMockBlobStorage(t)
343 manifestStorage := mocks.NewMockManifestStorage(t)
344 eventBus := mocks.NewMockEventPublisher(t)
345
346 svc := NewService(blobStorage, manifestStorage, eventBus)
347 ctx := testContext()
348
349 blobStorage.EXPECT().FinishBlobUpload("1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil)
350
351 err := svc.FinishUpload(ctx, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
352
353 assert.NoError(t, err)
354}
355
356func TestService_FinishUpload_Error(t *testing.T) {
357 blobStorage := mocks.NewMockBlobStorage(t)
358 manifestStorage := mocks.NewMockManifestStorage(t)
359 eventBus := mocks.NewMockEventPublisher(t)
360
361 svc := NewService(blobStorage, manifestStorage, eventBus)
362 ctx := testContext()
363
364 blobStorage.EXPECT().FinishBlobUpload("1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(errors.New("digest mismatch"))
365
366 err := svc.FinishUpload(ctx, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
367
368 assert.Error(t, err)
369 assert.Contains(t, err.Error(), "failed to finish blob upload")
370}
371
372func TestService_CancelUpload_Success(t *testing.T) {
373 blobStorage := mocks.NewMockBlobStorage(t)
374 manifestStorage := mocks.NewMockManifestStorage(t)
375 eventBus := mocks.NewMockEventPublisher(t)
376
377 svc := NewService(blobStorage, manifestStorage, eventBus)
378 ctx := testContext()
379
380 blobStorage.EXPECT().CancelBlobUpload("1234567890-myapp").Return(nil)
381
382 err := svc.CancelUpload(ctx, "1234567890-myapp")
383
384 assert.NoError(t, err)
385}
386
387func TestService_CancelUpload_Error(t *testing.T) {
388 blobStorage := mocks.NewMockBlobStorage(t)
389 manifestStorage := mocks.NewMockManifestStorage(t)
390 eventBus := mocks.NewMockEventPublisher(t)
391
392 svc := NewService(blobStorage, manifestStorage, eventBus)
393 ctx := testContext()
394
395 blobStorage.EXPECT().CancelBlobUpload("1234567890-myapp").Return(errors.New("upload not found"))
396
397 err := svc.CancelUpload(ctx, "1234567890-myapp")
398
399 assert.Error(t, err)
400 assert.Contains(t, err.Error(), "failed to cancel blob upload")
401}
402
403func TestService_ListTags_Success(t *testing.T) {
404 blobStorage := mocks.NewMockBlobStorage(t)
405 manifestStorage := mocks.NewMockManifestStorage(t)
406 eventBus := mocks.NewMockEventPublisher(t)
407
408 svc := NewService(blobStorage, manifestStorage, eventBus)
409 ctx := testContext()
410
411 manifestStorage.EXPECT().ListTags("myapp").Return([]string{
412 "latest",
413 "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
414 "v1.0",
415 "v2.0",
416 }, nil)
417
418 tags, err := svc.ListTags(ctx, "myapp")
419
420 assert.NoError(t, err)
421 assert.Equal(t, []string{"latest", "v1.0", "v2.0"}, tags)
422}
423
424func TestService_ListTags_Error(t *testing.T) {
425 blobStorage := mocks.NewMockBlobStorage(t)
426 manifestStorage := mocks.NewMockManifestStorage(t)
427 eventBus := mocks.NewMockEventPublisher(t)
428
429 svc := NewService(blobStorage, manifestStorage, eventBus)
430 ctx := testContext()
431
432 manifestStorage.EXPECT().ListTags("myapp").Return(nil, errors.New("repository not found"))
433
434 tags, err := svc.ListTags(ctx, "myapp")
435
436 assert.Error(t, err)
437 assert.Nil(t, tags)
438 assert.Contains(t, err.Error(), "failed to list tags")
439}
440
441func TestService_ListRepositories_Success(t *testing.T) {
442 blobStorage := mocks.NewMockBlobStorage(t)
443 manifestStorage := mocks.NewMockManifestStorage(t)
444 eventBus := mocks.NewMockEventPublisher(t)
445
446 svc := NewService(blobStorage, manifestStorage, eventBus)
447 ctx := testContext()
448
449 manifestStorage.EXPECT().ListRepositories().Return([]string{"myapp", "otherapp", "thirdapp"}, nil)
450
451 repos, err := svc.ListRepositories(ctx)
452
453 assert.NoError(t, err)
454 assert.Equal(t, []string{"myapp", "otherapp", "thirdapp"}, repos)
455}
456
457func TestService_ListRepositories_Error(t *testing.T) {
458 blobStorage := mocks.NewMockBlobStorage(t)
459 manifestStorage := mocks.NewMockManifestStorage(t)
460 eventBus := mocks.NewMockEventPublisher(t)
461
462 svc := NewService(blobStorage, manifestStorage, eventBus)
463 ctx := testContext()
464
465 manifestStorage.EXPECT().ListRepositories().Return(nil, errors.New("storage error"))
466
467 repos, err := svc.ListRepositories(ctx)
468
469 assert.Error(t, err)
470 assert.Nil(t, repos)
471 assert.Contains(t, err.Error(), "failed to list repositories")
472}
473
474func TestPutManifest_SkipsEventWhenDeployIntentSuppressed(t *testing.T) {
475 blobStorage := mocks.NewMockBlobStorage(t)
476 manifestStorage := mocks.NewMockManifestStorage(t)
477 eventBus := mocks.NewMockEventPublisher(t)
478
479 svc := NewService(blobStorage, manifestStorage, eventBus)
480
481 svc.SuppressDeployEvent("my-app")
482
483 manifestStorage.EXPECT().PutManifest("my-app", "latest", "application/vnd.oci.image.manifest.v1+json", mock.Anything).Return(nil)
484
485 manifest := &domain.Manifest{
486 Name: "my-app",
487 Reference: "latest",
488 ContentType: "application/vnd.oci.image.manifest.v1+json",
489 Data: []byte(`{"schemaVersion":2}`),
490 }
491
492 _, err := svc.PutManifest(context.Background(), manifest)
493 require.NoError(t, err)
494
495 eventBus.AssertNotCalled(t, "Publish", mock.Anything, mock.Anything)
496}
497
498func TestSuppressDeployEvent_ClearsCorrectly(t *testing.T) {
499 blobStorage := mocks.NewMockBlobStorage(t)
500 manifestStorage := mocks.NewMockManifestStorage(t)
501 eventBus := mocks.NewMockEventPublisher(t)
502
503 svc := NewService(blobStorage, manifestStorage, eventBus)
504
505 svc.SuppressDeployEvent("my-app")
506 assert.True(t, svc.IsDeployEventSuppressed("my-app"))
507
508 svc.ClearDeployEventSuppression("my-app")
509 assert.False(t, svc.IsDeployEventSuppressed("my-app"))
510}