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}