refactor(http): follow clean architecture in handlers
- Registry handler: remove blobStorage dependency, use RegistryService - Add GetBlobPath, AppendBlobChunk to RegistryService interface - PutManifest now returns digest (moved calculation to use case) - Config handler: add GetRoute for single route lookup - Add route validation (ErrRouteDomainEmpty, ErrRouteImageEmpty) - Update tests and regenerate mocks
- commit
- c642d3261b59281e740b534e142309e25ee53af4
- author
- bnema · · 2026-01-16 23:15 UTC
- parent
- 22b6b85
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
14 files · +554 -192
M internal/adapters/in/http/admin/handler.go +21 -15
| @@ -3,6 +3,7 @@ package admin | |||
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | 5 | "encoding/json" | |
| 6 | + | "errors" | |
| 6 | 7 | "net/http" | |
| 7 | 8 | "strings" | |
| 8 | 9 | ||
| @@ -134,20 +135,18 @@ func (h *Handler) handleRoutesGet(w http.ResponseWriter, r *http.Request, routeD | |||
| 134 | 135 | return | |
| 135 | 136 | } | |
| 136 | 137 | ||
| 137 | - | routes := h.configSvc.GetRoutes(ctx) | |
| 138 | - | ||
| 139 | 138 | if routeDomain == "" { | |
| 139 | + | routes := h.configSvc.GetRoutes(ctx) | |
| 140 | 140 | h.sendJSON(w, http.StatusOK, map[string]any{"routes": routes}) | |
| 141 | 141 | return | |
| 142 | 142 | } | |
| 143 | 143 | ||
| 144 | - | for _, route := range routes { | |
| 145 | - | if route.Domain == routeDomain { | |
| 146 | - | h.sendJSON(w, http.StatusOK, route) | |
| 147 | - | return | |
| 148 | - | } | |
| 144 | + | route, err := h.configSvc.GetRoute(ctx, routeDomain) | |
| 145 | + | if err != nil { | |
| 146 | + | h.sendError(w, http.StatusNotFound, "route not found") | |
| 147 | + | return | |
| 149 | 148 | } | |
| 150 | - | h.sendError(w, http.StatusNotFound, "route not found") | |
| 149 | + | h.sendJSON(w, http.StatusOK, route) | |
| 151 | 150 | } | |
| 152 | 151 | ||
| 153 | 152 | func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) { | |
| @@ -170,14 +169,14 @@ func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) { | |||
| 170 | 169 | return | |
| 171 | 170 | } | |
| 172 | 171 | ||
| 173 | - | if route.Domain == "" || route.Image == "" { | |
| 174 | - | h.sendError(w, http.StatusBadRequest, "domain and image are required") | |
| 175 | - | return | |
| 176 | - | } | |
| 177 | - | ||
| 178 | 172 | if err := h.configSvc.AddRoute(ctx, route); err != nil { | |
| 179 | 173 | log.Error().Err(err).Str("domain", route.Domain).Msg("failed to add route") | |
| 180 | - | h.sendError(w, http.StatusInternalServerError, "failed to add route") | |
| 174 | + | switch { | |
| 175 | + | case errors.Is(err, domain.ErrRouteDomainEmpty), errors.Is(err, domain.ErrRouteImageEmpty): | |
| 176 | + | h.sendError(w, http.StatusBadRequest, err.Error()) | |
| 177 | + | default: | |
| 178 | + | h.sendError(w, http.StatusInternalServerError, "failed to add route") | |
| 179 | + | } | |
| 181 | 180 | return | |
| 182 | 181 | } | |
| 183 | 182 | ||
| @@ -214,7 +213,14 @@ func (h *Handler) handleRoutesPut(w http.ResponseWriter, r *http.Request, routeD | |||
| 214 | 213 | ||
| 215 | 214 | if err := h.configSvc.UpdateRoute(ctx, route); err != nil { | |
| 216 | 215 | log.Error().Err(err).Str("domain", routeDomain).Msg("failed to update route") | |
| 217 | - | h.sendError(w, http.StatusInternalServerError, "failed to update route") | |
| 216 | + | switch { | |
| 217 | + | case errors.Is(err, domain.ErrRouteNotFound): | |
| 218 | + | h.sendError(w, http.StatusNotFound, "route not found") | |
| 219 | + | case errors.Is(err, domain.ErrRouteDomainEmpty), errors.Is(err, domain.ErrRouteImageEmpty): | |
| 220 | + | h.sendError(w, http.StatusBadRequest, err.Error()) | |
| 221 | + | default: | |
| 222 | + | h.sendError(w, http.StatusInternalServerError, "failed to update route") | |
| 223 | + | } | |
| 218 | 224 | return | |
| 219 | 225 | } | |
| 220 | 226 | ||
| @@ -3,6 +3,7 @@ package admin | |||||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | + | "errors" | |||
| 6 | "net/http" | 7 | "net/http" | ||
| 7 | "strings" | 8 | "strings" | ||
| 8 | 9 | ||||
| @@ -134,20 +135,18 @@ func (h *Handler) handleRoutesGet(w http.ResponseWriter, r *http.Request, routeD | |||||
| 134 | return | 135 | return | ||
| 135 | } | 136 | } | ||
| 136 | 137 | ||||
| 137 | - | routes := h.configSvc.GetRoutes(ctx) | |||
| 138 | - | ||||
| 139 | if routeDomain == "" { | 138 | if routeDomain == "" { | ||
| 139 | + | routes := h.configSvc.GetRoutes(ctx) | |||
| 140 | h.sendJSON(w, http.StatusOK, map[string]any{"routes": routes}) | 140 | h.sendJSON(w, http.StatusOK, map[string]any{"routes": routes}) | ||
| 141 | return | 141 | return | ||
| 142 | } | 142 | } | ||
| 143 | 143 | ||||
| 144 | - | for _, route := range routes { | 144 | + | route, err := h.configSvc.GetRoute(ctx, routeDomain) |
| 145 | - | if route.Domain == routeDomain { | 145 | + | if err != nil { |
| 146 | - | h.sendJSON(w, http.StatusOK, route) | 146 | + | h.sendError(w, http.StatusNotFound, "route not found") |
| 147 | - | return | 147 | + | return |
| 148 | - | } | |||
| 149 | } | 148 | } | ||
| 150 | - | h.sendError(w, http.StatusNotFound, "route not found") | 149 | + | h.sendJSON(w, http.StatusOK, route) |
| 151 | } | 150 | } | ||
| 152 | 151 | ||||
| 153 | func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) { | 152 | func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) { | ||
| @@ -170,14 +169,14 @@ func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) { | |||||
| 170 | return | 169 | return | ||
| 171 | } | 170 | } | ||
| 172 | 171 | ||||
| 173 | - | if route.Domain == "" || route.Image == "" { | |||
| 174 | - | h.sendError(w, http.StatusBadRequest, "domain and image are required") | |||
| 175 | - | return | |||
| 176 | - | } | |||
| 177 | - | ||||
| 178 | if err := h.configSvc.AddRoute(ctx, route); err != nil { | 172 | if err := h.configSvc.AddRoute(ctx, route); err != nil { | ||
| 179 | log.Error().Err(err).Str("domain", route.Domain).Msg("failed to add route") | 173 | log.Error().Err(err).Str("domain", route.Domain).Msg("failed to add route") | ||
| 180 | - | h.sendError(w, http.StatusInternalServerError, "failed to add route") | 174 | + | switch { |
| 175 | + | case errors.Is(err, domain.ErrRouteDomainEmpty), errors.Is(err, domain.ErrRouteImageEmpty): | |||
| 176 | + | h.sendError(w, http.StatusBadRequest, err.Error()) | |||
| 177 | + | default: | |||
| 178 | + | h.sendError(w, http.StatusInternalServerError, "failed to add route") | |||
| 179 | + | } | |||
| 181 | return | 180 | return | ||
| 182 | } | 181 | } | ||
| 183 | 182 | ||||
| @@ -214,7 +213,14 @@ func (h *Handler) handleRoutesPut(w http.ResponseWriter, r *http.Request, routeD | |||||
| 214 | 213 | ||||
| 215 | if err := h.configSvc.UpdateRoute(ctx, route); err != nil { | 214 | if err := h.configSvc.UpdateRoute(ctx, route); err != nil { | ||
| 216 | log.Error().Err(err).Str("domain", routeDomain).Msg("failed to update route") | 215 | log.Error().Err(err).Str("domain", routeDomain).Msg("failed to update route") | ||
| 217 | - | h.sendError(w, http.StatusInternalServerError, "failed to update route") | 216 | + | switch { |
| 217 | + | case errors.Is(err, domain.ErrRouteNotFound): | |||
| 218 | + | h.sendError(w, http.StatusNotFound, "route not found") | |||
| 219 | + | case errors.Is(err, domain.ErrRouteDomainEmpty), errors.Is(err, domain.ErrRouteImageEmpty): | |||
| 220 | + | h.sendError(w, http.StatusBadRequest, err.Error()) | |||
| 221 | + | default: | |||
| 222 | + | h.sendError(w, http.StatusInternalServerError, "failed to update route") | |||
| 223 | + | } | |||
| 218 | return | 224 | return | ||
| 219 | } | 225 | } | ||
| 220 | 226 | ||||
M internal/adapters/in/http/admin/handler_test.go +25 -19
| @@ -590,9 +590,9 @@ func TestHandler_RoutesGet_SingleRoute(t *testing.T) { | |||
| 590 | 590 | ||
| 591 | 591 | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | |
| 592 | 592 | ||
| 593 | - | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |
| 594 | - | {Domain: "app.example.com", Image: "app:latest"}, | |
| 595 | - | }) | |
| 593 | + | configSvc.EXPECT().GetRoute(mock.Anything, "app.example.com").Return(&domain.Route{ | |
| 594 | + | Domain: "app.example.com", Image: "app:latest", | |
| 595 | + | }, nil) | |
| 596 | 596 | ||
| 597 | 597 | req := httptest.NewRequest("GET", "/admin/routes/app.example.com", nil) | |
| 598 | 598 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | |
| @@ -617,7 +617,7 @@ func TestHandler_RoutesGet_NotFound(t *testing.T) { | |||
| 617 | 617 | ||
| 618 | 618 | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | |
| 619 | 619 | ||
| 620 | - | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{}) | |
| 620 | + | configSvc.EXPECT().GetRoute(mock.Anything, "nonexistent.example.com").Return(nil, domain.ErrRouteNotFound) | |
| 621 | 621 | ||
| 622 | 622 | req := httptest.NewRequest("GET", "/admin/routes/nonexistent.example.com", nil) | |
| 623 | 623 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | |
| @@ -629,33 +629,39 @@ func TestHandler_RoutesGet_NotFound(t *testing.T) { | |||
| 629 | 629 | } | |
| 630 | 630 | ||
| 631 | 631 | func TestHandler_RoutesPost_MissingFields(t *testing.T) { | |
| 632 | - | configSvc := inmocks.NewMockConfigService(t) | |
| 633 | - | authSvc := inmocks.NewMockAuthService(t) | |
| 634 | - | containerSvc := inmocks.NewMockContainerService(t) | |
| 635 | - | secretSvc := inmocks.NewMockSecretService(t) | |
| 636 | - | ||
| 637 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | |
| 638 | - | ||
| 639 | 632 | tests := []struct { | |
| 640 | - | name string | |
| 641 | - | body string | |
| 633 | + | name string | |
| 634 | + | body string | |
| 635 | + | mockErr error | |
| 642 | 636 | }{ | |
| 643 | 637 | { | |
| 644 | - | name: "missing domain", | |
| 645 | - | body: `{"image": "app:latest"}`, | |
| 638 | + | name: "missing domain", | |
| 639 | + | body: `{"image": "app:latest"}`, | |
| 640 | + | mockErr: domain.ErrRouteDomainEmpty, | |
| 646 | 641 | }, | |
| 647 | 642 | { | |
| 648 | - | name: "missing image", | |
| 649 | - | body: `{"domain": "app.example.com"}`, | |
| 643 | + | name: "missing image", | |
| 644 | + | body: `{"domain": "app.example.com"}`, | |
| 645 | + | mockErr: domain.ErrRouteImageEmpty, | |
| 650 | 646 | }, | |
| 651 | 647 | { | |
| 652 | - | name: "empty object", | |
| 653 | - | body: `{}`, | |
| 648 | + | name: "empty object", | |
| 649 | + | body: `{}`, | |
| 650 | + | mockErr: domain.ErrRouteDomainEmpty, | |
| 654 | 651 | }, | |
| 655 | 652 | } | |
| 656 | 653 | ||
| 657 | 654 | for _, tt := range tests { | |
| 658 | 655 | t.Run(tt.name, func(t *testing.T) { | |
| 656 | + | configSvc := inmocks.NewMockConfigService(t) | |
| 657 | + | authSvc := inmocks.NewMockAuthService(t) | |
| 658 | + | containerSvc := inmocks.NewMockContainerService(t) | |
| 659 | + | secretSvc := inmocks.NewMockSecretService(t) | |
| 660 | + | ||
| 661 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | |
| 662 | + | ||
| 663 | + | configSvc.EXPECT().AddRoute(mock.Anything, mock.Anything).Return(tt.mockErr) | |
| 664 | + | ||
| 659 | 665 | req := httptest.NewRequest("POST", "/admin/routes", bytes.NewBufferString(tt.body)) | |
| 660 | 666 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | |
| 661 | 667 | rec := httptest.NewRecorder() | |
| @@ -590,9 +590,9 @@ func TestHandler_RoutesGet_SingleRoute(t *testing.T) { | |||||
| 590 | 590 | ||||
| 591 | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | 591 | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | ||
| 592 | 592 | ||||
| 593 | - | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | 593 | + | configSvc.EXPECT().GetRoute(mock.Anything, "app.example.com").Return(&domain.Route{ |
| 594 | - | {Domain: "app.example.com", Image: "app:latest"}, | 594 | + | Domain: "app.example.com", Image: "app:latest", |
| 595 | - | }) | 595 | + | }, nil) |
| 596 | 596 | ||||
| 597 | req := httptest.NewRequest("GET", "/admin/routes/app.example.com", nil) | 597 | req := httptest.NewRequest("GET", "/admin/routes/app.example.com", nil) | ||
| 598 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | 598 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | ||
| @@ -617,7 +617,7 @@ func TestHandler_RoutesGet_NotFound(t *testing.T) { | |||||
| 617 | 617 | ||||
| 618 | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | 618 | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | ||
| 619 | 619 | ||||
| 620 | - | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{}) | 620 | + | configSvc.EXPECT().GetRoute(mock.Anything, "nonexistent.example.com").Return(nil, domain.ErrRouteNotFound) |
| 621 | 621 | ||||
| 622 | req := httptest.NewRequest("GET", "/admin/routes/nonexistent.example.com", nil) | 622 | req := httptest.NewRequest("GET", "/admin/routes/nonexistent.example.com", nil) | ||
| 623 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | 623 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | ||
| @@ -629,33 +629,39 @@ func TestHandler_RoutesGet_NotFound(t *testing.T) { | |||||
| 629 | } | 629 | } | ||
| 630 | 630 | ||||
| 631 | func TestHandler_RoutesPost_MissingFields(t *testing.T) { | 631 | func TestHandler_RoutesPost_MissingFields(t *testing.T) { | ||
| 632 | - | configSvc := inmocks.NewMockConfigService(t) | |||
| 633 | - | authSvc := inmocks.NewMockAuthService(t) | |||
| 634 | - | containerSvc := inmocks.NewMockContainerService(t) | |||
| 635 | - | secretSvc := inmocks.NewMockSecretService(t) | |||
| 636 | - | ||||
| 637 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | |||
| 638 | - | ||||
| 639 | tests := []struct { | 632 | tests := []struct { | ||
| 640 | - | name string | 633 | + | name string |
| 641 | - | body string | 634 | + | body string |
| 635 | + | mockErr error | |||
| 642 | }{ | 636 | }{ | ||
| 643 | { | 637 | { | ||
| 644 | - | name: "missing domain", | 638 | + | name: "missing domain", |
| 645 | - | body: `{"image": "app:latest"}`, | 639 | + | body: `{"image": "app:latest"}`, |
| 640 | + | mockErr: domain.ErrRouteDomainEmpty, | |||
| 646 | }, | 641 | }, | ||
| 647 | { | 642 | { | ||
| 648 | - | name: "missing image", | 643 | + | name: "missing image", |
| 649 | - | body: `{"domain": "app.example.com"}`, | 644 | + | body: `{"domain": "app.example.com"}`, |
| 645 | + | mockErr: domain.ErrRouteImageEmpty, | |||
| 650 | }, | 646 | }, | ||
| 651 | { | 647 | { | ||
| 652 | - | name: "empty object", | 648 | + | name: "empty object", |
| 653 | - | body: `{}`, | 649 | + | body: `{}`, |
| 650 | + | mockErr: domain.ErrRouteDomainEmpty, | |||
| 654 | }, | 651 | }, | ||
| 655 | } | 652 | } | ||
| 656 | 653 | ||||
| 657 | for _, tt := range tests { | 654 | for _, tt := range tests { | ||
| 658 | t.Run(tt.name, func(t *testing.T) { | 655 | t.Run(tt.name, func(t *testing.T) { | ||
| 656 | + | configSvc := inmocks.NewMockConfigService(t) | |||
| 657 | + | authSvc := inmocks.NewMockAuthService(t) | |||
| 658 | + | containerSvc := inmocks.NewMockContainerService(t) | |||
| 659 | + | secretSvc := inmocks.NewMockSecretService(t) | |||
| 660 | + | ||||
| 661 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, testLogger()) | |||
| 662 | + | ||||
| 663 | + | configSvc.EXPECT().AddRoute(mock.Anything, mock.Anything).Return(tt.mockErr) | |||
| 664 | + | ||||
| 659 | req := httptest.NewRequest("POST", "/admin/routes", bytes.NewBufferString(tt.body)) | 665 | req := httptest.NewRequest("POST", "/admin/routes", bytes.NewBufferString(tt.body)) | ||
| 660 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | 666 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | ||
| 661 | rec := httptest.NewRecorder() | 667 | rec := httptest.NewRecorder() | ||
M internal/adapters/in/http/registry/handler.go +4 -13
| @@ -2,7 +2,6 @@ | |||
| 2 | 2 | package registry | |
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | - | "crypto/sha256" | |
| 6 | 5 | "encoding/json" | |
| 7 | 6 | "errors" | |
| 8 | 7 | "fmt" | |
| @@ -14,7 +13,6 @@ import ( | |||
| 14 | 13 | "github.com/bnema/zerowrap" | |
| 15 | 14 | ||
| 16 | 15 | "gordon/internal/boundaries/in" | |
| 17 | - | "gordon/internal/boundaries/out" | |
| 18 | 16 | "gordon/internal/domain" | |
| 19 | 17 | "gordon/pkg/manifest" | |
| 20 | 18 | "gordon/pkg/validation" | |
| @@ -30,22 +28,16 @@ const ( | |||
| 30 | 28 | // Handler implements the HTTP handler for Docker Registry API v2. | |
| 31 | 29 | type Handler struct { | |
| 32 | 30 | registrySvc in.RegistryService | |
| 33 | - | blobStorage out.BlobStorage | |
| 34 | - | eventBus out.EventPublisher | |
| 35 | 31 | log zerowrap.Logger | |
| 36 | 32 | } | |
| 37 | 33 | ||
| 38 | 34 | // NewHandler creates a new registry HTTP handler. | |
| 39 | 35 | func NewHandler( | |
| 40 | 36 | registrySvc in.RegistryService, | |
| 41 | - | blobStorage out.BlobStorage, | |
| 42 | - | eventBus out.EventPublisher, | |
| 43 | 37 | log zerowrap.Logger, | |
| 44 | 38 | ) *Handler { | |
| 45 | 39 | return &Handler{ | |
| 46 | 40 | registrySvc: registrySvc, | |
| 47 | - | blobStorage: blobStorage, | |
| 48 | - | eventBus: eventBus, | |
| 49 | 41 | log: log, | |
| 50 | 42 | } | |
| 51 | 43 | } | |
| @@ -331,14 +323,13 @@ func (h *Handler) handlePutManifest(w http.ResponseWriter, r *http.Request) { | |||
| 331 | 323 | Annotations: annotations, | |
| 332 | 324 | } | |
| 333 | 325 | ||
| 334 | - | if err := h.registrySvc.PutManifest(ctx, manifestObj); err != nil { | |
| 326 | + | digest, err := h.registrySvc.PutManifest(ctx, manifestObj) | |
| 327 | + | if err != nil { | |
| 335 | 328 | log.Error().Err(err).Str("name", name).Str("reference", reference).Msg("failed to store manifest") | |
| 336 | 329 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) | |
| 337 | 330 | return | |
| 338 | 331 | } | |
| 339 | 332 | ||
| 340 | - | // Calculate digest for Docker-Content-Digest header | |
| 341 | - | digest := fmt.Sprintf("sha256:%x", sha256.Sum256(data)) | |
| 342 | 333 | w.Header().Set("Docker-Content-Digest", digest) | |
| 343 | 334 | w.Header().Set("Location", fmt.Sprintf("/v2/%s/manifests/%s", name, reference)) | |
| 344 | 335 | w.WriteHeader(http.StatusCreated) | |
| @@ -352,7 +343,7 @@ func (h *Handler) handleGetBlob(w http.ResponseWriter, r *http.Request) { | |||
| 352 | 343 | ||
| 353 | 344 | log.Debug().Str("name", name).Str("digest", digest).Msg("GET blob") | |
| 354 | 345 | ||
| 355 | - | path, err := h.blobStorage.GetBlobPath(digest) | |
| 346 | + | path, err := h.registrySvc.GetBlobPath(ctx, digest) | |
| 356 | 347 | if err != nil { | |
| 357 | 348 | log.Warn().Err(err).Str("name", name).Str("digest", digest).Msg("blob not found") | |
| 358 | 349 | http.NotFound(w, r) | |
| @@ -422,7 +413,7 @@ func (h *Handler) handleBlobUpload(w http.ResponseWriter, r *http.Request) { | |||
| 422 | 413 | } | |
| 423 | 414 | ||
| 424 | 415 | // Append the chunk to the upload | |
| 425 | - | length, err := h.blobStorage.AppendBlobChunk(name, uuid, chunk) | |
| 416 | + | length, err := h.registrySvc.AppendBlobChunk(ctx, name, uuid, chunk) | |
| 426 | 417 | if err != nil { | |
| 427 | 418 | log.Error().Err(err).Msg("failed to append blob chunk") | |
| 428 | 419 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) | |
| @@ -2,7 +2,6 @@ | |||||
| 2 | package registry | 2 | package registry | ||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | - | "crypto/sha256" | |||
| 6 | "encoding/json" | 5 | "encoding/json" | ||
| 7 | "errors" | 6 | "errors" | ||
| 8 | "fmt" | 7 | "fmt" | ||
| @@ -14,7 +13,6 @@ import ( | |||||
| 14 | "github.com/bnema/zerowrap" | 13 | "github.com/bnema/zerowrap" | ||
| 15 | 14 | ||||
| 16 | "gordon/internal/boundaries/in" | 15 | "gordon/internal/boundaries/in" | ||
| 17 | - | "gordon/internal/boundaries/out" | |||
| 18 | "gordon/internal/domain" | 16 | "gordon/internal/domain" | ||
| 19 | "gordon/pkg/manifest" | 17 | "gordon/pkg/manifest" | ||
| 20 | "gordon/pkg/validation" | 18 | "gordon/pkg/validation" | ||
| @@ -30,22 +28,16 @@ const ( | |||||
| 30 | // Handler implements the HTTP handler for Docker Registry API v2. | 28 | // Handler implements the HTTP handler for Docker Registry API v2. | ||
| 31 | type Handler struct { | 29 | type Handler struct { | ||
| 32 | registrySvc in.RegistryService | 30 | registrySvc in.RegistryService | ||
| 33 | - | blobStorage out.BlobStorage | |||
| 34 | - | eventBus out.EventPublisher | |||
| 35 | log zerowrap.Logger | 31 | log zerowrap.Logger | ||
| 36 | } | 32 | } | ||
| 37 | 33 | ||||
| 38 | // NewHandler creates a new registry HTTP handler. | 34 | // NewHandler creates a new registry HTTP handler. | ||
| 39 | func NewHandler( | 35 | func NewHandler( | ||
| 40 | registrySvc in.RegistryService, | 36 | registrySvc in.RegistryService, | ||
| 41 | - | blobStorage out.BlobStorage, | |||
| 42 | - | eventBus out.EventPublisher, | |||
| 43 | log zerowrap.Logger, | 37 | log zerowrap.Logger, | ||
| 44 | ) *Handler { | 38 | ) *Handler { | ||
| 45 | return &Handler{ | 39 | return &Handler{ | ||
| 46 | registrySvc: registrySvc, | 40 | registrySvc: registrySvc, | ||
| 47 | - | blobStorage: blobStorage, | |||
| 48 | - | eventBus: eventBus, | |||
| 49 | log: log, | 41 | log: log, | ||
| 50 | } | 42 | } | ||
| 51 | } | 43 | } | ||
| @@ -331,14 +323,13 @@ func (h *Handler) handlePutManifest(w http.ResponseWriter, r *http.Request) { | |||||
| 331 | Annotations: annotations, | 323 | Annotations: annotations, | ||
| 332 | } | 324 | } | ||
| 333 | 325 | ||||
| 334 | - | if err := h.registrySvc.PutManifest(ctx, manifestObj); err != nil { | 326 | + | digest, err := h.registrySvc.PutManifest(ctx, manifestObj) |
| 327 | + | if err != nil { | |||
| 335 | log.Error().Err(err).Str("name", name).Str("reference", reference).Msg("failed to store manifest") | 328 | log.Error().Err(err).Str("name", name).Str("reference", reference).Msg("failed to store manifest") | ||
| 336 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) | 329 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||
| 337 | return | 330 | return | ||
| 338 | } | 331 | } | ||
| 339 | 332 | ||||
| 340 | - | // Calculate digest for Docker-Content-Digest header | |||
| 341 | - | digest := fmt.Sprintf("sha256:%x", sha256.Sum256(data)) | |||
| 342 | w.Header().Set("Docker-Content-Digest", digest) | 333 | w.Header().Set("Docker-Content-Digest", digest) | ||
| 343 | w.Header().Set("Location", fmt.Sprintf("/v2/%s/manifests/%s", name, reference)) | 334 | w.Header().Set("Location", fmt.Sprintf("/v2/%s/manifests/%s", name, reference)) | ||
| 344 | w.WriteHeader(http.StatusCreated) | 335 | w.WriteHeader(http.StatusCreated) | ||
| @@ -352,7 +343,7 @@ func (h *Handler) handleGetBlob(w http.ResponseWriter, r *http.Request) { | |||||
| 352 | 343 | ||||
| 353 | log.Debug().Str("name", name).Str("digest", digest).Msg("GET blob") | 344 | log.Debug().Str("name", name).Str("digest", digest).Msg("GET blob") | ||
| 354 | 345 | ||||
| 355 | - | path, err := h.blobStorage.GetBlobPath(digest) | 346 | + | path, err := h.registrySvc.GetBlobPath(ctx, digest) |
| 356 | if err != nil { | 347 | if err != nil { | ||
| 357 | log.Warn().Err(err).Str("name", name).Str("digest", digest).Msg("blob not found") | 348 | log.Warn().Err(err).Str("name", name).Str("digest", digest).Msg("blob not found") | ||
| 358 | http.NotFound(w, r) | 349 | http.NotFound(w, r) | ||
| @@ -422,7 +413,7 @@ func (h *Handler) handleBlobUpload(w http.ResponseWriter, r *http.Request) { | |||||
| 422 | } | 413 | } | ||
| 423 | 414 | ||||
| 424 | // Append the chunk to the upload | 415 | // Append the chunk to the upload | ||
| 425 | - | length, err := h.blobStorage.AppendBlobChunk(name, uuid, chunk) | 416 | + | length, err := h.registrySvc.AppendBlobChunk(ctx, name, uuid, chunk) |
| 426 | if err != nil { | 417 | if err != nil { | ||
| 427 | log.Error().Err(err).Msg("failed to append blob chunk") | 418 | log.Error().Err(err).Msg("failed to append blob chunk") | ||
| 428 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) | 419 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||
M internal/adapters/in/http/registry/handler_test.go +29 -74
| @@ -12,7 +12,6 @@ import ( | |||
| 12 | 12 | "github.com/stretchr/testify/mock" | |
| 13 | 13 | ||
| 14 | 14 | inmocks "gordon/internal/boundaries/in/mocks" | |
| 15 | - | "gordon/internal/boundaries/out/mocks" | |
| 16 | 15 | "gordon/internal/domain" | |
| 17 | 16 | ) | |
| 18 | 17 | ||
| @@ -22,10 +21,8 @@ func testLogger() zerowrap.Logger { | |||
| 22 | 21 | ||
| 23 | 22 | func TestHandler_Base(t *testing.T) { | |
| 24 | 23 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 25 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 26 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 27 | 24 | ||
| 28 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 25 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 29 | 26 | ||
| 30 | 27 | req := httptest.NewRequest("GET", "/v2/", nil) | |
| 31 | 28 | rec := httptest.NewRecorder() | |
| @@ -38,10 +35,8 @@ func TestHandler_Base(t *testing.T) { | |||
| 38 | 35 | ||
| 39 | 36 | func TestHandler_NotFound(t *testing.T) { | |
| 40 | 37 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 41 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 42 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 43 | 38 | ||
| 44 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 39 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 45 | 40 | ||
| 46 | 41 | req := httptest.NewRequest("GET", "/v2/unknown/path", nil) | |
| 47 | 42 | rec := httptest.NewRecorder() | |
| @@ -53,10 +48,8 @@ func TestHandler_NotFound(t *testing.T) { | |||
| 53 | 48 | ||
| 54 | 49 | func TestHandler_GetManifest_Success(t *testing.T) { | |
| 55 | 50 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 56 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 57 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 58 | 51 | ||
| 59 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 52 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 60 | 53 | ||
| 61 | 54 | manifestData := []byte(`{"schemaVersion": 2}`) | |
| 62 | 55 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "latest").Return(&domain.Manifest{ | |
| @@ -78,10 +71,8 @@ func TestHandler_GetManifest_Success(t *testing.T) { | |||
| 78 | 71 | ||
| 79 | 72 | func TestHandler_GetManifest_HEAD(t *testing.T) { | |
| 80 | 73 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 81 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 82 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 83 | 74 | ||
| 84 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 75 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 85 | 76 | ||
| 86 | 77 | manifestData := []byte(`{"schemaVersion": 2}`) | |
| 87 | 78 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "latest").Return(&domain.Manifest{ | |
| @@ -104,10 +95,8 @@ func TestHandler_GetManifest_HEAD(t *testing.T) { | |||
| 104 | 95 | ||
| 105 | 96 | func TestHandler_GetManifest_NotFound(t *testing.T) { | |
| 106 | 97 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 107 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 108 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 109 | 98 | ||
| 110 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 99 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 111 | 100 | ||
| 112 | 101 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "notexists").Return(nil, assert.AnError) | |
| 113 | 102 | ||
| @@ -121,10 +110,8 @@ func TestHandler_GetManifest_NotFound(t *testing.T) { | |||
| 121 | 110 | ||
| 122 | 111 | func TestHandler_GetManifest_NestedName(t *testing.T) { | |
| 123 | 112 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 124 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 125 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 126 | 113 | ||
| 127 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 114 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 128 | 115 | ||
| 129 | 116 | manifestData := []byte(`{"schemaVersion": 2}`) | |
| 130 | 117 | registrySvc.EXPECT().GetManifest(mock.Anything, "org/project/app", "v1.0").Return(&domain.Manifest{ | |
| @@ -144,15 +131,13 @@ func TestHandler_GetManifest_NestedName(t *testing.T) { | |||
| 144 | 131 | ||
| 145 | 132 | func TestHandler_PutManifest_Success(t *testing.T) { | |
| 146 | 133 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 147 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 148 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 149 | 134 | ||
| 150 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 135 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 151 | 136 | ||
| 152 | 137 | manifestData := []byte(`{"schemaVersion": 2}`) | |
| 153 | 138 | registrySvc.EXPECT().PutManifest(mock.Anything, mock.MatchedBy(func(m *domain.Manifest) bool { | |
| 154 | 139 | return m.Name == "myapp" && m.Reference == "latest" && m.ContentType == "application/vnd.docker.distribution.manifest.v2+json" | |
| 155 | - | })).Return(nil) | |
| 140 | + | })).Return("sha256:abc123", nil) | |
| 156 | 141 | ||
| 157 | 142 | req := httptest.NewRequest("PUT", "/v2/myapp/manifests/latest", bytes.NewReader(manifestData)) | |
| 158 | 143 | req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") | |
| @@ -161,16 +146,14 @@ func TestHandler_PutManifest_Success(t *testing.T) { | |||
| 161 | 146 | handler.ServeHTTP(rec, req) | |
| 162 | 147 | ||
| 163 | 148 | assert.Equal(t, http.StatusCreated, rec.Code) | |
| 164 | - | assert.NotEmpty(t, rec.Header().Get("Docker-Content-Digest")) | |
| 149 | + | assert.Equal(t, "sha256:abc123", rec.Header().Get("Docker-Content-Digest")) | |
| 165 | 150 | assert.Contains(t, rec.Header().Get("Location"), "/v2/myapp/manifests/latest") | |
| 166 | 151 | } | |
| 167 | 152 | ||
| 168 | 153 | func TestHandler_PutManifest_MissingContentType(t *testing.T) { | |
| 169 | 154 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 170 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 171 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 172 | 155 | ||
| 173 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 156 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 174 | 157 | ||
| 175 | 158 | manifestData := []byte(`{"schemaVersion": 2}`) | |
| 176 | 159 | ||
| @@ -185,13 +168,11 @@ func TestHandler_PutManifest_MissingContentType(t *testing.T) { | |||
| 185 | 168 | ||
| 186 | 169 | func TestHandler_PutManifest_StorageError(t *testing.T) { | |
| 187 | 170 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 188 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 189 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 190 | 171 | ||
| 191 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 172 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 192 | 173 | ||
| 193 | 174 | manifestData := []byte(`{"schemaVersion": 2}`) | |
| 194 | - | registrySvc.EXPECT().PutManifest(mock.Anything, mock.Anything).Return(assert.AnError) | |
| 175 | + | registrySvc.EXPECT().PutManifest(mock.Anything, mock.Anything).Return("", assert.AnError) | |
| 195 | 176 | ||
| 196 | 177 | req := httptest.NewRequest("PUT", "/v2/myapp/manifests/latest", bytes.NewReader(manifestData)) | |
| 197 | 178 | req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") | |
| @@ -204,10 +185,8 @@ func TestHandler_PutManifest_StorageError(t *testing.T) { | |||
| 204 | 185 | ||
| 205 | 186 | func TestHandler_ManifestRoutes_MethodNotAllowed(t *testing.T) { | |
| 206 | 187 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 207 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 208 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 209 | 188 | ||
| 210 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 189 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 211 | 190 | ||
| 212 | 191 | req := httptest.NewRequest("DELETE", "/v2/myapp/manifests/latest", nil) | |
| 213 | 192 | rec := httptest.NewRecorder() | |
| @@ -219,10 +198,8 @@ func TestHandler_ManifestRoutes_MethodNotAllowed(t *testing.T) { | |||
| 219 | 198 | ||
| 220 | 199 | func TestHandler_GetBlob_Success(t *testing.T) { | |
| 221 | 200 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 222 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 223 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 224 | 201 | ||
| 225 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 202 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 226 | 203 | ||
| 227 | 204 | // Create a temp file for http.ServeFile | |
| 228 | 205 | tmpFile, err := os.CreateTemp("", "blob-*") | |
| @@ -233,7 +210,7 @@ func TestHandler_GetBlob_Success(t *testing.T) { | |||
| 233 | 210 | _, err = tmpFile.Write(blobContent) | |
| 234 | 211 | assert.NoError(t, err) | |
| 235 | 212 | ||
| 236 | - | blobStorage.EXPECT().GetBlobPath("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(tmpFile.Name(), nil) | |
| 213 | + | registrySvc.EXPECT().GetBlobPath(mock.Anything, "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(tmpFile.Name(), nil) | |
| 237 | 214 | ||
| 238 | 215 | req := httptest.NewRequest("GET", "/v2/myapp/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", nil) | |
| 239 | 216 | rec := httptest.NewRecorder() | |
| @@ -246,12 +223,10 @@ func TestHandler_GetBlob_Success(t *testing.T) { | |||
| 246 | 223 | ||
| 247 | 224 | func TestHandler_GetBlob_NotFound(t *testing.T) { | |
| 248 | 225 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 249 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 250 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 251 | 226 | ||
| 252 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 227 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 253 | 228 | ||
| 254 | - | blobStorage.EXPECT().GetBlobPath("sha256:0000000000000000000000000000000000000000000000000000000000000000").Return("", assert.AnError) | |
| 229 | + | registrySvc.EXPECT().GetBlobPath(mock.Anything, "sha256:0000000000000000000000000000000000000000000000000000000000000000").Return("", assert.AnError) | |
| 255 | 230 | ||
| 256 | 231 | req := httptest.NewRequest("GET", "/v2/myapp/blobs/sha256:0000000000000000000000000000000000000000000000000000000000000000", nil) | |
| 257 | 232 | rec := httptest.NewRecorder() | |
| @@ -263,10 +238,8 @@ func TestHandler_GetBlob_NotFound(t *testing.T) { | |||
| 263 | 238 | ||
| 264 | 239 | func TestHandler_BlobRoutes_MethodNotAllowed(t *testing.T) { | |
| 265 | 240 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 266 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 267 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 268 | 241 | ||
| 269 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 242 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 270 | 243 | ||
| 271 | 244 | req := httptest.NewRequest("POST", "/v2/myapp/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", nil) | |
| 272 | 245 | rec := httptest.NewRecorder() | |
| @@ -278,10 +251,8 @@ func TestHandler_BlobRoutes_MethodNotAllowed(t *testing.T) { | |||
| 278 | 251 | ||
| 279 | 252 | func TestHandler_StartBlobUpload_Success(t *testing.T) { | |
| 280 | 253 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 281 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 282 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 283 | 254 | ||
| 284 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 255 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 285 | 256 | ||
| 286 | 257 | registrySvc.EXPECT().StartUpload(mock.Anything, "myapp").Return("1234567890-myapp", nil) | |
| 287 | 258 | ||
| @@ -297,10 +268,8 @@ func TestHandler_StartBlobUpload_Success(t *testing.T) { | |||
| 297 | 268 | ||
| 298 | 269 | func TestHandler_StartBlobUpload_Error(t *testing.T) { | |
| 299 | 270 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 300 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 301 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 302 | 271 | ||
| 303 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 272 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 304 | 273 | ||
| 305 | 274 | registrySvc.EXPECT().StartUpload(mock.Anything, "myapp").Return("", assert.AnError) | |
| 306 | 275 | ||
| @@ -314,13 +283,11 @@ func TestHandler_StartBlobUpload_Error(t *testing.T) { | |||
| 314 | 283 | ||
| 315 | 284 | func TestHandler_BlobUpload_PATCH(t *testing.T) { | |
| 316 | 285 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 317 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 318 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 319 | 286 | ||
| 320 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 287 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 321 | 288 | ||
| 322 | 289 | chunkData := []byte("chunk content") | |
| 323 | - | blobStorage.EXPECT().AppendBlobChunk("myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) | |
| 290 | + | registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) | |
| 324 | 291 | ||
| 325 | 292 | req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/1234567890-myapp", bytes.NewReader(chunkData)) | |
| 326 | 293 | rec := httptest.NewRecorder() | |
| @@ -333,13 +300,11 @@ func TestHandler_BlobUpload_PATCH(t *testing.T) { | |||
| 333 | 300 | ||
| 334 | 301 | func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) { | |
| 335 | 302 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 336 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 337 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 338 | 303 | ||
| 339 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 304 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 340 | 305 | ||
| 341 | 306 | chunkData := []byte("final chunk") | |
| 342 | - | blobStorage.EXPECT().AppendBlobChunk("myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) | |
| 307 | + | registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) | |
| 343 | 308 | registrySvc.EXPECT().FinishUpload(mock.Anything, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil) | |
| 344 | 309 | ||
| 345 | 310 | req := httptest.NewRequest("PUT", "/v2/myapp/blobs/uploads/1234567890-myapp?digest=sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", bytes.NewReader(chunkData)) | |
| @@ -354,10 +319,8 @@ func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) { | |||
| 354 | 319 | ||
| 355 | 320 | func TestHandler_BlobUpload_PUT_DigestMismatch(t *testing.T) { | |
| 356 | 321 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 357 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 358 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 359 | 322 | ||
| 360 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 323 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 361 | 324 | ||
| 362 | 325 | chunkData := []byte("final chunk") | |
| 363 | 326 | // Note: Invalid digest format is rejected by validation before reaching storage layer | |
| @@ -373,10 +336,8 @@ func TestHandler_BlobUpload_PUT_DigestMismatch(t *testing.T) { | |||
| 373 | 336 | ||
| 374 | 337 | func TestHandler_ListTags_Success(t *testing.T) { | |
| 375 | 338 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 376 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 377 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 378 | 339 | ||
| 379 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 340 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 380 | 341 | ||
| 381 | 342 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return([]string{"latest", "v1.0", "v2.0"}, nil) | |
| 382 | 343 | ||
| @@ -393,10 +354,8 @@ func TestHandler_ListTags_Success(t *testing.T) { | |||
| 393 | 354 | ||
| 394 | 355 | func TestHandler_ListTags_NotFound(t *testing.T) { | |
| 395 | 356 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 396 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 397 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 398 | 357 | ||
| 399 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 358 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 400 | 359 | ||
| 401 | 360 | registrySvc.EXPECT().ListTags(mock.Anything, "notexists").Return(nil, assert.AnError) | |
| 402 | 361 | ||
| @@ -410,10 +369,8 @@ func TestHandler_ListTags_NotFound(t *testing.T) { | |||
| 410 | 369 | ||
| 411 | 370 | func TestHandler_ListTags_MethodNotAllowed(t *testing.T) { | |
| 412 | 371 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 413 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 414 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 415 | 372 | ||
| 416 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 373 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 417 | 374 | ||
| 418 | 375 | req := httptest.NewRequest("POST", "/v2/myapp/tags/list", nil) | |
| 419 | 376 | rec := httptest.NewRecorder() | |
| @@ -425,10 +382,8 @@ func TestHandler_ListTags_MethodNotAllowed(t *testing.T) { | |||
| 425 | 382 | ||
| 426 | 383 | func TestHandler_RegisterRoutes(t *testing.T) { | |
| 427 | 384 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 428 | - | blobStorage := mocks.NewMockBlobStorage(t) | |
| 429 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 430 | 385 | ||
| 431 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | |
| 386 | + | handler := NewHandler(registrySvc, testLogger()) | |
| 432 | 387 | ||
| 433 | 388 | mux := http.NewServeMux() | |
| 434 | 389 | handler.RegisterRoutes(mux) | |
| @@ -12,7 +12,6 @@ import ( | |||||
| 12 | "github.com/stretchr/testify/mock" | 12 | "github.com/stretchr/testify/mock" | ||
| 13 | 13 | ||||
| 14 | inmocks "gordon/internal/boundaries/in/mocks" | 14 | inmocks "gordon/internal/boundaries/in/mocks" | ||
| 15 | - | "gordon/internal/boundaries/out/mocks" | |||
| 16 | "gordon/internal/domain" | 15 | "gordon/internal/domain" | ||
| 17 | ) | 16 | ) | ||
| 18 | 17 | ||||
| @@ -22,10 +21,8 @@ func testLogger() zerowrap.Logger { | |||||
| 22 | 21 | ||||
| 23 | func TestHandler_Base(t *testing.T) { | 22 | func TestHandler_Base(t *testing.T) { | ||
| 24 | registrySvc := inmocks.NewMockRegistryService(t) | 23 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 25 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 26 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 27 | 24 | ||||
| 28 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 25 | + | handler := NewHandler(registrySvc, testLogger()) |
| 29 | 26 | ||||
| 30 | req := httptest.NewRequest("GET", "/v2/", nil) | 27 | req := httptest.NewRequest("GET", "/v2/", nil) | ||
| 31 | rec := httptest.NewRecorder() | 28 | rec := httptest.NewRecorder() | ||
| @@ -38,10 +35,8 @@ func TestHandler_Base(t *testing.T) { | |||||
| 38 | 35 | ||||
| 39 | func TestHandler_NotFound(t *testing.T) { | 36 | func TestHandler_NotFound(t *testing.T) { | ||
| 40 | registrySvc := inmocks.NewMockRegistryService(t) | 37 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 41 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 42 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 43 | 38 | ||||
| 44 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 39 | + | handler := NewHandler(registrySvc, testLogger()) |
| 45 | 40 | ||||
| 46 | req := httptest.NewRequest("GET", "/v2/unknown/path", nil) | 41 | req := httptest.NewRequest("GET", "/v2/unknown/path", nil) | ||
| 47 | rec := httptest.NewRecorder() | 42 | rec := httptest.NewRecorder() | ||
| @@ -53,10 +48,8 @@ func TestHandler_NotFound(t *testing.T) { | |||||
| 53 | 48 | ||||
| 54 | func TestHandler_GetManifest_Success(t *testing.T) { | 49 | func TestHandler_GetManifest_Success(t *testing.T) { | ||
| 55 | registrySvc := inmocks.NewMockRegistryService(t) | 50 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 56 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 57 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 58 | 51 | ||||
| 59 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 52 | + | handler := NewHandler(registrySvc, testLogger()) |
| 60 | 53 | ||||
| 61 | manifestData := []byte(`{"schemaVersion": 2}`) | 54 | manifestData := []byte(`{"schemaVersion": 2}`) | ||
| 62 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "latest").Return(&domain.Manifest{ | 55 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "latest").Return(&domain.Manifest{ | ||
| @@ -78,10 +71,8 @@ func TestHandler_GetManifest_Success(t *testing.T) { | |||||
| 78 | 71 | ||||
| 79 | func TestHandler_GetManifest_HEAD(t *testing.T) { | 72 | func TestHandler_GetManifest_HEAD(t *testing.T) { | ||
| 80 | registrySvc := inmocks.NewMockRegistryService(t) | 73 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 81 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 82 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 83 | 74 | ||||
| 84 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 75 | + | handler := NewHandler(registrySvc, testLogger()) |
| 85 | 76 | ||||
| 86 | manifestData := []byte(`{"schemaVersion": 2}`) | 77 | manifestData := []byte(`{"schemaVersion": 2}`) | ||
| 87 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "latest").Return(&domain.Manifest{ | 78 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "latest").Return(&domain.Manifest{ | ||
| @@ -104,10 +95,8 @@ func TestHandler_GetManifest_HEAD(t *testing.T) { | |||||
| 104 | 95 | ||||
| 105 | func TestHandler_GetManifest_NotFound(t *testing.T) { | 96 | func TestHandler_GetManifest_NotFound(t *testing.T) { | ||
| 106 | registrySvc := inmocks.NewMockRegistryService(t) | 97 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 107 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 108 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 109 | 98 | ||||
| 110 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 99 | + | handler := NewHandler(registrySvc, testLogger()) |
| 111 | 100 | ||||
| 112 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "notexists").Return(nil, assert.AnError) | 101 | registrySvc.EXPECT().GetManifest(mock.Anything, "myapp", "notexists").Return(nil, assert.AnError) | ||
| 113 | 102 | ||||
| @@ -121,10 +110,8 @@ func TestHandler_GetManifest_NotFound(t *testing.T) { | |||||
| 121 | 110 | ||||
| 122 | func TestHandler_GetManifest_NestedName(t *testing.T) { | 111 | func TestHandler_GetManifest_NestedName(t *testing.T) { | ||
| 123 | registrySvc := inmocks.NewMockRegistryService(t) | 112 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 124 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 125 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 126 | 113 | ||||
| 127 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 114 | + | handler := NewHandler(registrySvc, testLogger()) |
| 128 | 115 | ||||
| 129 | manifestData := []byte(`{"schemaVersion": 2}`) | 116 | manifestData := []byte(`{"schemaVersion": 2}`) | ||
| 130 | registrySvc.EXPECT().GetManifest(mock.Anything, "org/project/app", "v1.0").Return(&domain.Manifest{ | 117 | registrySvc.EXPECT().GetManifest(mock.Anything, "org/project/app", "v1.0").Return(&domain.Manifest{ | ||
| @@ -144,15 +131,13 @@ func TestHandler_GetManifest_NestedName(t *testing.T) { | |||||
| 144 | 131 | ||||
| 145 | func TestHandler_PutManifest_Success(t *testing.T) { | 132 | func TestHandler_PutManifest_Success(t *testing.T) { | ||
| 146 | registrySvc := inmocks.NewMockRegistryService(t) | 133 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 147 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 148 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 149 | 134 | ||||
| 150 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 135 | + | handler := NewHandler(registrySvc, testLogger()) |
| 151 | 136 | ||||
| 152 | manifestData := []byte(`{"schemaVersion": 2}`) | 137 | manifestData := []byte(`{"schemaVersion": 2}`) | ||
| 153 | registrySvc.EXPECT().PutManifest(mock.Anything, mock.MatchedBy(func(m *domain.Manifest) bool { | 138 | registrySvc.EXPECT().PutManifest(mock.Anything, mock.MatchedBy(func(m *domain.Manifest) bool { | ||
| 154 | return m.Name == "myapp" && m.Reference == "latest" && m.ContentType == "application/vnd.docker.distribution.manifest.v2+json" | 139 | return m.Name == "myapp" && m.Reference == "latest" && m.ContentType == "application/vnd.docker.distribution.manifest.v2+json" | ||
| 155 | - | })).Return(nil) | 140 | + | })).Return("sha256:abc123", nil) |
| 156 | 141 | ||||
| 157 | req := httptest.NewRequest("PUT", "/v2/myapp/manifests/latest", bytes.NewReader(manifestData)) | 142 | req := httptest.NewRequest("PUT", "/v2/myapp/manifests/latest", bytes.NewReader(manifestData)) | ||
| 158 | req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") | 143 | req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") | ||
| @@ -161,16 +146,14 @@ func TestHandler_PutManifest_Success(t *testing.T) { | |||||
| 161 | handler.ServeHTTP(rec, req) | 146 | handler.ServeHTTP(rec, req) | ||
| 162 | 147 | ||||
| 163 | assert.Equal(t, http.StatusCreated, rec.Code) | 148 | assert.Equal(t, http.StatusCreated, rec.Code) | ||
| 164 | - | assert.NotEmpty(t, rec.Header().Get("Docker-Content-Digest")) | 149 | + | assert.Equal(t, "sha256:abc123", rec.Header().Get("Docker-Content-Digest")) |
| 165 | assert.Contains(t, rec.Header().Get("Location"), "/v2/myapp/manifests/latest") | 150 | assert.Contains(t, rec.Header().Get("Location"), "/v2/myapp/manifests/latest") | ||
| 166 | } | 151 | } | ||
| 167 | 152 | ||||
| 168 | func TestHandler_PutManifest_MissingContentType(t *testing.T) { | 153 | func TestHandler_PutManifest_MissingContentType(t *testing.T) { | ||
| 169 | registrySvc := inmocks.NewMockRegistryService(t) | 154 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 170 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 171 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 172 | 155 | ||||
| 173 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 156 | + | handler := NewHandler(registrySvc, testLogger()) |
| 174 | 157 | ||||
| 175 | manifestData := []byte(`{"schemaVersion": 2}`) | 158 | manifestData := []byte(`{"schemaVersion": 2}`) | ||
| 176 | 159 | ||||
| @@ -185,13 +168,11 @@ func TestHandler_PutManifest_MissingContentType(t *testing.T) { | |||||
| 185 | 168 | ||||
| 186 | func TestHandler_PutManifest_StorageError(t *testing.T) { | 169 | func TestHandler_PutManifest_StorageError(t *testing.T) { | ||
| 187 | registrySvc := inmocks.NewMockRegistryService(t) | 170 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 188 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 189 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 190 | 171 | ||||
| 191 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 172 | + | handler := NewHandler(registrySvc, testLogger()) |
| 192 | 173 | ||||
| 193 | manifestData := []byte(`{"schemaVersion": 2}`) | 174 | manifestData := []byte(`{"schemaVersion": 2}`) | ||
| 194 | - | registrySvc.EXPECT().PutManifest(mock.Anything, mock.Anything).Return(assert.AnError) | 175 | + | registrySvc.EXPECT().PutManifest(mock.Anything, mock.Anything).Return("", assert.AnError) |
| 195 | 176 | ||||
| 196 | req := httptest.NewRequest("PUT", "/v2/myapp/manifests/latest", bytes.NewReader(manifestData)) | 177 | req := httptest.NewRequest("PUT", "/v2/myapp/manifests/latest", bytes.NewReader(manifestData)) | ||
| 197 | req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") | 178 | req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") | ||
| @@ -204,10 +185,8 @@ func TestHandler_PutManifest_StorageError(t *testing.T) { | |||||
| 204 | 185 | ||||
| 205 | func TestHandler_ManifestRoutes_MethodNotAllowed(t *testing.T) { | 186 | func TestHandler_ManifestRoutes_MethodNotAllowed(t *testing.T) { | ||
| 206 | registrySvc := inmocks.NewMockRegistryService(t) | 187 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 207 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 208 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 209 | 188 | ||||
| 210 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 189 | + | handler := NewHandler(registrySvc, testLogger()) |
| 211 | 190 | ||||
| 212 | req := httptest.NewRequest("DELETE", "/v2/myapp/manifests/latest", nil) | 191 | req := httptest.NewRequest("DELETE", "/v2/myapp/manifests/latest", nil) | ||
| 213 | rec := httptest.NewRecorder() | 192 | rec := httptest.NewRecorder() | ||
| @@ -219,10 +198,8 @@ func TestHandler_ManifestRoutes_MethodNotAllowed(t *testing.T) { | |||||
| 219 | 198 | ||||
| 220 | func TestHandler_GetBlob_Success(t *testing.T) { | 199 | func TestHandler_GetBlob_Success(t *testing.T) { | ||
| 221 | registrySvc := inmocks.NewMockRegistryService(t) | 200 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 222 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 223 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 224 | 201 | ||||
| 225 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 202 | + | handler := NewHandler(registrySvc, testLogger()) |
| 226 | 203 | ||||
| 227 | // Create a temp file for http.ServeFile | 204 | // Create a temp file for http.ServeFile | ||
| 228 | tmpFile, err := os.CreateTemp("", "blob-*") | 205 | tmpFile, err := os.CreateTemp("", "blob-*") | ||
| @@ -233,7 +210,7 @@ func TestHandler_GetBlob_Success(t *testing.T) { | |||||
| 233 | _, err = tmpFile.Write(blobContent) | 210 | _, err = tmpFile.Write(blobContent) | ||
| 234 | assert.NoError(t, err) | 211 | assert.NoError(t, err) | ||
| 235 | 212 | ||||
| 236 | - | blobStorage.EXPECT().GetBlobPath("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(tmpFile.Name(), nil) | 213 | + | registrySvc.EXPECT().GetBlobPath(mock.Anything, "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(tmpFile.Name(), nil) |
| 237 | 214 | ||||
| 238 | req := httptest.NewRequest("GET", "/v2/myapp/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", nil) | 215 | req := httptest.NewRequest("GET", "/v2/myapp/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", nil) | ||
| 239 | rec := httptest.NewRecorder() | 216 | rec := httptest.NewRecorder() | ||
| @@ -246,12 +223,10 @@ func TestHandler_GetBlob_Success(t *testing.T) { | |||||
| 246 | 223 | ||||
| 247 | func TestHandler_GetBlob_NotFound(t *testing.T) { | 224 | func TestHandler_GetBlob_NotFound(t *testing.T) { | ||
| 248 | registrySvc := inmocks.NewMockRegistryService(t) | 225 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 249 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 250 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 251 | 226 | ||||
| 252 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 227 | + | handler := NewHandler(registrySvc, testLogger()) |
| 253 | 228 | ||||
| 254 | - | blobStorage.EXPECT().GetBlobPath("sha256:0000000000000000000000000000000000000000000000000000000000000000").Return("", assert.AnError) | 229 | + | registrySvc.EXPECT().GetBlobPath(mock.Anything, "sha256:0000000000000000000000000000000000000000000000000000000000000000").Return("", assert.AnError) |
| 255 | 230 | ||||
| 256 | req := httptest.NewRequest("GET", "/v2/myapp/blobs/sha256:0000000000000000000000000000000000000000000000000000000000000000", nil) | 231 | req := httptest.NewRequest("GET", "/v2/myapp/blobs/sha256:0000000000000000000000000000000000000000000000000000000000000000", nil) | ||
| 257 | rec := httptest.NewRecorder() | 232 | rec := httptest.NewRecorder() | ||
| @@ -263,10 +238,8 @@ func TestHandler_GetBlob_NotFound(t *testing.T) { | |||||
| 263 | 238 | ||||
| 264 | func TestHandler_BlobRoutes_MethodNotAllowed(t *testing.T) { | 239 | func TestHandler_BlobRoutes_MethodNotAllowed(t *testing.T) { | ||
| 265 | registrySvc := inmocks.NewMockRegistryService(t) | 240 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 266 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 267 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 268 | 241 | ||||
| 269 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 242 | + | handler := NewHandler(registrySvc, testLogger()) |
| 270 | 243 | ||||
| 271 | req := httptest.NewRequest("POST", "/v2/myapp/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", nil) | 244 | req := httptest.NewRequest("POST", "/v2/myapp/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", nil) | ||
| 272 | rec := httptest.NewRecorder() | 245 | rec := httptest.NewRecorder() | ||
| @@ -278,10 +251,8 @@ func TestHandler_BlobRoutes_MethodNotAllowed(t *testing.T) { | |||||
| 278 | 251 | ||||
| 279 | func TestHandler_StartBlobUpload_Success(t *testing.T) { | 252 | func TestHandler_StartBlobUpload_Success(t *testing.T) { | ||
| 280 | registrySvc := inmocks.NewMockRegistryService(t) | 253 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 281 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 282 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 283 | 254 | ||||
| 284 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 255 | + | handler := NewHandler(registrySvc, testLogger()) |
| 285 | 256 | ||||
| 286 | registrySvc.EXPECT().StartUpload(mock.Anything, "myapp").Return("1234567890-myapp", nil) | 257 | registrySvc.EXPECT().StartUpload(mock.Anything, "myapp").Return("1234567890-myapp", nil) | ||
| 287 | 258 | ||||
| @@ -297,10 +268,8 @@ func TestHandler_StartBlobUpload_Success(t *testing.T) { | |||||
| 297 | 268 | ||||
| 298 | func TestHandler_StartBlobUpload_Error(t *testing.T) { | 269 | func TestHandler_StartBlobUpload_Error(t *testing.T) { | ||
| 299 | registrySvc := inmocks.NewMockRegistryService(t) | 270 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 300 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 301 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 302 | 271 | ||||
| 303 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 272 | + | handler := NewHandler(registrySvc, testLogger()) |
| 304 | 273 | ||||
| 305 | registrySvc.EXPECT().StartUpload(mock.Anything, "myapp").Return("", assert.AnError) | 274 | registrySvc.EXPECT().StartUpload(mock.Anything, "myapp").Return("", assert.AnError) | ||
| 306 | 275 | ||||
| @@ -314,13 +283,11 @@ func TestHandler_StartBlobUpload_Error(t *testing.T) { | |||||
| 314 | 283 | ||||
| 315 | func TestHandler_BlobUpload_PATCH(t *testing.T) { | 284 | func TestHandler_BlobUpload_PATCH(t *testing.T) { | ||
| 316 | registrySvc := inmocks.NewMockRegistryService(t) | 285 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 317 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 318 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 319 | 286 | ||||
| 320 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 287 | + | handler := NewHandler(registrySvc, testLogger()) |
| 321 | 288 | ||||
| 322 | chunkData := []byte("chunk content") | 289 | chunkData := []byte("chunk content") | ||
| 323 | - | blobStorage.EXPECT().AppendBlobChunk("myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) | 290 | + | registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) |
| 324 | 291 | ||||
| 325 | req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/1234567890-myapp", bytes.NewReader(chunkData)) | 292 | req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/1234567890-myapp", bytes.NewReader(chunkData)) | ||
| 326 | rec := httptest.NewRecorder() | 293 | rec := httptest.NewRecorder() | ||
| @@ -333,13 +300,11 @@ func TestHandler_BlobUpload_PATCH(t *testing.T) { | |||||
| 333 | 300 | ||||
| 334 | func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) { | 301 | func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) { | ||
| 335 | registrySvc := inmocks.NewMockRegistryService(t) | 302 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 336 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 337 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 338 | 303 | ||||
| 339 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 304 | + | handler := NewHandler(registrySvc, testLogger()) |
| 340 | 305 | ||||
| 341 | chunkData := []byte("final chunk") | 306 | chunkData := []byte("final chunk") | ||
| 342 | - | blobStorage.EXPECT().AppendBlobChunk("myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) | 307 | + | registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "1234567890-myapp", chunkData).Return(int64(len(chunkData)), nil) |
| 343 | registrySvc.EXPECT().FinishUpload(mock.Anything, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil) | 308 | registrySvc.EXPECT().FinishUpload(mock.Anything, "1234567890-myapp", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil) | ||
| 344 | 309 | ||||
| 345 | req := httptest.NewRequest("PUT", "/v2/myapp/blobs/uploads/1234567890-myapp?digest=sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", bytes.NewReader(chunkData)) | 310 | req := httptest.NewRequest("PUT", "/v2/myapp/blobs/uploads/1234567890-myapp?digest=sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", bytes.NewReader(chunkData)) | ||
| @@ -354,10 +319,8 @@ func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) { | |||||
| 354 | 319 | ||||
| 355 | func TestHandler_BlobUpload_PUT_DigestMismatch(t *testing.T) { | 320 | func TestHandler_BlobUpload_PUT_DigestMismatch(t *testing.T) { | ||
| 356 | registrySvc := inmocks.NewMockRegistryService(t) | 321 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 357 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 358 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 359 | 322 | ||||
| 360 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 323 | + | handler := NewHandler(registrySvc, testLogger()) |
| 361 | 324 | ||||
| 362 | chunkData := []byte("final chunk") | 325 | chunkData := []byte("final chunk") | ||
| 363 | // Note: Invalid digest format is rejected by validation before reaching storage layer | 326 | // Note: Invalid digest format is rejected by validation before reaching storage layer | ||
| @@ -373,10 +336,8 @@ func TestHandler_BlobUpload_PUT_DigestMismatch(t *testing.T) { | |||||
| 373 | 336 | ||||
| 374 | func TestHandler_ListTags_Success(t *testing.T) { | 337 | func TestHandler_ListTags_Success(t *testing.T) { | ||
| 375 | registrySvc := inmocks.NewMockRegistryService(t) | 338 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 376 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 377 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 378 | 339 | ||||
| 379 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 340 | + | handler := NewHandler(registrySvc, testLogger()) |
| 380 | 341 | ||||
| 381 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return([]string{"latest", "v1.0", "v2.0"}, nil) | 342 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return([]string{"latest", "v1.0", "v2.0"}, nil) | ||
| 382 | 343 | ||||
| @@ -393,10 +354,8 @@ func TestHandler_ListTags_Success(t *testing.T) { | |||||
| 393 | 354 | ||||
| 394 | func TestHandler_ListTags_NotFound(t *testing.T) { | 355 | func TestHandler_ListTags_NotFound(t *testing.T) { | ||
| 395 | registrySvc := inmocks.NewMockRegistryService(t) | 356 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 396 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 397 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 398 | 357 | ||||
| 399 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 358 | + | handler := NewHandler(registrySvc, testLogger()) |
| 400 | 359 | ||||
| 401 | registrySvc.EXPECT().ListTags(mock.Anything, "notexists").Return(nil, assert.AnError) | 360 | registrySvc.EXPECT().ListTags(mock.Anything, "notexists").Return(nil, assert.AnError) | ||
| 402 | 361 | ||||
| @@ -410,10 +369,8 @@ func TestHandler_ListTags_NotFound(t *testing.T) { | |||||
| 410 | 369 | ||||
| 411 | func TestHandler_ListTags_MethodNotAllowed(t *testing.T) { | 370 | func TestHandler_ListTags_MethodNotAllowed(t *testing.T) { | ||
| 412 | registrySvc := inmocks.NewMockRegistryService(t) | 371 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 413 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 414 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 415 | 372 | ||||
| 416 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 373 | + | handler := NewHandler(registrySvc, testLogger()) |
| 417 | 374 | ||||
| 418 | req := httptest.NewRequest("POST", "/v2/myapp/tags/list", nil) | 375 | req := httptest.NewRequest("POST", "/v2/myapp/tags/list", nil) | ||
| 419 | rec := httptest.NewRecorder() | 376 | rec := httptest.NewRecorder() | ||
| @@ -425,10 +382,8 @@ func TestHandler_ListTags_MethodNotAllowed(t *testing.T) { | |||||
| 425 | 382 | ||||
| 426 | func TestHandler_RegisterRoutes(t *testing.T) { | 383 | func TestHandler_RegisterRoutes(t *testing.T) { | ||
| 427 | registrySvc := inmocks.NewMockRegistryService(t) | 384 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 428 | - | blobStorage := mocks.NewMockBlobStorage(t) | |||
| 429 | - | eventBus := mocks.NewMockEventPublisher(t) | |||
| 430 | 385 | ||||
| 431 | - | handler := NewHandler(registrySvc, blobStorage, eventBus, testLogger()) | 386 | + | handler := NewHandler(registrySvc, testLogger()) |
| 432 | 387 | ||||
| 433 | mux := http.NewServeMux() | 388 | mux := http.NewServeMux() | ||
| 434 | handler.RegisterRoutes(mux) | 389 | handler.RegisterRoutes(mux) | ||
M internal/app/run.go +1 -1
| @@ -772,7 +772,7 @@ func syncAndAutoStart(ctx context.Context, svc *services, log zerowrap.Logger) { | |||
| 772 | 772 | ||
| 773 | 773 | // createHTTPHandlers creates HTTP handlers with middleware. | |
| 774 | 774 | func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Handler, http.Handler) { | |
| 775 | - | registryHandler := registry.NewHandler(svc.registrySvc, svc.blobStorage, svc.eventBus, log) | |
| 775 | + | registryHandler := registry.NewHandler(svc.registrySvc, log) | |
| 776 | 776 | ||
| 777 | 777 | registryMiddlewares := []func(http.Handler) http.Handler{ | |
| 778 | 778 | middleware.PanicRecovery(log), | |
| @@ -772,7 +772,7 @@ func syncAndAutoStart(ctx context.Context, svc *services, log zerowrap.Logger) { | |||||
| 772 | 772 | ||||
| 773 | // createHTTPHandlers creates HTTP handlers with middleware. | 773 | // createHTTPHandlers creates HTTP handlers with middleware. | ||
| 774 | func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Handler, http.Handler) { | 774 | func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Handler, http.Handler) { | ||
| 775 | - | registryHandler := registry.NewHandler(svc.registrySvc, svc.blobStorage, svc.eventBus, log) | 775 | + | registryHandler := registry.NewHandler(svc.registrySvc, log) |
| 776 | 776 | ||||
| 777 | registryMiddlewares := []func(http.Handler) http.Handler{ | 777 | registryMiddlewares := []func(http.Handler) http.Handler{ | ||
| 778 | middleware.PanicRecovery(log), | 778 | middleware.PanicRecovery(log), | ||
M internal/boundaries/in/config.go +3 -0
| @@ -14,6 +14,9 @@ type ConfigService interface { | |||
| 14 | 14 | // GetRoutes returns all configured routes. | |
| 15 | 15 | GetRoutes(ctx context.Context) []domain.Route | |
| 16 | 16 | ||
| 17 | + | // GetRoute returns a single route by domain. | |
| 18 | + | GetRoute(ctx context.Context, domain string) (*domain.Route, error) | |
| 19 | + | ||
| 17 | 20 | // AddRoute adds a new route to the configuration. | |
| 18 | 21 | AddRoute(ctx context.Context, route domain.Route) error | |
| 19 | 22 | ||
| @@ -14,6 +14,9 @@ type ConfigService interface { | |||||
| 14 | // GetRoutes returns all configured routes. | 14 | // GetRoutes returns all configured routes. | ||
| 15 | GetRoutes(ctx context.Context) []domain.Route | 15 | GetRoutes(ctx context.Context) []domain.Route | ||
| 16 | 16 | ||||
| 17 | + | // GetRoute returns a single route by domain. | |||
| 18 | + | GetRoute(ctx context.Context, domain string) (*domain.Route, error) | |||
| 19 | + | ||||
| 17 | // AddRoute adds a new route to the configuration. | 20 | // AddRoute adds a new route to the configuration. | ||
| 18 | AddRoute(ctx context.Context, route domain.Route) error | 21 | AddRoute(ctx context.Context, route domain.Route) error | ||
| 19 | 22 | ||||
M internal/boundaries/in/mocks/mock_config_service.go +68 -0
| @@ -317,6 +317,74 @@ func (_c *MockConfigService_GetRegistryPort_Call) RunAndReturn(run func() int) * | |||
| 317 | 317 | return _c | |
| 318 | 318 | } | |
| 319 | 319 | ||
| 320 | + | // GetRoute provides a mock function for the type MockConfigService | |
| 321 | + | func (_mock *MockConfigService) GetRoute(ctx context.Context, domain1 string) (*domain.Route, error) { | |
| 322 | + | ret := _mock.Called(ctx, domain1) | |
| 323 | + | ||
| 324 | + | if len(ret) == 0 { | |
| 325 | + | panic("no return value specified for GetRoute") | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | var r0 *domain.Route | |
| 329 | + | var r1 error | |
| 330 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*domain.Route, error)); ok { | |
| 331 | + | return returnFunc(ctx, domain1) | |
| 332 | + | } | |
| 333 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) *domain.Route); ok { | |
| 334 | + | r0 = returnFunc(ctx, domain1) | |
| 335 | + | } else { | |
| 336 | + | if ret.Get(0) != nil { | |
| 337 | + | r0 = ret.Get(0).(*domain.Route) | |
| 338 | + | } | |
| 339 | + | } | |
| 340 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |
| 341 | + | r1 = returnFunc(ctx, domain1) | |
| 342 | + | } else { | |
| 343 | + | r1 = ret.Error(1) | |
| 344 | + | } | |
| 345 | + | return r0, r1 | |
| 346 | + | } | |
| 347 | + | ||
| 348 | + | // MockConfigService_GetRoute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRoute' | |
| 349 | + | type MockConfigService_GetRoute_Call struct { | |
| 350 | + | *mock.Call | |
| 351 | + | } | |
| 352 | + | ||
| 353 | + | // GetRoute is a helper method to define mock.On call | |
| 354 | + | // - ctx context.Context | |
| 355 | + | // - domain1 string | |
| 356 | + | func (_e *MockConfigService_Expecter) GetRoute(ctx interface{}, domain1 interface{}) *MockConfigService_GetRoute_Call { | |
| 357 | + | return &MockConfigService_GetRoute_Call{Call: _e.mock.On("GetRoute", ctx, domain1)} | |
| 358 | + | } | |
| 359 | + | ||
| 360 | + | func (_c *MockConfigService_GetRoute_Call) Run(run func(ctx context.Context, domain1 string)) *MockConfigService_GetRoute_Call { | |
| 361 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 362 | + | var arg0 context.Context | |
| 363 | + | if args[0] != nil { | |
| 364 | + | arg0 = args[0].(context.Context) | |
| 365 | + | } | |
| 366 | + | var arg1 string | |
| 367 | + | if args[1] != nil { | |
| 368 | + | arg1 = args[1].(string) | |
| 369 | + | } | |
| 370 | + | run( | |
| 371 | + | arg0, | |
| 372 | + | arg1, | |
| 373 | + | ) | |
| 374 | + | }) | |
| 375 | + | return _c | |
| 376 | + | } | |
| 377 | + | ||
| 378 | + | func (_c *MockConfigService_GetRoute_Call) Return(route *domain.Route, err error) *MockConfigService_GetRoute_Call { | |
| 379 | + | _c.Call.Return(route, err) | |
| 380 | + | return _c | |
| 381 | + | } | |
| 382 | + | ||
| 383 | + | func (_c *MockConfigService_GetRoute_Call) RunAndReturn(run func(ctx context.Context, domain1 string) (*domain.Route, error)) *MockConfigService_GetRoute_Call { | |
| 384 | + | _c.Call.Return(run) | |
| 385 | + | return _c | |
| 386 | + | } | |
| 387 | + | ||
| 320 | 388 | // GetRoutes provides a mock function for the type MockConfigService | |
| 321 | 389 | func (_mock *MockConfigService) GetRoutes(ctx context.Context) []domain.Route { | |
| 322 | 390 | ret := _mock.Called(ctx) | |
| @@ -317,6 +317,74 @@ func (_c *MockConfigService_GetRegistryPort_Call) RunAndReturn(run func() int) * | |||||
| 317 | return _c | 317 | return _c | ||
| 318 | } | 318 | } | ||
| 319 | 319 | ||||
| 320 | + | // GetRoute provides a mock function for the type MockConfigService | |||
| 321 | + | func (_mock *MockConfigService) GetRoute(ctx context.Context, domain1 string) (*domain.Route, error) { | |||
| 322 | + | ret := _mock.Called(ctx, domain1) | |||
| 323 | + | ||||
| 324 | + | if len(ret) == 0 { | |||
| 325 | + | panic("no return value specified for GetRoute") | |||
| 326 | + | } | |||
| 327 | + | ||||
| 328 | + | var r0 *domain.Route | |||
| 329 | + | var r1 error | |||
| 330 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*domain.Route, error)); ok { | |||
| 331 | + | return returnFunc(ctx, domain1) | |||
| 332 | + | } | |||
| 333 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) *domain.Route); ok { | |||
| 334 | + | r0 = returnFunc(ctx, domain1) | |||
| 335 | + | } else { | |||
| 336 | + | if ret.Get(0) != nil { | |||
| 337 | + | r0 = ret.Get(0).(*domain.Route) | |||
| 338 | + | } | |||
| 339 | + | } | |||
| 340 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |||
| 341 | + | r1 = returnFunc(ctx, domain1) | |||
| 342 | + | } else { | |||
| 343 | + | r1 = ret.Error(1) | |||
| 344 | + | } | |||
| 345 | + | return r0, r1 | |||
| 346 | + | } | |||
| 347 | + | ||||
| 348 | + | // MockConfigService_GetRoute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRoute' | |||
| 349 | + | type MockConfigService_GetRoute_Call struct { | |||
| 350 | + | *mock.Call | |||
| 351 | + | } | |||
| 352 | + | ||||
| 353 | + | // GetRoute is a helper method to define mock.On call | |||
| 354 | + | // - ctx context.Context | |||
| 355 | + | // - domain1 string | |||
| 356 | + | func (_e *MockConfigService_Expecter) GetRoute(ctx interface{}, domain1 interface{}) *MockConfigService_GetRoute_Call { | |||
| 357 | + | return &MockConfigService_GetRoute_Call{Call: _e.mock.On("GetRoute", ctx, domain1)} | |||
| 358 | + | } | |||
| 359 | + | ||||
| 360 | + | func (_c *MockConfigService_GetRoute_Call) Run(run func(ctx context.Context, domain1 string)) *MockConfigService_GetRoute_Call { | |||
| 361 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 362 | + | var arg0 context.Context | |||
| 363 | + | if args[0] != nil { | |||
| 364 | + | arg0 = args[0].(context.Context) | |||
| 365 | + | } | |||
| 366 | + | var arg1 string | |||
| 367 | + | if args[1] != nil { | |||
| 368 | + | arg1 = args[1].(string) | |||
| 369 | + | } | |||
| 370 | + | run( | |||
| 371 | + | arg0, | |||
| 372 | + | arg1, | |||
| 373 | + | ) | |||
| 374 | + | }) | |||
| 375 | + | return _c | |||
| 376 | + | } | |||
| 377 | + | ||||
| 378 | + | func (_c *MockConfigService_GetRoute_Call) Return(route *domain.Route, err error) *MockConfigService_GetRoute_Call { | |||
| 379 | + | _c.Call.Return(route, err) | |||
| 380 | + | return _c | |||
| 381 | + | } | |||
| 382 | + | ||||
| 383 | + | func (_c *MockConfigService_GetRoute_Call) RunAndReturn(run func(ctx context.Context, domain1 string) (*domain.Route, error)) *MockConfigService_GetRoute_Call { | |||
| 384 | + | _c.Call.Return(run) | |||
| 385 | + | return _c | |||
| 386 | + | } | |||
| 387 | + | ||||
| 320 | // GetRoutes provides a mock function for the type MockConfigService | 388 | // GetRoutes provides a mock function for the type MockConfigService | ||
| 321 | func (_mock *MockConfigService) GetRoutes(ctx context.Context) []domain.Route { | 389 | func (_mock *MockConfigService) GetRoutes(ctx context.Context) []domain.Route { | ||
| 322 | ret := _mock.Called(ctx) | 390 | ret := _mock.Called(ctx) | ||
M internal/boundaries/in/mocks/mock_registry_service.go +161 -8
| @@ -39,6 +39,84 @@ func (_m *MockRegistryService) EXPECT() *MockRegistryService_Expecter { | |||
| 39 | 39 | return &MockRegistryService_Expecter{mock: &_m.Mock} | |
| 40 | 40 | } | |
| 41 | 41 | ||
| 42 | + | // AppendBlobChunk provides a mock function for the type MockRegistryService | |
| 43 | + | func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name string, uuid string, chunk []byte) (int64, error) { | |
| 44 | + | ret := _mock.Called(ctx, name, uuid, chunk) | |
| 45 | + | ||
| 46 | + | if len(ret) == 0 { | |
| 47 | + | panic("no return value specified for AppendBlobChunk") | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | var r0 int64 | |
| 51 | + | var r1 error | |
| 52 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) (int64, error)); ok { | |
| 53 | + | return returnFunc(ctx, name, uuid, chunk) | |
| 54 | + | } | |
| 55 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) int64); ok { | |
| 56 | + | r0 = returnFunc(ctx, name, uuid, chunk) | |
| 57 | + | } else { | |
| 58 | + | r0 = ret.Get(0).(int64) | |
| 59 | + | } | |
| 60 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, []byte) error); ok { | |
| 61 | + | r1 = returnFunc(ctx, name, uuid, chunk) | |
| 62 | + | } else { | |
| 63 | + | r1 = ret.Error(1) | |
| 64 | + | } | |
| 65 | + | return r0, r1 | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | // MockRegistryService_AppendBlobChunk_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AppendBlobChunk' | |
| 69 | + | type MockRegistryService_AppendBlobChunk_Call struct { | |
| 70 | + | *mock.Call | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // AppendBlobChunk is a helper method to define mock.On call | |
| 74 | + | // - ctx context.Context | |
| 75 | + | // - name string | |
| 76 | + | // - uuid string | |
| 77 | + | // - chunk []byte | |
| 78 | + | func (_e *MockRegistryService_Expecter) AppendBlobChunk(ctx interface{}, name interface{}, uuid interface{}, chunk interface{}) *MockRegistryService_AppendBlobChunk_Call { | |
| 79 | + | return &MockRegistryService_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", ctx, name, uuid, chunk)} | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Context, name string, uuid string, chunk []byte)) *MockRegistryService_AppendBlobChunk_Call { | |
| 83 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 84 | + | var arg0 context.Context | |
| 85 | + | if args[0] != nil { | |
| 86 | + | arg0 = args[0].(context.Context) | |
| 87 | + | } | |
| 88 | + | var arg1 string | |
| 89 | + | if args[1] != nil { | |
| 90 | + | arg1 = args[1].(string) | |
| 91 | + | } | |
| 92 | + | var arg2 string | |
| 93 | + | if args[2] != nil { | |
| 94 | + | arg2 = args[2].(string) | |
| 95 | + | } | |
| 96 | + | var arg3 []byte | |
| 97 | + | if args[3] != nil { | |
| 98 | + | arg3 = args[3].([]byte) | |
| 99 | + | } | |
| 100 | + | run( | |
| 101 | + | arg0, | |
| 102 | + | arg1, | |
| 103 | + | arg2, | |
| 104 | + | arg3, | |
| 105 | + | ) | |
| 106 | + | }) | |
| 107 | + | return _c | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | func (_c *MockRegistryService_AppendBlobChunk_Call) Return(n int64, err error) *MockRegistryService_AppendBlobChunk_Call { | |
| 111 | + | _c.Call.Return(n, err) | |
| 112 | + | return _c | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | func (_c *MockRegistryService_AppendBlobChunk_Call) RunAndReturn(run func(ctx context.Context, name string, uuid string, chunk []byte) (int64, error)) *MockRegistryService_AppendBlobChunk_Call { | |
| 116 | + | _c.Call.Return(run) | |
| 117 | + | return _c | |
| 118 | + | } | |
| 119 | + | ||
| 42 | 120 | // BlobExists provides a mock function for the type MockRegistryService | |
| 43 | 121 | func (_mock *MockRegistryService) BlobExists(ctx context.Context, digest string) bool { | |
| 44 | 122 | ret := _mock.Called(ctx, digest) | |
| @@ -347,6 +425,72 @@ func (_c *MockRegistryService_GetBlob_Call) RunAndReturn(run func(ctx context.Co | |||
| 347 | 425 | return _c | |
| 348 | 426 | } | |
| 349 | 427 | ||
| 428 | + | // GetBlobPath provides a mock function for the type MockRegistryService | |
| 429 | + | func (_mock *MockRegistryService) GetBlobPath(ctx context.Context, digest string) (string, error) { | |
| 430 | + | ret := _mock.Called(ctx, digest) | |
| 431 | + | ||
| 432 | + | if len(ret) == 0 { | |
| 433 | + | panic("no return value specified for GetBlobPath") | |
| 434 | + | } | |
| 435 | + | ||
| 436 | + | var r0 string | |
| 437 | + | var r1 error | |
| 438 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { | |
| 439 | + | return returnFunc(ctx, digest) | |
| 440 | + | } | |
| 441 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) string); ok { | |
| 442 | + | r0 = returnFunc(ctx, digest) | |
| 443 | + | } else { | |
| 444 | + | r0 = ret.Get(0).(string) | |
| 445 | + | } | |
| 446 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |
| 447 | + | r1 = returnFunc(ctx, digest) | |
| 448 | + | } else { | |
| 449 | + | r1 = ret.Error(1) | |
| 450 | + | } | |
| 451 | + | return r0, r1 | |
| 452 | + | } | |
| 453 | + | ||
| 454 | + | // MockRegistryService_GetBlobPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlobPath' | |
| 455 | + | type MockRegistryService_GetBlobPath_Call struct { | |
| 456 | + | *mock.Call | |
| 457 | + | } | |
| 458 | + | ||
| 459 | + | // GetBlobPath is a helper method to define mock.On call | |
| 460 | + | // - ctx context.Context | |
| 461 | + | // - digest string | |
| 462 | + | func (_e *MockRegistryService_Expecter) GetBlobPath(ctx interface{}, digest interface{}) *MockRegistryService_GetBlobPath_Call { | |
| 463 | + | return &MockRegistryService_GetBlobPath_Call{Call: _e.mock.On("GetBlobPath", ctx, digest)} | |
| 464 | + | } | |
| 465 | + | ||
| 466 | + | func (_c *MockRegistryService_GetBlobPath_Call) Run(run func(ctx context.Context, digest string)) *MockRegistryService_GetBlobPath_Call { | |
| 467 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 468 | + | var arg0 context.Context | |
| 469 | + | if args[0] != nil { | |
| 470 | + | arg0 = args[0].(context.Context) | |
| 471 | + | } | |
| 472 | + | var arg1 string | |
| 473 | + | if args[1] != nil { | |
| 474 | + | arg1 = args[1].(string) | |
| 475 | + | } | |
| 476 | + | run( | |
| 477 | + | arg0, | |
| 478 | + | arg1, | |
| 479 | + | ) | |
| 480 | + | }) | |
| 481 | + | return _c | |
| 482 | + | } | |
| 483 | + | ||
| 484 | + | func (_c *MockRegistryService_GetBlobPath_Call) Return(s string, err error) *MockRegistryService_GetBlobPath_Call { | |
| 485 | + | _c.Call.Return(s, err) | |
| 486 | + | return _c | |
| 487 | + | } | |
| 488 | + | ||
| 489 | + | func (_c *MockRegistryService_GetBlobPath_Call) RunAndReturn(run func(ctx context.Context, digest string) (string, error)) *MockRegistryService_GetBlobPath_Call { | |
| 490 | + | _c.Call.Return(run) | |
| 491 | + | return _c | |
| 492 | + | } | |
| 493 | + | ||
| 350 | 494 | // GetManifest provides a mock function for the type MockRegistryService | |
| 351 | 495 | func (_mock *MockRegistryService) GetManifest(ctx context.Context, name string, reference string) (*domain.Manifest, error) { | |
| 352 | 496 | ret := _mock.Called(ctx, name, reference) | |
| @@ -621,20 +765,29 @@ func (_c *MockRegistryService_PutBlob_Call) RunAndReturn(run func(ctx context.Co | |||
| 621 | 765 | } | |
| 622 | 766 | ||
| 623 | 767 | // PutManifest provides a mock function for the type MockRegistryService | |
| 624 | - | func (_mock *MockRegistryService) PutManifest(ctx context.Context, manifest *domain.Manifest) error { | |
| 768 | + | func (_mock *MockRegistryService) PutManifest(ctx context.Context, manifest *domain.Manifest) (string, error) { | |
| 625 | 769 | ret := _mock.Called(ctx, manifest) | |
| 626 | 770 | ||
| 627 | 771 | if len(ret) == 0 { | |
| 628 | 772 | panic("no return value specified for PutManifest") | |
| 629 | 773 | } | |
| 630 | 774 | ||
| 631 | - | var r0 error | |
| 632 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, *domain.Manifest) error); ok { | |
| 775 | + | var r0 string | |
| 776 | + | var r1 error | |
| 777 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, *domain.Manifest) (string, error)); ok { | |
| 778 | + | return returnFunc(ctx, manifest) | |
| 779 | + | } | |
| 780 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, *domain.Manifest) string); ok { | |
| 633 | 781 | r0 = returnFunc(ctx, manifest) | |
| 634 | 782 | } else { | |
| 635 | - | r0 = ret.Error(0) | |
| 783 | + | r0 = ret.Get(0).(string) | |
| 636 | 784 | } | |
| 637 | - | return r0 | |
| 785 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, *domain.Manifest) error); ok { | |
| 786 | + | r1 = returnFunc(ctx, manifest) | |
| 787 | + | } else { | |
| 788 | + | r1 = ret.Error(1) | |
| 789 | + | } | |
| 790 | + | return r0, r1 | |
| 638 | 791 | } | |
| 639 | 792 | ||
| 640 | 793 | // MockRegistryService_PutManifest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutManifest' | |
| @@ -667,12 +820,12 @@ func (_c *MockRegistryService_PutManifest_Call) Run(run func(ctx context.Context | |||
| 667 | 820 | return _c | |
| 668 | 821 | } | |
| 669 | 822 | ||
| 670 | - | func (_c *MockRegistryService_PutManifest_Call) Return(err error) *MockRegistryService_PutManifest_Call { | |
| 671 | - | _c.Call.Return(err) | |
| 823 | + | func (_c *MockRegistryService_PutManifest_Call) Return(digest string, err error) *MockRegistryService_PutManifest_Call { | |
| 824 | + | _c.Call.Return(digest, err) | |
| 672 | 825 | return _c | |
| 673 | 826 | } | |
| 674 | 827 | ||
| 675 | - | func (_c *MockRegistryService_PutManifest_Call) RunAndReturn(run func(ctx context.Context, manifest *domain.Manifest) error) *MockRegistryService_PutManifest_Call { | |
| 828 | + | func (_c *MockRegistryService_PutManifest_Call) RunAndReturn(run func(ctx context.Context, manifest *domain.Manifest) (string, error)) *MockRegistryService_PutManifest_Call { | |
| 676 | 829 | _c.Call.Return(run) | |
| 677 | 830 | return _c | |
| 678 | 831 | } | |
| @@ -39,6 +39,84 @@ func (_m *MockRegistryService) EXPECT() *MockRegistryService_Expecter { | |||||
| 39 | return &MockRegistryService_Expecter{mock: &_m.Mock} | 39 | return &MockRegistryService_Expecter{mock: &_m.Mock} | ||
| 40 | } | 40 | } | ||
| 41 | 41 | ||||
| 42 | + | // AppendBlobChunk provides a mock function for the type MockRegistryService | |||
| 43 | + | func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name string, uuid string, chunk []byte) (int64, error) { | |||
| 44 | + | ret := _mock.Called(ctx, name, uuid, chunk) | |||
| 45 | + | ||||
| 46 | + | if len(ret) == 0 { | |||
| 47 | + | panic("no return value specified for AppendBlobChunk") | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | var r0 int64 | |||
| 51 | + | var r1 error | |||
| 52 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) (int64, error)); ok { | |||
| 53 | + | return returnFunc(ctx, name, uuid, chunk) | |||
| 54 | + | } | |||
| 55 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) int64); ok { | |||
| 56 | + | r0 = returnFunc(ctx, name, uuid, chunk) | |||
| 57 | + | } else { | |||
| 58 | + | r0 = ret.Get(0).(int64) | |||
| 59 | + | } | |||
| 60 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, []byte) error); ok { | |||
| 61 | + | r1 = returnFunc(ctx, name, uuid, chunk) | |||
| 62 | + | } else { | |||
| 63 | + | r1 = ret.Error(1) | |||
| 64 | + | } | |||
| 65 | + | return r0, r1 | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | // MockRegistryService_AppendBlobChunk_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AppendBlobChunk' | |||
| 69 | + | type MockRegistryService_AppendBlobChunk_Call struct { | |||
| 70 | + | *mock.Call | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // AppendBlobChunk is a helper method to define mock.On call | |||
| 74 | + | // - ctx context.Context | |||
| 75 | + | // - name string | |||
| 76 | + | // - uuid string | |||
| 77 | + | // - chunk []byte | |||
| 78 | + | func (_e *MockRegistryService_Expecter) AppendBlobChunk(ctx interface{}, name interface{}, uuid interface{}, chunk interface{}) *MockRegistryService_AppendBlobChunk_Call { | |||
| 79 | + | return &MockRegistryService_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", ctx, name, uuid, chunk)} | |||
| 80 | + | } | |||
| 81 | + | ||||
| 82 | + | func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Context, name string, uuid string, chunk []byte)) *MockRegistryService_AppendBlobChunk_Call { | |||
| 83 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 84 | + | var arg0 context.Context | |||
| 85 | + | if args[0] != nil { | |||
| 86 | + | arg0 = args[0].(context.Context) | |||
| 87 | + | } | |||
| 88 | + | var arg1 string | |||
| 89 | + | if args[1] != nil { | |||
| 90 | + | arg1 = args[1].(string) | |||
| 91 | + | } | |||
| 92 | + | var arg2 string | |||
| 93 | + | if args[2] != nil { | |||
| 94 | + | arg2 = args[2].(string) | |||
| 95 | + | } | |||
| 96 | + | var arg3 []byte | |||
| 97 | + | if args[3] != nil { | |||
| 98 | + | arg3 = args[3].([]byte) | |||
| 99 | + | } | |||
| 100 | + | run( | |||
| 101 | + | arg0, | |||
| 102 | + | arg1, | |||
| 103 | + | arg2, | |||
| 104 | + | arg3, | |||
| 105 | + | ) | |||
| 106 | + | }) | |||
| 107 | + | return _c | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | func (_c *MockRegistryService_AppendBlobChunk_Call) Return(n int64, err error) *MockRegistryService_AppendBlobChunk_Call { | |||
| 111 | + | _c.Call.Return(n, err) | |||
| 112 | + | return _c | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | func (_c *MockRegistryService_AppendBlobChunk_Call) RunAndReturn(run func(ctx context.Context, name string, uuid string, chunk []byte) (int64, error)) *MockRegistryService_AppendBlobChunk_Call { | |||
| 116 | + | _c.Call.Return(run) | |||
| 117 | + | return _c | |||
| 118 | + | } | |||
| 119 | + | ||||
| 42 | // BlobExists provides a mock function for the type MockRegistryService | 120 | // BlobExists provides a mock function for the type MockRegistryService | ||
| 43 | func (_mock *MockRegistryService) BlobExists(ctx context.Context, digest string) bool { | 121 | func (_mock *MockRegistryService) BlobExists(ctx context.Context, digest string) bool { | ||
| 44 | ret := _mock.Called(ctx, digest) | 122 | ret := _mock.Called(ctx, digest) | ||
| @@ -347,6 +425,72 @@ func (_c *MockRegistryService_GetBlob_Call) RunAndReturn(run func(ctx context.Co | |||||
| 347 | return _c | 425 | return _c | ||
| 348 | } | 426 | } | ||
| 349 | 427 | ||||
| 428 | + | // GetBlobPath provides a mock function for the type MockRegistryService | |||
| 429 | + | func (_mock *MockRegistryService) GetBlobPath(ctx context.Context, digest string) (string, error) { | |||
| 430 | + | ret := _mock.Called(ctx, digest) | |||
| 431 | + | ||||
| 432 | + | if len(ret) == 0 { | |||
| 433 | + | panic("no return value specified for GetBlobPath") | |||
| 434 | + | } | |||
| 435 | + | ||||
| 436 | + | var r0 string | |||
| 437 | + | var r1 error | |||
| 438 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { | |||
| 439 | + | return returnFunc(ctx, digest) | |||
| 440 | + | } | |||
| 441 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) string); ok { | |||
| 442 | + | r0 = returnFunc(ctx, digest) | |||
| 443 | + | } else { | |||
| 444 | + | r0 = ret.Get(0).(string) | |||
| 445 | + | } | |||
| 446 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |||
| 447 | + | r1 = returnFunc(ctx, digest) | |||
| 448 | + | } else { | |||
| 449 | + | r1 = ret.Error(1) | |||
| 450 | + | } | |||
| 451 | + | return r0, r1 | |||
| 452 | + | } | |||
| 453 | + | ||||
| 454 | + | // MockRegistryService_GetBlobPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlobPath' | |||
| 455 | + | type MockRegistryService_GetBlobPath_Call struct { | |||
| 456 | + | *mock.Call | |||
| 457 | + | } | |||
| 458 | + | ||||
| 459 | + | // GetBlobPath is a helper method to define mock.On call | |||
| 460 | + | // - ctx context.Context | |||
| 461 | + | // - digest string | |||
| 462 | + | func (_e *MockRegistryService_Expecter) GetBlobPath(ctx interface{}, digest interface{}) *MockRegistryService_GetBlobPath_Call { | |||
| 463 | + | return &MockRegistryService_GetBlobPath_Call{Call: _e.mock.On("GetBlobPath", ctx, digest)} | |||
| 464 | + | } | |||
| 465 | + | ||||
| 466 | + | func (_c *MockRegistryService_GetBlobPath_Call) Run(run func(ctx context.Context, digest string)) *MockRegistryService_GetBlobPath_Call { | |||
| 467 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 468 | + | var arg0 context.Context | |||
| 469 | + | if args[0] != nil { | |||
| 470 | + | arg0 = args[0].(context.Context) | |||
| 471 | + | } | |||
| 472 | + | var arg1 string | |||
| 473 | + | if args[1] != nil { | |||
| 474 | + | arg1 = args[1].(string) | |||
| 475 | + | } | |||
| 476 | + | run( | |||
| 477 | + | arg0, | |||
| 478 | + | arg1, | |||
| 479 | + | ) | |||
| 480 | + | }) | |||
| 481 | + | return _c | |||
| 482 | + | } | |||
| 483 | + | ||||
| 484 | + | func (_c *MockRegistryService_GetBlobPath_Call) Return(s string, err error) *MockRegistryService_GetBlobPath_Call { | |||
| 485 | + | _c.Call.Return(s, err) | |||
| 486 | + | return _c | |||
| 487 | + | } | |||
| 488 | + | ||||
| 489 | + | func (_c *MockRegistryService_GetBlobPath_Call) RunAndReturn(run func(ctx context.Context, digest string) (string, error)) *MockRegistryService_GetBlobPath_Call { | |||
| 490 | + | _c.Call.Return(run) | |||
| 491 | + | return _c | |||
| 492 | + | } | |||
| 493 | + | ||||
| 350 | // GetManifest provides a mock function for the type MockRegistryService | 494 | // GetManifest provides a mock function for the type MockRegistryService | ||
| 351 | func (_mock *MockRegistryService) GetManifest(ctx context.Context, name string, reference string) (*domain.Manifest, error) { | 495 | func (_mock *MockRegistryService) GetManifest(ctx context.Context, name string, reference string) (*domain.Manifest, error) { | ||
| 352 | ret := _mock.Called(ctx, name, reference) | 496 | ret := _mock.Called(ctx, name, reference) | ||
| @@ -621,20 +765,29 @@ func (_c *MockRegistryService_PutBlob_Call) RunAndReturn(run func(ctx context.Co | |||||
| 621 | } | 765 | } | ||
| 622 | 766 | ||||
| 623 | // PutManifest provides a mock function for the type MockRegistryService | 767 | // PutManifest provides a mock function for the type MockRegistryService | ||
| 624 | - | func (_mock *MockRegistryService) PutManifest(ctx context.Context, manifest *domain.Manifest) error { | 768 | + | func (_mock *MockRegistryService) PutManifest(ctx context.Context, manifest *domain.Manifest) (string, error) { |
| 625 | ret := _mock.Called(ctx, manifest) | 769 | ret := _mock.Called(ctx, manifest) | ||
| 626 | 770 | ||||
| 627 | if len(ret) == 0 { | 771 | if len(ret) == 0 { | ||
| 628 | panic("no return value specified for PutManifest") | 772 | panic("no return value specified for PutManifest") | ||
| 629 | } | 773 | } | ||
| 630 | 774 | ||||
| 631 | - | var r0 error | 775 | + | var r0 string |
| 632 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, *domain.Manifest) error); ok { | 776 | + | var r1 error |
| 777 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, *domain.Manifest) (string, error)); ok { | |||
| 778 | + | return returnFunc(ctx, manifest) | |||
| 779 | + | } | |||
| 780 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, *domain.Manifest) string); ok { | |||
| 633 | r0 = returnFunc(ctx, manifest) | 781 | r0 = returnFunc(ctx, manifest) | ||
| 634 | } else { | 782 | } else { | ||
| 635 | - | r0 = ret.Error(0) | 783 | + | r0 = ret.Get(0).(string) |
| 636 | } | 784 | } | ||
| 637 | - | return r0 | 785 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, *domain.Manifest) error); ok { |
| 786 | + | r1 = returnFunc(ctx, manifest) | |||
| 787 | + | } else { | |||
| 788 | + | r1 = ret.Error(1) | |||
| 789 | + | } | |||
| 790 | + | return r0, r1 | |||
| 638 | } | 791 | } | ||
| 639 | 792 | ||||
| 640 | // MockRegistryService_PutManifest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutManifest' | 793 | // MockRegistryService_PutManifest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutManifest' | ||
| @@ -667,12 +820,12 @@ func (_c *MockRegistryService_PutManifest_Call) Run(run func(ctx context.Context | |||||
| 667 | return _c | 820 | return _c | ||
| 668 | } | 821 | } | ||
| 669 | 822 | ||||
| 670 | - | func (_c *MockRegistryService_PutManifest_Call) Return(err error) *MockRegistryService_PutManifest_Call { | 823 | + | func (_c *MockRegistryService_PutManifest_Call) Return(digest string, err error) *MockRegistryService_PutManifest_Call { |
| 671 | - | _c.Call.Return(err) | 824 | + | _c.Call.Return(digest, err) |
| 672 | return _c | 825 | return _c | ||
| 673 | } | 826 | } | ||
| 674 | 827 | ||||
| 675 | - | func (_c *MockRegistryService_PutManifest_Call) RunAndReturn(run func(ctx context.Context, manifest *domain.Manifest) error) *MockRegistryService_PutManifest_Call { | 828 | + | func (_c *MockRegistryService_PutManifest_Call) RunAndReturn(run func(ctx context.Context, manifest *domain.Manifest) (string, error)) *MockRegistryService_PutManifest_Call { |
| 676 | _c.Call.Return(run) | 829 | _c.Call.Return(run) | ||
| 677 | return _c | 830 | return _c | ||
| 678 | } | 831 | } | ||
M internal/boundaries/in/registry.go +3 -1
| @@ -11,16 +11,18 @@ import ( | |||
| 11 | 11 | type RegistryService interface { | |
| 12 | 12 | // Manifest operations | |
| 13 | 13 | GetManifest(ctx context.Context, name, reference string) (*domain.Manifest, error) | |
| 14 | - | PutManifest(ctx context.Context, manifest *domain.Manifest) error | |
| 14 | + | PutManifest(ctx context.Context, manifest *domain.Manifest) (digest string, err error) | |
| 15 | 15 | DeleteManifest(ctx context.Context, name, reference string) error | |
| 16 | 16 | ||
| 17 | 17 | // Blob operations | |
| 18 | 18 | GetBlob(ctx context.Context, digest string) (io.ReadCloser, error) | |
| 19 | + | GetBlobPath(ctx context.Context, digest string) (string, error) | |
| 19 | 20 | PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error | |
| 20 | 21 | BlobExists(ctx context.Context, digest string) bool | |
| 21 | 22 | ||
| 22 | 23 | // Upload operations | |
| 23 | 24 | StartUpload(ctx context.Context, name string) (string, error) | |
| 25 | + | AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error) | |
| 24 | 26 | FinishUpload(ctx context.Context, uuid, digest string) error | |
| 25 | 27 | CancelUpload(ctx context.Context, uuid string) error | |
| 26 | 28 | ||
| @@ -11,16 +11,18 @@ import ( | |||||
| 11 | type RegistryService interface { | 11 | type RegistryService interface { | ||
| 12 | // Manifest operations | 12 | // Manifest operations | ||
| 13 | GetManifest(ctx context.Context, name, reference string) (*domain.Manifest, error) | 13 | GetManifest(ctx context.Context, name, reference string) (*domain.Manifest, error) | ||
| 14 | - | PutManifest(ctx context.Context, manifest *domain.Manifest) error | 14 | + | PutManifest(ctx context.Context, manifest *domain.Manifest) (digest string, err error) |
| 15 | DeleteManifest(ctx context.Context, name, reference string) error | 15 | DeleteManifest(ctx context.Context, name, reference string) error | ||
| 16 | 16 | ||||
| 17 | // Blob operations | 17 | // Blob operations | ||
| 18 | GetBlob(ctx context.Context, digest string) (io.ReadCloser, error) | 18 | GetBlob(ctx context.Context, digest string) (io.ReadCloser, error) | ||
| 19 | + | GetBlobPath(ctx context.Context, digest string) (string, error) | |||
| 19 | PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error | 20 | PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error | ||
| 20 | BlobExists(ctx context.Context, digest string) bool | 21 | BlobExists(ctx context.Context, digest string) bool | ||
| 21 | 22 | ||||
| 22 | // Upload operations | 23 | // Upload operations | ||
| 23 | StartUpload(ctx context.Context, name string) (string, error) | 24 | StartUpload(ctx context.Context, name string) (string, error) | ||
| 25 | + | AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error) | |||
| 24 | FinishUpload(ctx context.Context, uuid, digest string) error | 26 | FinishUpload(ctx context.Context, uuid, digest string) error | ||
| 25 | CancelUpload(ctx context.Context, uuid string) error | 27 | CancelUpload(ctx context.Context, uuid string) error | ||
| 26 | 28 | ||||
M internal/domain/errors.go +2 -0
| @@ -20,6 +20,8 @@ var ( | |||
| 20 | 20 | ErrRouteNotFound = errors.New("route not found") | |
| 21 | 21 | ErrRouteExists = errors.New("route already exists") | |
| 22 | 22 | ErrInvalidRoute = errors.New("invalid route configuration") | |
| 23 | + | ErrRouteDomainEmpty = errors.New("route domain cannot be empty") | |
| 24 | + | ErrRouteImageEmpty = errors.New("route image cannot be empty") | |
| 23 | 25 | ErrNoTargetAvailable = errors.New("no target available for route") | |
| 24 | 26 | ||
| 25 | 27 | // Registry errors | |
| @@ -20,6 +20,8 @@ var ( | |||||
| 20 | ErrRouteNotFound = errors.New("route not found") | 20 | ErrRouteNotFound = errors.New("route not found") | ||
| 21 | ErrRouteExists = errors.New("route already exists") | 21 | ErrRouteExists = errors.New("route already exists") | ||
| 22 | ErrInvalidRoute = errors.New("invalid route configuration") | 22 | ErrInvalidRoute = errors.New("invalid route configuration") | ||
| 23 | + | ErrRouteDomainEmpty = errors.New("route domain cannot be empty") | |||
| 24 | + | ErrRouteImageEmpty = errors.New("route image cannot be empty") | |||
| 23 | ErrNoTargetAvailable = errors.New("no target available for route") | 25 | ErrNoTargetAvailable = errors.New("no target available for route") | ||
| 24 | 26 | ||||
| 25 | // Registry errors | 27 | // Registry errors | ||
M internal/usecase/config/service.go +41 -0
| @@ -182,6 +182,31 @@ func (s *Service) GetRoutes(_ context.Context) []domain.Route { | |||
| 182 | 182 | return routes | |
| 183 | 183 | } | |
| 184 | 184 | ||
| 185 | + | // GetRoute returns a single route by domain. | |
| 186 | + | func (s *Service) GetRoute(_ context.Context, domainName string) (*domain.Route, error) { | |
| 187 | + | s.mu.RLock() | |
| 188 | + | defer s.mu.RUnlock() | |
| 189 | + | ||
| 190 | + | image, exists := s.config.Routes[domainName] | |
| 191 | + | if !exists { | |
| 192 | + | return nil, domain.ErrRouteNotFound | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | route := &domain.Route{ | |
| 196 | + | Domain: domainName, | |
| 197 | + | Image: image, | |
| 198 | + | HTTPS: true, | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | // Check if domain has http:// prefix | |
| 202 | + | if strings.HasPrefix(domainName, "http://") { | |
| 203 | + | route.Domain = strings.TrimPrefix(domainName, "http://") | |
| 204 | + | route.HTTPS = false | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | return route, nil | |
| 208 | + | } | |
| 209 | + | ||
| 185 | 210 | // AddRoute adds a new route to the configuration and persists it. | |
| 186 | 211 | func (s *Service) AddRoute(ctx context.Context, route domain.Route) error { | |
| 187 | 212 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| @@ -191,6 +216,14 @@ func (s *Service) AddRoute(ctx context.Context, route domain.Route) error { | |||
| 191 | 216 | }) | |
| 192 | 217 | log := zerowrap.FromCtx(ctx) | |
| 193 | 218 | ||
| 219 | + | // Validate route | |
| 220 | + | if route.Domain == "" { | |
| 221 | + | return domain.ErrRouteDomainEmpty | |
| 222 | + | } | |
| 223 | + | if route.Image == "" { | |
| 224 | + | return domain.ErrRouteImageEmpty | |
| 225 | + | } | |
| 226 | + | ||
| 194 | 227 | // Store previous value for rollback | |
| 195 | 228 | s.mu.Lock() | |
| 196 | 229 | previousImage, existed := s.config.Routes[route.Domain] | |
| @@ -226,6 +259,14 @@ func (s *Service) UpdateRoute(ctx context.Context, route domain.Route) error { | |||
| 226 | 259 | }) | |
| 227 | 260 | log := zerowrap.FromCtx(ctx) | |
| 228 | 261 | ||
| 262 | + | // Validate route | |
| 263 | + | if route.Domain == "" { | |
| 264 | + | return domain.ErrRouteDomainEmpty | |
| 265 | + | } | |
| 266 | + | if route.Image == "" { | |
| 267 | + | return domain.ErrRouteImageEmpty | |
| 268 | + | } | |
| 269 | + | ||
| 229 | 270 | // Store previous value for rollback | |
| 230 | 271 | s.mu.Lock() | |
| 231 | 272 | previousImage, exists := s.config.Routes[route.Domain] | |
| @@ -182,6 +182,31 @@ func (s *Service) GetRoutes(_ context.Context) []domain.Route { | |||||
| 182 | return routes | 182 | return routes | ||
| 183 | } | 183 | } | ||
| 184 | 184 | ||||
| 185 | + | // GetRoute returns a single route by domain. | |||
| 186 | + | func (s *Service) GetRoute(_ context.Context, domainName string) (*domain.Route, error) { | |||
| 187 | + | s.mu.RLock() | |||
| 188 | + | defer s.mu.RUnlock() | |||
| 189 | + | ||||
| 190 | + | image, exists := s.config.Routes[domainName] | |||
| 191 | + | if !exists { | |||
| 192 | + | return nil, domain.ErrRouteNotFound | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | route := &domain.Route{ | |||
| 196 | + | Domain: domainName, | |||
| 197 | + | Image: image, | |||
| 198 | + | HTTPS: true, | |||
| 199 | + | } | |||
| 200 | + | ||||
| 201 | + | // Check if domain has http:// prefix | |||
| 202 | + | if strings.HasPrefix(domainName, "http://") { | |||
| 203 | + | route.Domain = strings.TrimPrefix(domainName, "http://") | |||
| 204 | + | route.HTTPS = false | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | return route, nil | |||
| 208 | + | } | |||
| 209 | + | ||||
| 185 | // AddRoute adds a new route to the configuration and persists it. | 210 | // AddRoute adds a new route to the configuration and persists it. | ||
| 186 | func (s *Service) AddRoute(ctx context.Context, route domain.Route) error { | 211 | func (s *Service) AddRoute(ctx context.Context, route domain.Route) error { | ||
| 187 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | 212 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | ||
| @@ -191,6 +216,14 @@ func (s *Service) AddRoute(ctx context.Context, route domain.Route) error { | |||||
| 191 | }) | 216 | }) | ||
| 192 | log := zerowrap.FromCtx(ctx) | 217 | log := zerowrap.FromCtx(ctx) | ||
| 193 | 218 | ||||
| 219 | + | // Validate route | |||
| 220 | + | if route.Domain == "" { | |||
| 221 | + | return domain.ErrRouteDomainEmpty | |||
| 222 | + | } | |||
| 223 | + | if route.Image == "" { | |||
| 224 | + | return domain.ErrRouteImageEmpty | |||
| 225 | + | } | |||
| 226 | + | ||||
| 194 | // Store previous value for rollback | 227 | // Store previous value for rollback | ||
| 195 | s.mu.Lock() | 228 | s.mu.Lock() | ||
| 196 | previousImage, existed := s.config.Routes[route.Domain] | 229 | previousImage, existed := s.config.Routes[route.Domain] | ||
| @@ -226,6 +259,14 @@ func (s *Service) UpdateRoute(ctx context.Context, route domain.Route) error { | |||||
| 226 | }) | 259 | }) | ||
| 227 | log := zerowrap.FromCtx(ctx) | 260 | log := zerowrap.FromCtx(ctx) | ||
| 228 | 261 | ||||
| 262 | + | // Validate route | |||
| 263 | + | if route.Domain == "" { | |||
| 264 | + | return domain.ErrRouteDomainEmpty | |||
| 265 | + | } | |||
| 266 | + | if route.Image == "" { | |||
| 267 | + | return domain.ErrRouteImageEmpty | |||
| 268 | + | } | |||
| 269 | + | ||||
| 229 | // Store previous value for rollback | 270 | // Store previous value for rollback | ||
| 230 | s.mu.Lock() | 271 | s.mu.Lock() | ||
| 231 | previousImage, exists := s.config.Routes[route.Domain] | 272 | previousImage, exists := s.config.Routes[route.Domain] | ||
M internal/usecase/config/service_test.go +143 -52
| @@ -98,83 +98,174 @@ func TestService_GetRoutes(t *testing.T) { | |||
| 98 | 98 | assert.False(t, routeMap["insecure.example"].HTTPS) | |
| 99 | 99 | } | |
| 100 | 100 | ||
| 101 | - | func TestService_AddRoute(t *testing.T) { | |
| 102 | - | // Create temp config file for Save() to work | |
| 103 | - | tmpDir := t.TempDir() | |
| 104 | - | configFile := filepath.Join(tmpDir, "gordon.toml") | |
| 105 | - | err := os.WriteFile(configFile, []byte("[routes]\n"), 0600) | |
| 106 | - | require.NoError(t, err) | |
| 107 | - | ||
| 101 | + | func TestService_GetRoute(t *testing.T) { | |
| 108 | 102 | v := viper.New() | |
| 109 | - | v.SetConfigFile(configFile) | |
| 103 | + | v.Set("routes", map[string]interface{}{ | |
| 104 | + | "app.example.com": "myapp:latest", | |
| 105 | + | "http://insecure.example": "insecureapp:latest", | |
| 106 | + | }) | |
| 107 | + | ||
| 110 | 108 | eventBus := mocks.NewMockEventPublisher(t) | |
| 111 | 109 | svc := NewService(v, eventBus) | |
| 112 | 110 | ctx := testContext() | |
| 113 | 111 | ||
| 114 | 112 | _ = svc.Load(ctx) | |
| 115 | 113 | ||
| 116 | - | route := domain.Route{ | |
| 117 | - | Domain: "new.example.com", | |
| 118 | - | Image: "newapp:latest", | |
| 119 | - | } | |
| 114 | + | t.Run("existing route", func(t *testing.T) { | |
| 115 | + | route, err := svc.GetRoute(ctx, "app.example.com") | |
| 116 | + | require.NoError(t, err) | |
| 117 | + | assert.Equal(t, "app.example.com", route.Domain) | |
| 118 | + | assert.Equal(t, "myapp:latest", route.Image) | |
| 119 | + | assert.True(t, route.HTTPS) | |
| 120 | + | }) | |
| 120 | 121 | ||
| 121 | - | err = svc.AddRoute(ctx, route) | |
| 122 | + | t.Run("non-existent route", func(t *testing.T) { | |
| 123 | + | route, err := svc.GetRoute(ctx, "notfound.example.com") | |
| 124 | + | assert.ErrorIs(t, err, domain.ErrRouteNotFound) | |
| 125 | + | assert.Nil(t, route) | |
| 126 | + | }) | |
| 127 | + | } | |
| 122 | 128 | ||
| 123 | - | assert.NoError(t, err) | |
| 129 | + | func TestService_AddRoute(t *testing.T) { | |
| 130 | + | t.Run("success", func(t *testing.T) { | |
| 131 | + | // Create temp config file for Save() to work | |
| 132 | + | tmpDir := t.TempDir() | |
| 133 | + | configFile := filepath.Join(tmpDir, "gordon.toml") | |
| 134 | + | err := os.WriteFile(configFile, []byte("[routes]\n"), 0600) | |
| 135 | + | require.NoError(t, err) | |
| 124 | 136 | ||
| 125 | - | // Verify route was added | |
| 126 | - | config := svc.GetConfig() | |
| 127 | - | assert.Equal(t, "newapp:latest", config.Routes["new.example.com"]) | |
| 128 | - | } | |
| 137 | + | v := viper.New() | |
| 138 | + | v.SetConfigFile(configFile) | |
| 139 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 140 | + | svc := NewService(v, eventBus) | |
| 141 | + | ctx := testContext() | |
| 129 | 142 | ||
| 130 | - | func TestService_UpdateRoute(t *testing.T) { | |
| 131 | - | // Create temp config file for Save() to work | |
| 132 | - | tmpDir := t.TempDir() | |
| 133 | - | configFile := filepath.Join(tmpDir, "gordon.toml") | |
| 134 | - | err := os.WriteFile(configFile, []byte("[routes]\n\"app.example.com\" = \"myapp:v1\"\n"), 0600) | |
| 135 | - | require.NoError(t, err) | |
| 143 | + | _ = svc.Load(ctx) | |
| 136 | 144 | ||
| 137 | - | v := viper.New() | |
| 138 | - | v.SetConfigFile(configFile) | |
| 139 | - | v.Set("routes", map[string]interface{}{ | |
| 140 | - | "app.example.com": "myapp:v1", | |
| 145 | + | route := domain.Route{ | |
| 146 | + | Domain: "new.example.com", | |
| 147 | + | Image: "newapp:latest", | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | err = svc.AddRoute(ctx, route) | |
| 151 | + | ||
| 152 | + | assert.NoError(t, err) | |
| 153 | + | ||
| 154 | + | // Verify route was added | |
| 155 | + | config := svc.GetConfig() | |
| 156 | + | assert.Equal(t, "newapp:latest", config.Routes["new.example.com"]) | |
| 141 | 157 | }) | |
| 142 | 158 | ||
| 143 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 144 | - | svc := NewService(v, eventBus) | |
| 145 | - | ctx := testContext() | |
| 159 | + | t.Run("empty domain", func(t *testing.T) { | |
| 160 | + | v := viper.New() | |
| 161 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 162 | + | svc := NewService(v, eventBus) | |
| 163 | + | ctx := testContext() | |
| 146 | 164 | ||
| 147 | - | _ = svc.Load(ctx) | |
| 165 | + | route := domain.Route{ | |
| 166 | + | Domain: "", | |
| 167 | + | Image: "myapp:latest", | |
| 168 | + | } | |
| 148 | 169 | ||
| 149 | - | route := domain.Route{ | |
| 150 | - | Domain: "app.example.com", | |
| 151 | - | Image: "myapp:v2", | |
| 152 | - | } | |
| 170 | + | err := svc.AddRoute(ctx, route) | |
| 171 | + | assert.ErrorIs(t, err, domain.ErrRouteDomainEmpty) | |
| 172 | + | }) | |
| 153 | 173 | ||
| 154 | - | err = svc.UpdateRoute(ctx, route) | |
| 174 | + | t.Run("empty image", func(t *testing.T) { | |
| 175 | + | v := viper.New() | |
| 176 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 177 | + | svc := NewService(v, eventBus) | |
| 178 | + | ctx := testContext() | |
| 155 | 179 | ||
| 156 | - | assert.NoError(t, err) | |
| 180 | + | route := domain.Route{ | |
| 181 | + | Domain: "example.com", | |
| 182 | + | Image: "", | |
| 183 | + | } | |
| 157 | 184 | ||
| 158 | - | config := svc.GetConfig() | |
| 159 | - | assert.Equal(t, "myapp:v2", config.Routes["app.example.com"]) | |
| 185 | + | err := svc.AddRoute(ctx, route) | |
| 186 | + | assert.ErrorIs(t, err, domain.ErrRouteImageEmpty) | |
| 187 | + | }) | |
| 160 | 188 | } | |
| 161 | 189 | ||
| 162 | - | func TestService_UpdateRoute_NotFound(t *testing.T) { | |
| 163 | - | v := viper.New() | |
| 164 | - | eventBus := mocks.NewMockEventPublisher(t) | |
| 165 | - | svc := NewService(v, eventBus) | |
| 166 | - | ctx := testContext() | |
| 190 | + | func TestService_UpdateRoute(t *testing.T) { | |
| 191 | + | t.Run("success", func(t *testing.T) { | |
| 192 | + | // Create temp config file for Save() to work | |
| 193 | + | tmpDir := t.TempDir() | |
| 194 | + | configFile := filepath.Join(tmpDir, "gordon.toml") | |
| 195 | + | err := os.WriteFile(configFile, []byte("[routes]\n\"app.example.com\" = \"myapp:v1\"\n"), 0600) | |
| 196 | + | require.NoError(t, err) | |
| 197 | + | ||
| 198 | + | v := viper.New() | |
| 199 | + | v.SetConfigFile(configFile) | |
| 200 | + | v.Set("routes", map[string]any{ | |
| 201 | + | "app.example.com": "myapp:v1", | |
| 202 | + | }) | |
| 167 | 203 | ||
| 168 | - | _ = svc.Load(ctx) | |
| 204 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 205 | + | svc := NewService(v, eventBus) | |
| 206 | + | ctx := testContext() | |
| 169 | 207 | ||
| 170 | - | route := domain.Route{ | |
| 171 | - | Domain: "nonexistent.example.com", | |
| 172 | - | Image: "myapp:latest", | |
| 173 | - | } | |
| 208 | + | _ = svc.Load(ctx) | |
| 174 | 209 | ||
| 175 | - | err := svc.UpdateRoute(ctx, route) | |
| 210 | + | route := domain.Route{ | |
| 211 | + | Domain: "app.example.com", | |
| 212 | + | Image: "myapp:v2", | |
| 213 | + | } | |
| 176 | 214 | ||
| 177 | - | assert.ErrorIs(t, err, domain.ErrRouteNotFound) | |
| 215 | + | err = svc.UpdateRoute(ctx, route) | |
| 216 | + | ||
| 217 | + | assert.NoError(t, err) | |
| 218 | + | ||
| 219 | + | config := svc.GetConfig() | |
| 220 | + | assert.Equal(t, "myapp:v2", config.Routes["app.example.com"]) | |
| 221 | + | }) | |
| 222 | + | ||
| 223 | + | t.Run("not found", func(t *testing.T) { | |
| 224 | + | v := viper.New() | |
| 225 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 226 | + | svc := NewService(v, eventBus) | |
| 227 | + | ctx := testContext() | |
| 228 | + | ||
| 229 | + | _ = svc.Load(ctx) | |
| 230 | + | ||
| 231 | + | route := domain.Route{ | |
| 232 | + | Domain: "nonexistent.example.com", | |
| 233 | + | Image: "myapp:latest", | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | err := svc.UpdateRoute(ctx, route) | |
| 237 | + | assert.ErrorIs(t, err, domain.ErrRouteNotFound) | |
| 238 | + | }) | |
| 239 | + | ||
| 240 | + | t.Run("empty domain", func(t *testing.T) { | |
| 241 | + | v := viper.New() | |
| 242 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 243 | + | svc := NewService(v, eventBus) | |
| 244 | + | ctx := testContext() | |
| 245 | + | ||
| 246 | + | route := domain.Route{ | |
| 247 | + | Domain: "", | |
| 248 | + | Image: "myapp:latest", | |
| 249 | + | } | |
| 250 | + | ||
| 251 | + | err := svc.UpdateRoute(ctx, route) | |
| 252 | + | assert.ErrorIs(t, err, domain.ErrRouteDomainEmpty) | |
| 253 | + | }) | |
| 254 | + | ||
| 255 | + | t.Run("empty image", func(t *testing.T) { | |
| 256 | + | v := viper.New() | |
| 257 | + | eventBus := mocks.NewMockEventPublisher(t) | |
| 258 | + | svc := NewService(v, eventBus) | |
| 259 | + | ctx := testContext() | |
| 260 | + | ||
| 261 | + | route := domain.Route{ | |
| 262 | + | Domain: "example.com", | |
| 263 | + | Image: "", | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | err := svc.UpdateRoute(ctx, route) | |
| 267 | + | assert.ErrorIs(t, err, domain.ErrRouteImageEmpty) | |
| 268 | + | }) | |
| 178 | 269 | } | |
| 179 | 270 | ||
| 180 | 271 | func TestService_RemoveRoute(t *testing.T) { | |
| @@ -98,83 +98,174 @@ func TestService_GetRoutes(t *testing.T) { | |||||
| 98 | assert.False(t, routeMap["insecure.example"].HTTPS) | 98 | assert.False(t, routeMap["insecure.example"].HTTPS) | ||
| 99 | } | 99 | } | ||
| 100 | 100 | ||||
| 101 | - | func TestService_AddRoute(t *testing.T) { | 101 | + | func TestService_GetRoute(t *testing.T) { |
| 102 | - | // Create temp config file for Save() to work | |||
| 103 | - | tmpDir := t.TempDir() | |||
| 104 | - | configFile := filepath.Join(tmpDir, "gordon.toml") | |||
| 105 | - | err := os.WriteFile(configFile, []byte("[routes]\n"), 0600) | |||
| 106 | - | require.NoError(t, err) | |||
| 107 | - | ||||
| 108 | v := viper.New() | 102 | v := viper.New() | ||
| 109 | - | v.SetConfigFile(configFile) | 103 | + | v.Set("routes", map[string]interface{}{ |
| 104 | + | "app.example.com": "myapp:latest", | |||
| 105 | + | "http://insecure.example": "insecureapp:latest", | |||
| 106 | + | }) | |||
| 107 | + | ||||
| 110 | eventBus := mocks.NewMockEventPublisher(t) | 108 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 111 | svc := NewService(v, eventBus) | 109 | svc := NewService(v, eventBus) | ||
| 112 | ctx := testContext() | 110 | ctx := testContext() | ||
| 113 | 111 | ||||
| 114 | _ = svc.Load(ctx) | 112 | _ = svc.Load(ctx) | ||
| 115 | 113 | ||||
| 116 | - | route := domain.Route{ | 114 | + | t.Run("existing route", func(t *testing.T) { |
| 117 | - | Domain: "new.example.com", | 115 | + | route, err := svc.GetRoute(ctx, "app.example.com") |
| 118 | - | Image: "newapp:latest", | 116 | + | require.NoError(t, err) |
| 119 | - | } | 117 | + | assert.Equal(t, "app.example.com", route.Domain) |
| 118 | + | assert.Equal(t, "myapp:latest", route.Image) | |||
| 119 | + | assert.True(t, route.HTTPS) | |||
| 120 | + | }) | |||
| 120 | 121 | ||||
| 121 | - | err = svc.AddRoute(ctx, route) | 122 | + | t.Run("non-existent route", func(t *testing.T) { |
| 123 | + | route, err := svc.GetRoute(ctx, "notfound.example.com") | |||
| 124 | + | assert.ErrorIs(t, err, domain.ErrRouteNotFound) | |||
| 125 | + | assert.Nil(t, route) | |||
| 126 | + | }) | |||
| 127 | + | } | |||
| 122 | 128 | ||||
| 123 | - | assert.NoError(t, err) | 129 | + | func TestService_AddRoute(t *testing.T) { |
| 130 | + | t.Run("success", func(t *testing.T) { | |||
| 131 | + | // Create temp config file for Save() to work | |||
| 132 | + | tmpDir := t.TempDir() | |||
| 133 | + | configFile := filepath.Join(tmpDir, "gordon.toml") | |||
| 134 | + | err := os.WriteFile(configFile, []byte("[routes]\n"), 0600) | |||
| 135 | + | require.NoError(t, err) | |||
| 124 | 136 | ||||
| 125 | - | // Verify route was added | 137 | + | v := viper.New() |
| 126 | - | config := svc.GetConfig() | 138 | + | v.SetConfigFile(configFile) |
| 127 | - | assert.Equal(t, "newapp:latest", config.Routes["new.example.com"]) | 139 | + | eventBus := mocks.NewMockEventPublisher(t) |
| 128 | - | } | 140 | + | svc := NewService(v, eventBus) |
| 141 | + | ctx := testContext() | |||
| 129 | 142 | ||||
| 130 | - | func TestService_UpdateRoute(t *testing.T) { | 143 | + | _ = svc.Load(ctx) |
| 131 | - | // Create temp config file for Save() to work | |||
| 132 | - | tmpDir := t.TempDir() | |||
| 133 | - | configFile := filepath.Join(tmpDir, "gordon.toml") | |||
| 134 | - | err := os.WriteFile(configFile, []byte("[routes]\n\"app.example.com\" = \"myapp:v1\"\n"), 0600) | |||
| 135 | - | require.NoError(t, err) | |||
| 136 | 144 | ||||
| 137 | - | v := viper.New() | 145 | + | route := domain.Route{ |
| 138 | - | v.SetConfigFile(configFile) | 146 | + | Domain: "new.example.com", |
| 139 | - | v.Set("routes", map[string]interface{}{ | 147 | + | Image: "newapp:latest", |
| 140 | - | "app.example.com": "myapp:v1", | 148 | + | } |
| 149 | + | ||||
| 150 | + | err = svc.AddRoute(ctx, route) | |||
| 151 | + | ||||
| 152 | + | assert.NoError(t, err) | |||
| 153 | + | ||||
| 154 | + | // Verify route was added | |||
| 155 | + | config := svc.GetConfig() | |||
| 156 | + | assert.Equal(t, "newapp:latest", config.Routes["new.example.com"]) | |||
| 141 | }) | 157 | }) | ||
| 142 | 158 | ||||
| 143 | - | eventBus := mocks.NewMockEventPublisher(t) | 159 | + | t.Run("empty domain", func(t *testing.T) { |
| 144 | - | svc := NewService(v, eventBus) | 160 | + | v := viper.New() |
| 145 | - | ctx := testContext() | 161 | + | eventBus := mocks.NewMockEventPublisher(t) |
| 162 | + | svc := NewService(v, eventBus) | |||
| 163 | + | ctx := testContext() | |||
| 146 | 164 | ||||
| 147 | - | _ = svc.Load(ctx) | 165 | + | route := domain.Route{ |
| 166 | + | Domain: "", | |||
| 167 | + | Image: "myapp:latest", | |||
| 168 | + | } | |||
| 148 | 169 | ||||
| 149 | - | route := domain.Route{ | 170 | + | err := svc.AddRoute(ctx, route) |
| 150 | - | Domain: "app.example.com", | 171 | + | assert.ErrorIs(t, err, domain.ErrRouteDomainEmpty) |
| 151 | - | Image: "myapp:v2", | 172 | + | }) |
| 152 | - | } | |||
| 153 | 173 | ||||
| 154 | - | err = svc.UpdateRoute(ctx, route) | 174 | + | t.Run("empty image", func(t *testing.T) { |
| 175 | + | v := viper.New() | |||
| 176 | + | eventBus := mocks.NewMockEventPublisher(t) | |||
| 177 | + | svc := NewService(v, eventBus) | |||
| 178 | + | ctx := testContext() | |||
| 155 | 179 | ||||
| 156 | - | assert.NoError(t, err) | 180 | + | route := domain.Route{ |
| 181 | + | Domain: "example.com", | |||
| 182 | + | Image: "", | |||
| 183 | + | } | |||
| 157 | 184 | ||||
| 158 | - | config := svc.GetConfig() | 185 | + | err := svc.AddRoute(ctx, route) |
| 159 | - | assert.Equal(t, "myapp:v2", config.Routes["app.example.com"]) | 186 | + | assert.ErrorIs(t, err, domain.ErrRouteImageEmpty) |
| 187 | + | }) | |||
| 160 | } | 188 | } | ||
| 161 | 189 | ||||
| 162 | - | func TestService_UpdateRoute_NotFound(t *testing.T) { | 190 | + | func TestService_UpdateRoute(t *testing.T) { |
| 163 | - | v := viper.New() | 191 | + | t.Run("success", func(t *testing.T) { |
| 164 | - | eventBus := mocks.NewMockEventPublisher(t) | 192 | + | // Create temp config file for Save() to work |
| 165 | - | svc := NewService(v, eventBus) | 193 | + | tmpDir := t.TempDir() |
| 166 | - | ctx := testContext() | 194 | + | configFile := filepath.Join(tmpDir, "gordon.toml") |
| 195 | + | err := os.WriteFile(configFile, []byte("[routes]\n\"app.example.com\" = \"myapp:v1\"\n"), 0600) | |||
| 196 | + | require.NoError(t, err) | |||
| 197 | + | ||||
| 198 | + | v := viper.New() | |||
| 199 | + | v.SetConfigFile(configFile) | |||
| 200 | + | v.Set("routes", map[string]any{ | |||
| 201 | + | "app.example.com": "myapp:v1", | |||
| 202 | + | }) | |||
| 167 | 203 | ||||
| 168 | - | _ = svc.Load(ctx) | 204 | + | eventBus := mocks.NewMockEventPublisher(t) |
| 205 | + | svc := NewService(v, eventBus) | |||
| 206 | + | ctx := testContext() | |||
| 169 | 207 | ||||
| 170 | - | route := domain.Route{ | 208 | + | _ = svc.Load(ctx) |
| 171 | - | Domain: "nonexistent.example.com", | |||
| 172 | - | Image: "myapp:latest", | |||
| 173 | - | } | |||
| 174 | 209 | ||||
| 175 | - | err := svc.UpdateRoute(ctx, route) | 210 | + | route := domain.Route{ |
| 211 | + | Domain: "app.example.com", | |||
| 212 | + | Image: "myapp:v2", | |||
| 213 | + | } | |||
| 176 | 214 | ||||
| 177 | - | assert.ErrorIs(t, err, domain.ErrRouteNotFound) | 215 | + | err = svc.UpdateRoute(ctx, route) |
| 216 | + | ||||
| 217 | + | assert.NoError(t, err) | |||
| 218 | + | ||||
| 219 | + | config := svc.GetConfig() | |||
| 220 | + | assert.Equal(t, "myapp:v2", config.Routes["app.example.com"]) | |||
| 221 | + | }) | |||
| 222 | + | ||||
| 223 | + | t.Run("not found", func(t *testing.T) { | |||
| 224 | + | v := viper.New() | |||
| 225 | + | eventBus := mocks.NewMockEventPublisher(t) | |||
| 226 | + | svc := NewService(v, eventBus) | |||
| 227 | + | ctx := testContext() | |||
| 228 | + | ||||
| 229 | + | _ = svc.Load(ctx) | |||
| 230 | + | ||||
| 231 | + | route := domain.Route{ | |||
| 232 | + | Domain: "nonexistent.example.com", | |||
| 233 | + | Image: "myapp:latest", | |||
| 234 | + | } | |||
| 235 | + | ||||
| 236 | + | err := svc.UpdateRoute(ctx, route) | |||
| 237 | + | assert.ErrorIs(t, err, domain.ErrRouteNotFound) | |||
| 238 | + | }) | |||
| 239 | + | ||||
| 240 | + | t.Run("empty domain", func(t *testing.T) { | |||
| 241 | + | v := viper.New() | |||
| 242 | + | eventBus := mocks.NewMockEventPublisher(t) | |||
| 243 | + | svc := NewService(v, eventBus) | |||
| 244 | + | ctx := testContext() | |||
| 245 | + | ||||
| 246 | + | route := domain.Route{ | |||
| 247 | + | Domain: "", | |||
| 248 | + | Image: "myapp:latest", | |||
| 249 | + | } | |||
| 250 | + | ||||
| 251 | + | err := svc.UpdateRoute(ctx, route) | |||
| 252 | + | assert.ErrorIs(t, err, domain.ErrRouteDomainEmpty) | |||
| 253 | + | }) | |||
| 254 | + | ||||
| 255 | + | t.Run("empty image", func(t *testing.T) { | |||
| 256 | + | v := viper.New() | |||
| 257 | + | eventBus := mocks.NewMockEventPublisher(t) | |||
| 258 | + | svc := NewService(v, eventBus) | |||
| 259 | + | ctx := testContext() | |||
| 260 | + | ||||
| 261 | + | route := domain.Route{ | |||
| 262 | + | Domain: "example.com", | |||
| 263 | + | Image: "", | |||
| 264 | + | } | |||
| 265 | + | ||||
| 266 | + | err := svc.UpdateRoute(ctx, route) | |||
| 267 | + | assert.ErrorIs(t, err, domain.ErrRouteImageEmpty) | |||
| 268 | + | }) | |||
| 178 | } | 269 | } | ||
| 179 | 270 | ||||
| 180 | func TestService_RemoveRoute(t *testing.T) { | 271 | func TestService_RemoveRoute(t *testing.T) { | ||
M internal/usecase/registry/service.go +46 -5
| @@ -3,6 +3,8 @@ package registry | |||
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | 5 | "context" | |
| 6 | + | "crypto/sha256" | |
| 7 | + | "fmt" | |
| 6 | 8 | "io" | |
| 7 | 9 | ||
| 8 | 10 | "github.com/bnema/zerowrap" | |
| @@ -54,8 +56,8 @@ func (s *Service) GetManifest(ctx context.Context, name, reference string) (*dom | |||
| 54 | 56 | }, nil | |
| 55 | 57 | } | |
| 56 | 58 | ||
| 57 | - | // PutManifest stores a manifest. | |
| 58 | - | func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) error { | |
| 59 | + | // PutManifest stores a manifest and returns the calculated digest. | |
| 60 | + | func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) (string, error) { | |
| 59 | 61 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| 60 | 62 | zerowrap.FieldLayer: "usecase", | |
| 61 | 63 | zerowrap.FieldUseCase: "PutManifest", | |
| @@ -64,8 +66,11 @@ func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) er | |||
| 64 | 66 | }) | |
| 65 | 67 | log := zerowrap.FromCtx(ctx) | |
| 66 | 68 | ||
| 69 | + | // Calculate digest | |
| 70 | + | digest := fmt.Sprintf("sha256:%x", sha256.Sum256(manifest.Data)) | |
| 71 | + | ||
| 67 | 72 | if err := s.manifestStorage.PutManifest(manifest.Name, manifest.Reference, manifest.ContentType, manifest.Data); err != nil { | |
| 68 | - | return log.WrapErr(err, "failed to store manifest") | |
| 73 | + | return "", log.WrapErr(err, "failed to store manifest") | |
| 69 | 74 | } | |
| 70 | 75 | ||
| 71 | 76 | // Publish image pushed event | |
| @@ -80,8 +85,8 @@ func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) er | |||
| 80 | 85 | } | |
| 81 | 86 | } | |
| 82 | 87 | ||
| 83 | - | log.Info().Msg("manifest stored") | |
| 84 | - | return nil | |
| 88 | + | log.Info().Str("digest", digest).Msg("manifest stored") | |
| 89 | + | return digest, nil | |
| 85 | 90 | } | |
| 86 | 91 | ||
| 87 | 92 | // DeleteManifest removes a manifest. | |
| @@ -119,6 +124,23 @@ func (s *Service) GetBlob(ctx context.Context, digest string) (io.ReadCloser, er | |||
| 119 | 124 | return reader, nil | |
| 120 | 125 | } | |
| 121 | 126 | ||
| 127 | + | // GetBlobPath returns the filesystem path to a blob for direct serving. | |
| 128 | + | func (s *Service) GetBlobPath(ctx context.Context, digest string) (string, error) { | |
| 129 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| 130 | + | zerowrap.FieldLayer: "usecase", | |
| 131 | + | zerowrap.FieldUseCase: "GetBlobPath", | |
| 132 | + | "digest": digest, | |
| 133 | + | }) | |
| 134 | + | log := zerowrap.FromCtx(ctx) | |
| 135 | + | ||
| 136 | + | path, err := s.blobStorage.GetBlobPath(digest) | |
| 137 | + | if err != nil { | |
| 138 | + | return "", log.WrapErr(err, "failed to get blob path") | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | return path, nil | |
| 142 | + | } | |
| 143 | + | ||
| 122 | 144 | // PutBlob stores a blob with the given digest. | |
| 123 | 145 | func (s *Service) PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error { | |
| 124 | 146 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| @@ -160,6 +182,25 @@ func (s *Service) StartUpload(ctx context.Context, name string) (string, error) | |||
| 160 | 182 | return uuid, nil | |
| 161 | 183 | } | |
| 162 | 184 | ||
| 185 | + | // AppendBlobChunk appends data to an in-progress blob upload. | |
| 186 | + | func (s *Service) AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error) { | |
| 187 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| 188 | + | zerowrap.FieldLayer: "usecase", | |
| 189 | + | zerowrap.FieldUseCase: "AppendBlobChunk", | |
| 190 | + | "name": name, | |
| 191 | + | "uuid": uuid, | |
| 192 | + | zerowrap.FieldSize: len(chunk), | |
| 193 | + | }) | |
| 194 | + | log := zerowrap.FromCtx(ctx) | |
| 195 | + | ||
| 196 | + | length, err := s.blobStorage.AppendBlobChunk(name, uuid, chunk) | |
| 197 | + | if err != nil { | |
| 198 | + | return 0, log.WrapErr(err, "failed to append blob chunk") | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | return length, nil | |
| 202 | + | } | |
| 203 | + | ||
| 163 | 204 | // FinishUpload completes a blob upload. | |
| 164 | 205 | func (s *Service) FinishUpload(ctx context.Context, uuid, digest string) error { | |
| 165 | 206 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| @@ -3,6 +3,8 @@ package registry | |||||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | "context" | 5 | "context" | ||
| 6 | + | "crypto/sha256" | |||
| 7 | + | "fmt" | |||
| 6 | "io" | 8 | "io" | ||
| 7 | 9 | ||||
| 8 | "github.com/bnema/zerowrap" | 10 | "github.com/bnema/zerowrap" | ||
| @@ -54,8 +56,8 @@ func (s *Service) GetManifest(ctx context.Context, name, reference string) (*dom | |||||
| 54 | }, nil | 56 | }, nil | ||
| 55 | } | 57 | } | ||
| 56 | 58 | ||||
| 57 | - | // PutManifest stores a manifest. | 59 | + | // PutManifest stores a manifest and returns the calculated digest. |
| 58 | - | func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) error { | 60 | + | func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) (string, error) { |
| 59 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | 61 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | ||
| 60 | zerowrap.FieldLayer: "usecase", | 62 | zerowrap.FieldLayer: "usecase", | ||
| 61 | zerowrap.FieldUseCase: "PutManifest", | 63 | zerowrap.FieldUseCase: "PutManifest", | ||
| @@ -64,8 +66,11 @@ func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) er | |||||
| 64 | }) | 66 | }) | ||
| 65 | log := zerowrap.FromCtx(ctx) | 67 | log := zerowrap.FromCtx(ctx) | ||
| 66 | 68 | ||||
| 69 | + | // Calculate digest | |||
| 70 | + | digest := fmt.Sprintf("sha256:%x", sha256.Sum256(manifest.Data)) | |||
| 71 | + | ||||
| 67 | if err := s.manifestStorage.PutManifest(manifest.Name, manifest.Reference, manifest.ContentType, manifest.Data); err != nil { | 72 | if err := s.manifestStorage.PutManifest(manifest.Name, manifest.Reference, manifest.ContentType, manifest.Data); err != nil { | ||
| 68 | - | return log.WrapErr(err, "failed to store manifest") | 73 | + | return "", log.WrapErr(err, "failed to store manifest") |
| 69 | } | 74 | } | ||
| 70 | 75 | ||||
| 71 | // Publish image pushed event | 76 | // Publish image pushed event | ||
| @@ -80,8 +85,8 @@ func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) er | |||||
| 80 | } | 85 | } | ||
| 81 | } | 86 | } | ||
| 82 | 87 | ||||
| 83 | - | log.Info().Msg("manifest stored") | 88 | + | log.Info().Str("digest", digest).Msg("manifest stored") |
| 84 | - | return nil | 89 | + | return digest, nil |
| 85 | } | 90 | } | ||
| 86 | 91 | ||||
| 87 | // DeleteManifest removes a manifest. | 92 | // DeleteManifest removes a manifest. | ||
| @@ -119,6 +124,23 @@ func (s *Service) GetBlob(ctx context.Context, digest string) (io.ReadCloser, er | |||||
| 119 | return reader, nil | 124 | return reader, nil | ||
| 120 | } | 125 | } | ||
| 121 | 126 | ||||
| 127 | + | // GetBlobPath returns the filesystem path to a blob for direct serving. | |||
| 128 | + | func (s *Service) GetBlobPath(ctx context.Context, digest string) (string, error) { | |||
| 129 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |||
| 130 | + | zerowrap.FieldLayer: "usecase", | |||
| 131 | + | zerowrap.FieldUseCase: "GetBlobPath", | |||
| 132 | + | "digest": digest, | |||
| 133 | + | }) | |||
| 134 | + | log := zerowrap.FromCtx(ctx) | |||
| 135 | + | ||||
| 136 | + | path, err := s.blobStorage.GetBlobPath(digest) | |||
| 137 | + | if err != nil { | |||
| 138 | + | return "", log.WrapErr(err, "failed to get blob path") | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | return path, nil | |||
| 142 | + | } | |||
| 143 | + | ||||
| 122 | // PutBlob stores a blob with the given digest. | 144 | // PutBlob stores a blob with the given digest. | ||
| 123 | func (s *Service) PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error { | 145 | func (s *Service) PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error { | ||
| 124 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | 146 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | ||
| @@ -160,6 +182,25 @@ func (s *Service) StartUpload(ctx context.Context, name string) (string, error) | |||||
| 160 | return uuid, nil | 182 | return uuid, nil | ||
| 161 | } | 183 | } | ||
| 162 | 184 | ||||
| 185 | + | // AppendBlobChunk appends data to an in-progress blob upload. | |||
| 186 | + | func (s *Service) AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error) { | |||
| 187 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |||
| 188 | + | zerowrap.FieldLayer: "usecase", | |||
| 189 | + | zerowrap.FieldUseCase: "AppendBlobChunk", | |||
| 190 | + | "name": name, | |||
| 191 | + | "uuid": uuid, | |||
| 192 | + | zerowrap.FieldSize: len(chunk), | |||
| 193 | + | }) | |||
| 194 | + | log := zerowrap.FromCtx(ctx) | |||
| 195 | + | ||||
| 196 | + | length, err := s.blobStorage.AppendBlobChunk(name, uuid, chunk) | |||
| 197 | + | if err != nil { | |||
| 198 | + | return 0, log.WrapErr(err, "failed to append blob chunk") | |||
| 199 | + | } | |||
| 200 | + | ||||
| 201 | + | return length, nil | |||
| 202 | + | } | |||
| 203 | + | ||||
| 163 | // FinishUpload completes a blob upload. | 204 | // FinishUpload completes a blob upload. | ||
| 164 | func (s *Service) FinishUpload(ctx context.Context, uuid, digest string) error { | 205 | func (s *Service) FinishUpload(ctx context.Context, uuid, digest string) error { | ||
| 165 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | 206 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | ||
M internal/usecase/registry/service_test.go +7 -4
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "context" | |
| 6 | 6 | "errors" | |
| 7 | 7 | "io" | |
| 8 | + | "strings" | |
| 8 | 9 | "testing" | |
| 9 | 10 | ||
| 10 | 11 | "github.com/bnema/zerowrap" | |
| @@ -75,9 +76,11 @@ func TestService_PutManifest_Success(t *testing.T) { | |||
| 75 | 76 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil) | |
| 76 | 77 | eventBus.EXPECT().Publish(domain.EventImagePushed, mock.AnythingOfType("domain.ImagePushedPayload")).Return(nil) | |
| 77 | 78 | ||
| 78 | - | err := svc.PutManifest(ctx, manifest) | |
| 79 | + | digest, err := svc.PutManifest(ctx, manifest) | |
| 79 | 80 | ||
| 80 | 81 | assert.NoError(t, err) | |
| 82 | + | assert.NotEmpty(t, digest) | |
| 83 | + | assert.True(t, strings.HasPrefix(digest, "sha256:")) | |
| 81 | 84 | } | |
| 82 | 85 | ||
| 83 | 86 | func TestService_PutManifest_StorageError(t *testing.T) { | |
| @@ -97,7 +100,7 @@ func TestService_PutManifest_StorageError(t *testing.T) { | |||
| 97 | 100 | ||
| 98 | 101 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(errors.New("storage full")) | |
| 99 | 102 | ||
| 100 | - | err := svc.PutManifest(ctx, manifest) | |
| 103 | + | _, err := svc.PutManifest(ctx, manifest) | |
| 101 | 104 | ||
| 102 | 105 | assert.Error(t, err) | |
| 103 | 106 | assert.Contains(t, err.Error(), "failed to store manifest") | |
| @@ -122,7 +125,7 @@ func TestService_PutManifest_EventPublishError(t *testing.T) { | |||
| 122 | 125 | eventBus.EXPECT().Publish(domain.EventImagePushed, mock.Anything).Return(errors.New("event bus error")) | |
| 123 | 126 | ||
| 124 | 127 | // Should still succeed - event publish error is logged but doesn't fail the operation | |
| 125 | - | err := svc.PutManifest(ctx, manifest) | |
| 128 | + | _, err := svc.PutManifest(ctx, manifest) | |
| 126 | 129 | ||
| 127 | 130 | assert.NoError(t, err) | |
| 128 | 131 | } | |
| @@ -144,7 +147,7 @@ func TestService_PutManifest_NilEventBus(t *testing.T) { | |||
| 144 | 147 | ||
| 145 | 148 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil) | |
| 146 | 149 | ||
| 147 | - | err := svc.PutManifest(ctx, manifest) | |
| 150 | + | _, err := svc.PutManifest(ctx, manifest) | |
| 148 | 151 | ||
| 149 | 152 | assert.NoError(t, err) | |
| 150 | 153 | } | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "context" | 5 | "context" | ||
| 6 | "errors" | 6 | "errors" | ||
| 7 | "io" | 7 | "io" | ||
| 8 | + | "strings" | |||
| 8 | "testing" | 9 | "testing" | ||
| 9 | 10 | ||||
| 10 | "github.com/bnema/zerowrap" | 11 | "github.com/bnema/zerowrap" | ||
| @@ -75,9 +76,11 @@ func TestService_PutManifest_Success(t *testing.T) { | |||||
| 75 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil) | 76 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil) | ||
| 76 | eventBus.EXPECT().Publish(domain.EventImagePushed, mock.AnythingOfType("domain.ImagePushedPayload")).Return(nil) | 77 | eventBus.EXPECT().Publish(domain.EventImagePushed, mock.AnythingOfType("domain.ImagePushedPayload")).Return(nil) | ||
| 77 | 78 | ||||
| 78 | - | err := svc.PutManifest(ctx, manifest) | 79 | + | digest, err := svc.PutManifest(ctx, manifest) |
| 79 | 80 | ||||
| 80 | assert.NoError(t, err) | 81 | assert.NoError(t, err) | ||
| 82 | + | assert.NotEmpty(t, digest) | |||
| 83 | + | assert.True(t, strings.HasPrefix(digest, "sha256:")) | |||
| 81 | } | 84 | } | ||
| 82 | 85 | ||||
| 83 | func TestService_PutManifest_StorageError(t *testing.T) { | 86 | func TestService_PutManifest_StorageError(t *testing.T) { | ||
| @@ -97,7 +100,7 @@ func TestService_PutManifest_StorageError(t *testing.T) { | |||||
| 97 | 100 | ||||
| 98 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(errors.New("storage full")) | 101 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(errors.New("storage full")) | ||
| 99 | 102 | ||||
| 100 | - | err := svc.PutManifest(ctx, manifest) | 103 | + | _, err := svc.PutManifest(ctx, manifest) |
| 101 | 104 | ||||
| 102 | assert.Error(t, err) | 105 | assert.Error(t, err) | ||
| 103 | assert.Contains(t, err.Error(), "failed to store manifest") | 106 | assert.Contains(t, err.Error(), "failed to store manifest") | ||
| @@ -122,7 +125,7 @@ func TestService_PutManifest_EventPublishError(t *testing.T) { | |||||
| 122 | eventBus.EXPECT().Publish(domain.EventImagePushed, mock.Anything).Return(errors.New("event bus error")) | 125 | eventBus.EXPECT().Publish(domain.EventImagePushed, mock.Anything).Return(errors.New("event bus error")) | ||
| 123 | 126 | ||||
| 124 | // Should still succeed - event publish error is logged but doesn't fail the operation | 127 | // Should still succeed - event publish error is logged but doesn't fail the operation | ||
| 125 | - | err := svc.PutManifest(ctx, manifest) | 128 | + | _, err := svc.PutManifest(ctx, manifest) |
| 126 | 129 | ||||
| 127 | assert.NoError(t, err) | 130 | assert.NoError(t, err) | ||
| 128 | } | 131 | } | ||
| @@ -144,7 +147,7 @@ func TestService_PutManifest_NilEventBus(t *testing.T) { | |||||
| 144 | 147 | ||||
| 145 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil) | 148 | manifestStorage.EXPECT().PutManifest("myapp", "latest", "application/vnd.docker.distribution.manifest.v2+json", manifest.Data).Return(nil) | ||
| 146 | 149 | ||||
| 147 | - | err := svc.PutManifest(ctx, manifest) | 150 | + | _, err := svc.PutManifest(ctx, manifest) |
| 148 | 151 | ||||
| 149 | assert.NoError(t, err) | 152 | assert.NoError(t, err) | ||
| 150 | } | 153 | } | ||