feat(admin): add preview API endpoints — list, delete, extend
Adds GET /admin/previews (list all previews) and DELETE/PATCH
/admin/preview/{name} (teardown / extend TTL) following the existing
prefix-route dispatch pattern. A minimal previewService interface is
defined in the admin package; the concrete *preview.Service will be
wired in Task 13.- commit
- 458a175c6ed5cf7d23fb206906cfef02596b8a2e
- author
- brice · · 2026-03-19 05:07 UTC
- committer
- brice · · 2026-03-19 05:13 UTC
- parent
- 03a3f23
- signature
- unsigned
5 files · +210 -64
M internal/adapters/in/http/admin/bootstrap_test.go +7 -7
| @@ -24,7 +24,7 @@ func TestBootstrap_FullWorkflow_HappyPath(t *testing.T) { | |||
| 24 | 24 | secretSvc := inmocks.NewMockSecretService(t) | |
| 25 | 25 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 26 | 26 | ||
| 27 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 27 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 28 | 28 | ||
| 29 | 29 | payload := dto.BootstrapRequest{ | |
| 30 | 30 | Domain: "app.example.com", | |
| @@ -73,7 +73,7 @@ func TestBootstrap_Idempotent_Rerun(t *testing.T) { | |||
| 73 | 73 | containerSvc := inmocks.NewMockContainerService(t) | |
| 74 | 74 | secretSvc := inmocks.NewMockSecretService(t) | |
| 75 | 75 | ||
| 76 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 76 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 77 | 77 | ||
| 78 | 78 | payload := dto.BootstrapRequest{ | |
| 79 | 79 | Domain: "app.example.com", | |
| @@ -137,7 +137,7 @@ func TestBootstrap_MissingDomain(t *testing.T) { | |||
| 137 | 137 | containerSvc := inmocks.NewMockContainerService(t) | |
| 138 | 138 | secretSvc := inmocks.NewMockSecretService(t) | |
| 139 | 139 | ||
| 140 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 140 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 141 | 141 | ||
| 142 | 142 | body, err := json.Marshal(dto.BootstrapRequest{Image: "myapp:latest"}) | |
| 143 | 143 | require.NoError(t, err) | |
| @@ -158,7 +158,7 @@ func TestBootstrap_MissingImage(t *testing.T) { | |||
| 158 | 158 | containerSvc := inmocks.NewMockContainerService(t) | |
| 159 | 159 | secretSvc := inmocks.NewMockSecretService(t) | |
| 160 | 160 | ||
| 161 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 161 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 162 | 162 | ||
| 163 | 163 | body, err := json.Marshal(dto.BootstrapRequest{Domain: "app.example.com"}) | |
| 164 | 164 | require.NoError(t, err) | |
| @@ -179,7 +179,7 @@ func TestBootstrap_NormalizesImageBeforeStoring(t *testing.T) { | |||
| 179 | 179 | containerSvc := inmocks.NewMockContainerService(t) | |
| 180 | 180 | secretSvc := inmocks.NewMockSecretService(t) | |
| 181 | 181 | ||
| 182 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 182 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 183 | 183 | ||
| 184 | 184 | payload := dto.BootstrapRequest{Domain: "app.example.com", Image: "pitlane:v1"} | |
| 185 | 185 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | |
| @@ -225,7 +225,7 @@ func TestBootstrap_AddRouteValidationErrors(t *testing.T) { | |||
| 225 | 225 | containerSvc := inmocks.NewMockContainerService(t) | |
| 226 | 226 | secretSvc := inmocks.NewMockSecretService(t) | |
| 227 | 227 | ||
| 228 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 228 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 229 | 229 | ||
| 230 | 230 | payload := dto.BootstrapRequest{Domain: "app.example.com", Image: "myapp:latest"} | |
| 231 | 231 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | |
| @@ -252,7 +252,7 @@ func TestBootstrap_PartialFailure_AttachmentError(t *testing.T) { | |||
| 252 | 252 | containerSvc := inmocks.NewMockContainerService(t) | |
| 253 | 253 | secretSvc := inmocks.NewMockSecretService(t) | |
| 254 | 254 | ||
| 255 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 255 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 256 | 256 | ||
| 257 | 257 | payload := dto.BootstrapRequest{ | |
| 258 | 258 | Domain: "app.example.com", | |
| @@ -24,7 +24,7 @@ func TestBootstrap_FullWorkflow_HappyPath(t *testing.T) { | |||||
| 24 | secretSvc := inmocks.NewMockSecretService(t) | 24 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 25 | registrySvc := inmocks.NewMockRegistryService(t) | 25 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 26 | 26 | ||||
| 27 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 27 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 28 | 28 | ||||
| 29 | payload := dto.BootstrapRequest{ | 29 | payload := dto.BootstrapRequest{ | ||
| 30 | Domain: "app.example.com", | 30 | Domain: "app.example.com", | ||
| @@ -73,7 +73,7 @@ func TestBootstrap_Idempotent_Rerun(t *testing.T) { | |||||
| 73 | containerSvc := inmocks.NewMockContainerService(t) | 73 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 74 | secretSvc := inmocks.NewMockSecretService(t) | 74 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 75 | 75 | ||||
| 76 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 76 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 77 | 77 | ||||
| 78 | payload := dto.BootstrapRequest{ | 78 | payload := dto.BootstrapRequest{ | ||
| 79 | Domain: "app.example.com", | 79 | Domain: "app.example.com", | ||
| @@ -137,7 +137,7 @@ func TestBootstrap_MissingDomain(t *testing.T) { | |||||
| 137 | containerSvc := inmocks.NewMockContainerService(t) | 137 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 138 | secretSvc := inmocks.NewMockSecretService(t) | 138 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 139 | 139 | ||||
| 140 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 140 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 141 | 141 | ||||
| 142 | body, err := json.Marshal(dto.BootstrapRequest{Image: "myapp:latest"}) | 142 | body, err := json.Marshal(dto.BootstrapRequest{Image: "myapp:latest"}) | ||
| 143 | require.NoError(t, err) | 143 | require.NoError(t, err) | ||
| @@ -158,7 +158,7 @@ func TestBootstrap_MissingImage(t *testing.T) { | |||||
| 158 | containerSvc := inmocks.NewMockContainerService(t) | 158 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 159 | secretSvc := inmocks.NewMockSecretService(t) | 159 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 160 | 160 | ||||
| 161 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 161 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 162 | 162 | ||||
| 163 | body, err := json.Marshal(dto.BootstrapRequest{Domain: "app.example.com"}) | 163 | body, err := json.Marshal(dto.BootstrapRequest{Domain: "app.example.com"}) | ||
| 164 | require.NoError(t, err) | 164 | require.NoError(t, err) | ||
| @@ -179,7 +179,7 @@ func TestBootstrap_NormalizesImageBeforeStoring(t *testing.T) { | |||||
| 179 | containerSvc := inmocks.NewMockContainerService(t) | 179 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 180 | secretSvc := inmocks.NewMockSecretService(t) | 180 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 181 | 181 | ||||
| 182 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 182 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 183 | 183 | ||||
| 184 | payload := dto.BootstrapRequest{Domain: "app.example.com", Image: "pitlane:v1"} | 184 | payload := dto.BootstrapRequest{Domain: "app.example.com", Image: "pitlane:v1"} | ||
| 185 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | 185 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | ||
| @@ -225,7 +225,7 @@ func TestBootstrap_AddRouteValidationErrors(t *testing.T) { | |||||
| 225 | containerSvc := inmocks.NewMockContainerService(t) | 225 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 226 | secretSvc := inmocks.NewMockSecretService(t) | 226 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 227 | 227 | ||||
| 228 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 228 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 229 | 229 | ||||
| 230 | payload := dto.BootstrapRequest{Domain: "app.example.com", Image: "myapp:latest"} | 230 | payload := dto.BootstrapRequest{Domain: "app.example.com", Image: "myapp:latest"} | ||
| 231 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | 231 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | ||
| @@ -252,7 +252,7 @@ func TestBootstrap_PartialFailure_AttachmentError(t *testing.T) { | |||||
| 252 | containerSvc := inmocks.NewMockContainerService(t) | 252 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 253 | secretSvc := inmocks.NewMockSecretService(t) | 253 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 254 | 254 | ||||
| 255 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 255 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 256 | 256 | ||||
| 257 | payload := dto.BootstrapRequest{ | 257 | payload := dto.BootstrapRequest{ | ||
| 258 | Domain: "app.example.com", | 258 | Domain: "app.example.com", | ||
M internal/adapters/in/http/admin/handler.go +5 -0
| @@ -47,6 +47,7 @@ type Handler struct { | |||
| 47 | 47 | logSvc in.LogService | |
| 48 | 48 | volumeSvc in.VolumeService | |
| 49 | 49 | registrySvc registryDeployService | |
| 50 | + | previewSvc previewService | |
| 50 | 51 | eventBus out.EventPublisher | |
| 51 | 52 | log zerowrap.Logger | |
| 52 | 53 | } | |
| @@ -142,6 +143,7 @@ func NewHandler( | |||
| 142 | 143 | eventBus out.EventPublisher, | |
| 143 | 144 | log zerowrap.Logger, | |
| 144 | 145 | backupSvc in.BackupService, | |
| 146 | + | previewSvc previewService, | |
| 145 | 147 | imageSvcs ...in.ImageService, | |
| 146 | 148 | ) *Handler { | |
| 147 | 149 | var imageSvc in.ImageService | |
| @@ -159,6 +161,7 @@ func NewHandler( | |||
| 159 | 161 | secretSvc: secretSvc, | |
| 160 | 162 | logSvc: logSvc, | |
| 161 | 163 | registrySvc: registrySvc, | |
| 164 | + | previewSvc: previewSvc, | |
| 162 | 165 | eventBus: eventBus, | |
| 163 | 166 | log: log, | |
| 164 | 167 | } | |
| @@ -239,6 +242,8 @@ func (h *Handler) matchRoute(path string) (routeHandler, bool) { | |||
| 239 | 242 | {"/images", h.handleImages}, | |
| 240 | 243 | {"/logs", h.handleLogs}, | |
| 241 | 244 | {"/autoroute/allowed-domains", h.handleAutoRouteAllowedDomains}, | |
| 245 | + | {"/previews", h.handlePreviewList}, | |
| 246 | + | {"/preview", h.handlePreviewAction}, | |
| 242 | 247 | } | |
| 243 | 248 | for _, route := range prefixRoutes { | |
| 244 | 249 | if path == route.prefix || strings.HasPrefix(path, route.prefix+"/") { | |
| @@ -47,6 +47,7 @@ type Handler struct { | |||||
| 47 | logSvc in.LogService | 47 | logSvc in.LogService | ||
| 48 | volumeSvc in.VolumeService | 48 | volumeSvc in.VolumeService | ||
| 49 | registrySvc registryDeployService | 49 | registrySvc registryDeployService | ||
| 50 | + | previewSvc previewService | |||
| 50 | eventBus out.EventPublisher | 51 | eventBus out.EventPublisher | ||
| 51 | log zerowrap.Logger | 52 | log zerowrap.Logger | ||
| 52 | } | 53 | } | ||
| @@ -142,6 +143,7 @@ func NewHandler( | |||||
| 142 | eventBus out.EventPublisher, | 143 | eventBus out.EventPublisher, | ||
| 143 | log zerowrap.Logger, | 144 | log zerowrap.Logger, | ||
| 144 | backupSvc in.BackupService, | 145 | backupSvc in.BackupService, | ||
| 146 | + | previewSvc previewService, | |||
| 145 | imageSvcs ...in.ImageService, | 147 | imageSvcs ...in.ImageService, | ||
| 146 | ) *Handler { | 148 | ) *Handler { | ||
| 147 | var imageSvc in.ImageService | 149 | var imageSvc in.ImageService | ||
| @@ -159,6 +161,7 @@ func NewHandler( | |||||
| 159 | secretSvc: secretSvc, | 161 | secretSvc: secretSvc, | ||
| 160 | logSvc: logSvc, | 162 | logSvc: logSvc, | ||
| 161 | registrySvc: registrySvc, | 163 | registrySvc: registrySvc, | ||
| 164 | + | previewSvc: previewSvc, | |||
| 162 | eventBus: eventBus, | 165 | eventBus: eventBus, | ||
| 163 | log: log, | 166 | log: log, | ||
| 164 | } | 167 | } | ||
| @@ -239,6 +242,8 @@ func (h *Handler) matchRoute(path string) (routeHandler, bool) { | |||||
| 239 | {"/images", h.handleImages}, | 242 | {"/images", h.handleImages}, | ||
| 240 | {"/logs", h.handleLogs}, | 243 | {"/logs", h.handleLogs}, | ||
| 241 | {"/autoroute/allowed-domains", h.handleAutoRouteAllowedDomains}, | 244 | {"/autoroute/allowed-domains", h.handleAutoRouteAllowedDomains}, | ||
| 245 | + | {"/previews", h.handlePreviewList}, | |||
| 246 | + | {"/preview", h.handlePreviewAction}, | |||
| 242 | } | 247 | } | ||
| 243 | for _, route := range prefixRoutes { | 248 | for _, route := range prefixRoutes { | ||
| 244 | if path == route.prefix || strings.HasPrefix(path, route.prefix+"/") { | 249 | if path == route.prefix || strings.HasPrefix(path, route.prefix+"/") { | ||
M internal/adapters/in/http/admin/handler_test.go +57 -57
| @@ -58,7 +58,7 @@ func TestHandler_RoutesGet_RequiresReadScope(t *testing.T) { | |||
| 58 | 58 | containerSvc := inmocks.NewMockContainerService(t) | |
| 59 | 59 | secretSvc := inmocks.NewMockSecretService(t) | |
| 60 | 60 | ||
| 61 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 61 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 62 | 62 | ||
| 63 | 63 | tests := []struct { | |
| 64 | 64 | name string | |
| @@ -121,7 +121,7 @@ func TestHandler_RoutesPost_RequiresWriteScope(t *testing.T) { | |||
| 121 | 121 | secretSvc := inmocks.NewMockSecretService(t) | |
| 122 | 122 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 123 | 123 | ||
| 124 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 124 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 125 | 125 | ||
| 126 | 126 | routeJSON := `{"domain": "app.example.com", "image": "myapp:latest"}` | |
| 127 | 127 | ||
| @@ -181,7 +181,7 @@ func TestHandler_RoutesPut_RequiresWriteScope(t *testing.T) { | |||
| 181 | 181 | secretSvc := inmocks.NewMockSecretService(t) | |
| 182 | 182 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 183 | 183 | ||
| 184 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 184 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 185 | 185 | ||
| 186 | 186 | routeJSON := `{"image": "myapp:v2"}` | |
| 187 | 187 | ||
| @@ -227,7 +227,7 @@ func TestHandler_RoutesPost_AllowsRouteCreationWithoutRegistryManifest(t *testin | |||
| 227 | 227 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 228 | 228 | route := domain.Route{Domain: "app.example.com", Image: "myapp:latest"} | |
| 229 | 229 | ||
| 230 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 230 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 231 | 231 | ||
| 232 | 232 | configSvc.EXPECT().AddRoute(mock.Anything, route).Return(nil).Once() | |
| 233 | 233 | ||
| @@ -250,7 +250,7 @@ func TestHandler_RoutesPut_AllowsRouteUpdateWithoutRegistryManifest(t *testing.T | |||
| 250 | 250 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 251 | 251 | route := domain.Route{Domain: "app.example.com", Image: "myapp:v2"} | |
| 252 | 252 | ||
| 253 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 253 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 254 | 254 | ||
| 255 | 255 | configSvc.EXPECT().UpdateRoute(mock.Anything, route).Return(nil).Once() | |
| 256 | 256 | ||
| @@ -271,7 +271,7 @@ func TestHandler_AttachmentsByImageGet_OneTarget(t *testing.T) { | |||
| 271 | 271 | containerSvc := inmocks.NewMockContainerService(t) | |
| 272 | 272 | secretSvc := inmocks.NewMockSecretService(t) | |
| 273 | 273 | ||
| 274 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 274 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 275 | 275 | ||
| 276 | 276 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "postgres:16").Return([]string{"app.example.com"}).Once() | |
| 277 | 277 | ||
| @@ -291,7 +291,7 @@ func TestHandler_AttachmentsByImageGet_MultipleTargets(t *testing.T) { | |||
| 291 | 291 | containerSvc := inmocks.NewMockContainerService(t) | |
| 292 | 292 | secretSvc := inmocks.NewMockSecretService(t) | |
| 293 | 293 | ||
| 294 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 294 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 295 | 295 | ||
| 296 | 296 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "postgres").Return([]string{"app.example.com", "workers"}).Once() | |
| 297 | 297 | ||
| @@ -311,7 +311,7 @@ func TestHandler_AttachmentsByImageGet_URLDecodedImage(t *testing.T) { | |||
| 311 | 311 | containerSvc := inmocks.NewMockContainerService(t) | |
| 312 | 312 | secretSvc := inmocks.NewMockSecretService(t) | |
| 313 | 313 | ||
| 314 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 314 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 315 | 315 | ||
| 316 | 316 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "registry/org/image:tag").Return([]string{"app.example.com"}).Once() | |
| 317 | 317 | ||
| @@ -331,7 +331,7 @@ func TestHandler_AttachmentsByImageGet_EmptyResultSet(t *testing.T) { | |||
| 331 | 331 | containerSvc := inmocks.NewMockContainerService(t) | |
| 332 | 332 | secretSvc := inmocks.NewMockSecretService(t) | |
| 333 | 333 | ||
| 334 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 334 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 335 | 335 | ||
| 336 | 336 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "missing:latest").Return([]string{}).Once() | |
| 337 | 337 | ||
| @@ -351,7 +351,7 @@ func TestHandler_AttachmentsByImageGet_MissingImagePath(t *testing.T) { | |||
| 351 | 351 | containerSvc := inmocks.NewMockContainerService(t) | |
| 352 | 352 | secretSvc := inmocks.NewMockSecretService(t) | |
| 353 | 353 | ||
| 354 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 354 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 355 | 355 | ||
| 356 | 356 | req := httptest.NewRequest("GET", "/admin/attachments/by-image", nil) | |
| 357 | 357 | req = req.WithContext(ctxWithScopes("admin:config:read")) | |
| @@ -369,7 +369,7 @@ func TestHandler_AttachmentsByImageGet_InvalidEncoding(t *testing.T) { | |||
| 369 | 369 | containerSvc := inmocks.NewMockContainerService(t) | |
| 370 | 370 | secretSvc := inmocks.NewMockSecretService(t) | |
| 371 | 371 | ||
| 372 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 372 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 373 | 373 | ||
| 374 | 374 | req := httptest.NewRequest("GET", "/admin/attachments/by-image/placeholder", nil) | |
| 375 | 375 | req.URL = &url.URL{Path: "/admin/attachments/by-image/%zz"} | |
| @@ -388,7 +388,7 @@ func TestHandler_AttachmentsByImageGet_RequiresReadScope(t *testing.T) { | |||
| 388 | 388 | containerSvc := inmocks.NewMockContainerService(t) | |
| 389 | 389 | secretSvc := inmocks.NewMockSecretService(t) | |
| 390 | 390 | ||
| 391 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 391 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 392 | 392 | ||
| 393 | 393 | req := httptest.NewRequest("GET", "/admin/attachments/by-image/postgres:16", nil) | |
| 394 | 394 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | |
| @@ -406,7 +406,7 @@ func TestHandler_RoutesDelete_RequiresWriteScope(t *testing.T) { | |||
| 406 | 406 | containerSvc := inmocks.NewMockContainerService(t) | |
| 407 | 407 | secretSvc := inmocks.NewMockSecretService(t) | |
| 408 | 408 | ||
| 409 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 409 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 410 | 410 | ||
| 411 | 411 | tests := []struct { | |
| 412 | 412 | name string | |
| @@ -450,7 +450,7 @@ func TestHandler_SecretsGet_RequiresReadScope(t *testing.T) { | |||
| 450 | 450 | containerSvc := inmocks.NewMockContainerService(t) | |
| 451 | 451 | secretSvc := inmocks.NewMockSecretService(t) | |
| 452 | 452 | ||
| 453 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 453 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 454 | 454 | ||
| 455 | 455 | tests := []struct { | |
| 456 | 456 | name string | |
| @@ -507,7 +507,7 @@ func TestHandler_SecretsPost_RequiresWriteScope(t *testing.T) { | |||
| 507 | 507 | containerSvc := inmocks.NewMockContainerService(t) | |
| 508 | 508 | secretSvc := inmocks.NewMockSecretService(t) | |
| 509 | 509 | ||
| 510 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 510 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 511 | 511 | ||
| 512 | 512 | secretsJSON := `{"API_KEY": "secret123"}` | |
| 513 | 513 | ||
| @@ -556,7 +556,7 @@ func TestHandler_SecretsDelete_RequiresWriteScope(t *testing.T) { | |||
| 556 | 556 | containerSvc := inmocks.NewMockContainerService(t) | |
| 557 | 557 | secretSvc := inmocks.NewMockSecretService(t) | |
| 558 | 558 | ||
| 559 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 559 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 560 | 560 | ||
| 561 | 561 | tests := []struct { | |
| 562 | 562 | name string | |
| @@ -600,7 +600,7 @@ func TestHandler_AttachmentSecretsPost(t *testing.T) { | |||
| 600 | 600 | containerSvc := inmocks.NewMockContainerService(t) | |
| 601 | 601 | secretSvc := inmocks.NewMockSecretService(t) | |
| 602 | 602 | ||
| 603 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 603 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 604 | 604 | ||
| 605 | 605 | secretSvc.EXPECT().SetAttachment(mock.Anything, "app.example.com", "redis", mock.AnythingOfType("map[string]string")).Return(nil) | |
| 606 | 606 | ||
| @@ -626,7 +626,7 @@ func TestHandler_AttachmentSecretsDelete(t *testing.T) { | |||
| 626 | 626 | containerSvc := inmocks.NewMockContainerService(t) | |
| 627 | 627 | secretSvc := inmocks.NewMockSecretService(t) | |
| 628 | 628 | ||
| 629 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 629 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 630 | 630 | ||
| 631 | 631 | secretSvc.EXPECT().DeleteAttachment(mock.Anything, "app.example.com", "redis", "REDIS_URL").Return(nil) | |
| 632 | 632 | ||
| @@ -652,7 +652,7 @@ func TestHandler_Bootstrap_HappyPath(t *testing.T) { | |||
| 652 | 652 | secretSvc := inmocks.NewMockSecretService(t) | |
| 653 | 653 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 654 | 654 | ||
| 655 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 655 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 656 | 656 | ||
| 657 | 657 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | |
| 658 | 658 | configSvc.EXPECT().AddRoute(mock.Anything, domain.Route{Domain: "app.example.com", Image: "reg.bnema.dev/myapp"}).Return(nil).Once() | |
| @@ -699,7 +699,7 @@ func TestHandler_Bootstrap_RequiresPermissions(t *testing.T) { | |||
| 699 | 699 | containerSvc := inmocks.NewMockContainerService(t) | |
| 700 | 700 | secretSvc := inmocks.NewMockSecretService(t) | |
| 701 | 701 | ||
| 702 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 702 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 703 | 703 | ||
| 704 | 704 | tests := []struct { | |
| 705 | 705 | name string | |
| @@ -741,7 +741,7 @@ func TestHandler_Status_RequiresReadScope(t *testing.T) { | |||
| 741 | 741 | containerSvc := inmocks.NewMockContainerService(t) | |
| 742 | 742 | secretSvc := inmocks.NewMockSecretService(t) | |
| 743 | 743 | ||
| 744 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 744 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 745 | 745 | ||
| 746 | 746 | tests := []struct { | |
| 747 | 747 | name string | |
| @@ -795,7 +795,7 @@ func TestHandler_Config_RequiresReadScope(t *testing.T) { | |||
| 795 | 795 | containerSvc := inmocks.NewMockContainerService(t) | |
| 796 | 796 | secretSvc := inmocks.NewMockSecretService(t) | |
| 797 | 797 | ||
| 798 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 798 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 799 | 799 | ||
| 800 | 800 | tests := []struct { | |
| 801 | 801 | name string | |
| @@ -852,7 +852,7 @@ func TestHandler_Reload_RequiresWriteScope(t *testing.T) { | |||
| 852 | 852 | containerSvc := inmocks.NewMockContainerService(t) | |
| 853 | 853 | secretSvc := inmocks.NewMockSecretService(t) | |
| 854 | 854 | ||
| 855 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 855 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 856 | 856 | ||
| 857 | 857 | tests := []struct { | |
| 858 | 858 | name string | |
| @@ -906,7 +906,7 @@ func TestHandler_RoutesGet_ReturnsRoutes(t *testing.T) { | |||
| 906 | 906 | containerSvc := inmocks.NewMockContainerService(t) | |
| 907 | 907 | secretSvc := inmocks.NewMockSecretService(t) | |
| 908 | 908 | ||
| 909 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 909 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 910 | 910 | ||
| 911 | 911 | expectedRoutes := []domain.Route{ | |
| 912 | 912 | {Domain: "app1.example.com", Image: "app1:latest"}, | |
| @@ -941,7 +941,7 @@ func TestHandler_RoutesGet_SingleRoute(t *testing.T) { | |||
| 941 | 941 | containerSvc := inmocks.NewMockContainerService(t) | |
| 942 | 942 | secretSvc := inmocks.NewMockSecretService(t) | |
| 943 | 943 | ||
| 944 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 944 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 945 | 945 | ||
| 946 | 946 | configSvc.EXPECT().GetRoute(mock.Anything, "app.example.com").Return(&domain.Route{ | |
| 947 | 947 | Domain: "app.example.com", Image: "app:latest", | |
| @@ -972,7 +972,7 @@ func TestHandler_RoutesGet_NotFound(t *testing.T) { | |||
| 972 | 972 | containerSvc := inmocks.NewMockContainerService(t) | |
| 973 | 973 | secretSvc := inmocks.NewMockSecretService(t) | |
| 974 | 974 | ||
| 975 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 975 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 976 | 976 | ||
| 977 | 977 | configSvc.EXPECT().GetRoute(mock.Anything, "nonexistent.example.com").Return(nil, domain.ErrRouteNotFound) | |
| 978 | 978 | ||
| @@ -1016,7 +1016,7 @@ func TestHandler_RoutesPost_MissingFields(t *testing.T) { | |||
| 1016 | 1016 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1017 | 1017 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 1018 | 1018 | ||
| 1019 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 1019 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 1020 | 1020 | ||
| 1021 | 1021 | configSvc.EXPECT().AddRoute(mock.Anything, mock.Anything).Return(tt.mockErr) | |
| 1022 | 1022 | ||
| @@ -1037,7 +1037,7 @@ func TestHandler_RoutesPut_MissingDomain(t *testing.T) { | |||
| 1037 | 1037 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1038 | 1038 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1039 | 1039 | ||
| 1040 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1040 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1041 | 1041 | ||
| 1042 | 1042 | req := httptest.NewRequest("PUT", "/admin/routes/", bytes.NewBufferString(`{"image": "app:latest"}`)) | |
| 1043 | 1043 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | |
| @@ -1054,7 +1054,7 @@ func TestHandler_RoutesDelete_MissingDomain(t *testing.T) { | |||
| 1054 | 1054 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1055 | 1055 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1056 | 1056 | ||
| 1057 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1057 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1058 | 1058 | ||
| 1059 | 1059 | req := httptest.NewRequest("DELETE", "/admin/routes/", nil) | |
| 1060 | 1060 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | |
| @@ -1071,7 +1071,7 @@ func TestHandler_Secrets_MissingDomain(t *testing.T) { | |||
| 1071 | 1071 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1072 | 1072 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1073 | 1073 | ||
| 1074 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1074 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1075 | 1075 | ||
| 1076 | 1076 | req := httptest.NewRequest("GET", "/admin/secrets/", nil) | |
| 1077 | 1077 | req = req.WithContext(ctxWithScopes("admin:secrets:read")) | |
| @@ -1088,7 +1088,7 @@ func TestHandler_SecretsDelete_MissingKey(t *testing.T) { | |||
| 1088 | 1088 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1089 | 1089 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1090 | 1090 | ||
| 1091 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1091 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1092 | 1092 | ||
| 1093 | 1093 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com", nil) | |
| 1094 | 1094 | req = req.WithContext(ctxWithScopes("admin:secrets:write")) | |
| @@ -1105,7 +1105,7 @@ func TestHandler_MethodNotAllowed(t *testing.T) { | |||
| 1105 | 1105 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1106 | 1106 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1107 | 1107 | ||
| 1108 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1108 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1109 | 1109 | ||
| 1110 | 1110 | tests := []struct { | |
| 1111 | 1111 | method string | |
| @@ -1138,7 +1138,7 @@ func TestHandler_Restart_InvalidDomain(t *testing.T) { | |||
| 1138 | 1138 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1139 | 1139 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1140 | 1140 | ||
| 1141 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1141 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1142 | 1142 | ||
| 1143 | 1143 | tests := []struct { | |
| 1144 | 1144 | name string | |
| @@ -1168,7 +1168,7 @@ func TestHandler_Restart_ContainerNotFound(t *testing.T) { | |||
| 1168 | 1168 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1169 | 1169 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1170 | 1170 | ||
| 1171 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1171 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1172 | 1172 | ||
| 1173 | 1173 | containerSvc.EXPECT().Restart(mock.Anything, "nonexistent.example.com", false).Return(domain.ErrContainerNotFound) | |
| 1174 | 1174 | ||
| @@ -1187,7 +1187,7 @@ func TestHandler_Restart_GenericError(t *testing.T) { | |||
| 1187 | 1187 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1188 | 1188 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1189 | 1189 | ||
| 1190 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1190 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1191 | 1191 | ||
| 1192 | 1192 | containerSvc.EXPECT().Restart(mock.Anything, "test.example.com", false).Return(fmt.Errorf("docker daemon error: secret details")) | |
| 1193 | 1193 | ||
| @@ -1209,7 +1209,7 @@ func TestHandler_Restart_Success(t *testing.T) { | |||
| 1209 | 1209 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1210 | 1210 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1211 | 1211 | ||
| 1212 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1212 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1213 | 1213 | ||
| 1214 | 1214 | tests := []struct { | |
| 1215 | 1215 | name string | |
| @@ -1256,7 +1256,7 @@ func TestHandler_Tags_InvalidRepository(t *testing.T) { | |||
| 1256 | 1256 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1257 | 1257 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1258 | 1258 | ||
| 1259 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1259 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1260 | 1260 | ||
| 1261 | 1261 | tests := []struct { | |
| 1262 | 1262 | name string | |
| @@ -1287,7 +1287,7 @@ func TestHandler_Tags_Success(t *testing.T) { | |||
| 1287 | 1287 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1288 | 1288 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 1289 | 1289 | ||
| 1290 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 1290 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 1291 | 1291 | ||
| 1292 | 1292 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return([]string{"latest", "v1.0"}, nil) | |
| 1293 | 1293 | ||
| @@ -1314,7 +1314,7 @@ func TestHandler_Tags_Error(t *testing.T) { | |||
| 1314 | 1314 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1315 | 1315 | registrySvc := inmocks.NewMockRegistryService(t) | |
| 1316 | 1316 | ||
| 1317 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | |
| 1317 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) | |
| 1318 | 1318 | ||
| 1319 | 1319 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return(nil, fmt.Errorf("registry error")) | |
| 1320 | 1320 | ||
| @@ -1334,7 +1334,7 @@ func TestHandler_AttachmentSecretsPost_RequiresWriteScope(t *testing.T) { | |||
| 1334 | 1334 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1335 | 1335 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1336 | 1336 | ||
| 1337 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1337 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1338 | 1338 | ||
| 1339 | 1339 | body := `{"REDIS_URL": "redis://localhost:6379"}` | |
| 1340 | 1340 | req := httptest.NewRequest("POST", "/admin/secrets/app.example.com/attachments/redis", bytes.NewBufferString(body)) | |
| @@ -1352,7 +1352,7 @@ func TestHandler_AttachmentSecretsDelete_RequiresWriteScope(t *testing.T) { | |||
| 1352 | 1352 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1353 | 1353 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1354 | 1354 | ||
| 1355 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1355 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1356 | 1356 | ||
| 1357 | 1357 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com/attachments/redis/REDIS_URL", nil) | |
| 1358 | 1358 | req = req.WithContext(ctxWithScopes("admin:secrets:read")) | |
| @@ -1369,7 +1369,7 @@ func TestHandler_AttachmentSecretsDelete_RequiresKey(t *testing.T) { | |||
| 1369 | 1369 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1370 | 1370 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1371 | 1371 | ||
| 1372 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1372 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1373 | 1373 | ||
| 1374 | 1374 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com/attachments/redis", nil) | |
| 1375 | 1375 | req = req.WithContext(ctxWithScopes("admin:secrets:write")) | |
| @@ -1387,7 +1387,7 @@ func TestHandler_AttachmentSecretsPost_InvalidJSON(t *testing.T) { | |||
| 1387 | 1387 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1388 | 1388 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1389 | 1389 | ||
| 1390 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1390 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1391 | 1391 | ||
| 1392 | 1392 | body := `{not valid json` | |
| 1393 | 1393 | req := httptest.NewRequest("POST", "/admin/secrets/app.example.com/attachments/redis", bytes.NewBufferString(body)) | |
| @@ -1406,7 +1406,7 @@ func TestHandler_NotFound(t *testing.T) { | |||
| 1406 | 1406 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1407 | 1407 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1408 | 1408 | ||
| 1409 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1409 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1410 | 1410 | ||
| 1411 | 1411 | req := httptest.NewRequest("GET", "/admin/unknown", nil) | |
| 1412 | 1412 | req = req.WithContext(ctxWithScopes("admin:*:*")) | |
| @@ -1426,7 +1426,7 @@ func TestHandler_BackupsStatus(t *testing.T) { | |||
| 1426 | 1426 | ||
| 1427 | 1427 | backupSvc.EXPECT().Status(mock.Anything).Return([]domain.BackupJob{{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted, FilePath: "/var/lib/gordon/backups/private.bak"}}, nil) | |
| 1428 | 1428 | ||
| 1429 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | |
| 1429 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) | |
| 1430 | 1430 | ||
| 1431 | 1431 | req := httptest.NewRequest("GET", "/admin/backups/status", nil) | |
| 1432 | 1432 | req = req.WithContext(ctxWithScopes("admin:status:read")) | |
| @@ -1448,7 +1448,7 @@ func TestHandler_BackupsListDomain(t *testing.T) { | |||
| 1448 | 1448 | ||
| 1449 | 1449 | backupSvc.EXPECT().ListBackups(mock.Anything, "app.example.com").Return([]domain.BackupJob{{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | |
| 1450 | 1450 | ||
| 1451 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | |
| 1451 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) | |
| 1452 | 1452 | ||
| 1453 | 1453 | req := httptest.NewRequest("GET", "/admin/backups/app.example.com", nil) | |
| 1454 | 1454 | req = req.WithContext(ctxWithScopes("admin:status:read")) | |
| @@ -1469,7 +1469,7 @@ func TestHandler_BackupsRunDomain(t *testing.T) { | |||
| 1469 | 1469 | ||
| 1470 | 1470 | backupSvc.EXPECT().RunBackup(mock.Anything, "app.example.com", "postgres").Return(&domain.BackupResult{Job: domain.BackupJob{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | |
| 1471 | 1471 | ||
| 1472 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | |
| 1472 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) | |
| 1473 | 1473 | ||
| 1474 | 1474 | body := bytes.NewBufferString(`{"db":"postgres"}`) | |
| 1475 | 1475 | req := httptest.NewRequest("POST", "/admin/backups/app.example.com", body) | |
| @@ -1491,7 +1491,7 @@ func TestHandler_BackupsRunDomain_ChunkedBodyIsDecoded(t *testing.T) { | |||
| 1491 | 1491 | ||
| 1492 | 1492 | backupSvc.EXPECT().RunBackup(mock.Anything, "app.example.com", "postgres").Return(&domain.BackupResult{Job: domain.BackupJob{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | |
| 1493 | 1493 | ||
| 1494 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | |
| 1494 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) | |
| 1495 | 1495 | ||
| 1496 | 1496 | body := bytes.NewBufferString(`{"db":"postgres"}`) | |
| 1497 | 1497 | req := httptest.NewRequest("POST", "/admin/backups/app.example.com", body) | |
| @@ -1514,7 +1514,7 @@ func TestHandler_BackupsDetectDomain(t *testing.T) { | |||
| 1514 | 1514 | ||
| 1515 | 1515 | backupSvc.EXPECT().DetectDatabases(mock.Anything, "app.example.com").Return([]domain.DBInfo{{Type: domain.DBTypePostgreSQL, Name: "postgres", Port: 5432}}, nil) | |
| 1516 | 1516 | ||
| 1517 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | |
| 1517 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) | |
| 1518 | 1518 | ||
| 1519 | 1519 | req := httptest.NewRequest("GET", "/admin/backups/app.example.com/detect", nil) | |
| 1520 | 1520 | req = req.WithContext(ctxWithScopes("admin:status:read")) | |
| @@ -1556,7 +1556,7 @@ func TestHandler_ImagesGet_ReturnsMappedList(t *testing.T) { | |||
| 1556 | 1556 | }, | |
| 1557 | 1557 | } | |
| 1558 | 1558 | ||
| 1559 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | |
| 1559 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) | |
| 1560 | 1560 | ||
| 1561 | 1561 | req := httptest.NewRequest("GET", "/admin/images", nil) | |
| 1562 | 1562 | req = req.WithContext(ctxWithScopes("admin:status:read")) | |
| @@ -1617,7 +1617,7 @@ func TestHandler_ImagesPrune_AcceptsOptionalKeepLast(t *testing.T) { | |||
| 1617 | 1617 | }, | |
| 1618 | 1618 | } | |
| 1619 | 1619 | ||
| 1620 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | |
| 1620 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) | |
| 1621 | 1621 | ||
| 1622 | 1622 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(tt.body)) | |
| 1623 | 1623 | req = req.WithContext(ctxWithScopes("admin:config:write")) | |
| @@ -1646,7 +1646,7 @@ func TestHandler_Images_ErrorMappingForServiceFailures(t *testing.T) { | |||
| 1646 | 1646 | }, | |
| 1647 | 1647 | } | |
| 1648 | 1648 | ||
| 1649 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | |
| 1649 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) | |
| 1650 | 1650 | ||
| 1651 | 1651 | req := httptest.NewRequest("GET", "/admin/images", nil) | |
| 1652 | 1652 | req = req.WithContext(ctxWithScopes("admin:status:read")) | |
| @@ -1671,7 +1671,7 @@ func TestHandler_Images_ErrorMappingForServiceFailures(t *testing.T) { | |||
| 1671 | 1671 | }, | |
| 1672 | 1672 | } | |
| 1673 | 1673 | ||
| 1674 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | |
| 1674 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) | |
| 1675 | 1675 | ||
| 1676 | 1676 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(`{"keep_last": 2}`)) | |
| 1677 | 1677 | req = req.WithContext(ctxWithScopes("admin:config:write")) | |
| @@ -1692,7 +1692,7 @@ func TestHandler_ImagesPrune_ValidationAndAvailability(t *testing.T) { | |||
| 1692 | 1692 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1693 | 1693 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1694 | 1694 | ||
| 1695 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, &stubImageService{}) | |
| 1695 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, &stubImageService{}) | |
| 1696 | 1696 | ||
| 1697 | 1697 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(`{"keep_last": -1}`)) | |
| 1698 | 1698 | req = req.WithContext(ctxWithScopes("admin:config:write")) | |
| @@ -1710,7 +1710,7 @@ func TestHandler_ImagesPrune_ValidationAndAvailability(t *testing.T) { | |||
| 1710 | 1710 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1711 | 1711 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1712 | 1712 | ||
| 1713 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | |
| 1713 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) | |
| 1714 | 1714 | ||
| 1715 | 1715 | req := httptest.NewRequest("GET", "/admin/images", nil) | |
| 1716 | 1716 | req = req.WithContext(ctxWithScopes("admin:status:read")) | |
| @@ -1781,7 +1781,7 @@ func TestHandler_ImagesPrune_ScopeDefaults(t *testing.T) { | |||
| 1781 | 1781 | }, | |
| 1782 | 1782 | } | |
| 1783 | 1783 | ||
| 1784 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | |
| 1784 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) | |
| 1785 | 1785 | ||
| 1786 | 1786 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(tt.body)) | |
| 1787 | 1787 | req = req.WithContext(ctxWithScopes("admin:config:write")) | |
| @@ -1806,7 +1806,7 @@ func TestHandler_Images_Authorization(t *testing.T) { | |||
| 1806 | 1806 | containerSvc := inmocks.NewMockContainerService(t) | |
| 1807 | 1807 | secretSvc := inmocks.NewMockSecretService(t) | |
| 1808 | 1808 | ||
| 1809 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, &stubImageService{}) | |
| 1809 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, &stubImageService{}) | |
| 1810 | 1810 | ||
| 1811 | 1811 | t.Run("images list requires status:read", func(t *testing.T) { | |
| 1812 | 1812 | req := httptest.NewRequest("GET", "/admin/images", nil) | |
| @@ -58,7 +58,7 @@ func TestHandler_RoutesGet_RequiresReadScope(t *testing.T) { | |||||
| 58 | containerSvc := inmocks.NewMockContainerService(t) | 58 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 59 | secretSvc := inmocks.NewMockSecretService(t) | 59 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 60 | 60 | ||||
| 61 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 61 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 62 | 62 | ||||
| 63 | tests := []struct { | 63 | tests := []struct { | ||
| 64 | name string | 64 | name string | ||
| @@ -121,7 +121,7 @@ func TestHandler_RoutesPost_RequiresWriteScope(t *testing.T) { | |||||
| 121 | secretSvc := inmocks.NewMockSecretService(t) | 121 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 122 | registrySvc := inmocks.NewMockRegistryService(t) | 122 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 123 | 123 | ||||
| 124 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 124 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 125 | 125 | ||||
| 126 | routeJSON := `{"domain": "app.example.com", "image": "myapp:latest"}` | 126 | routeJSON := `{"domain": "app.example.com", "image": "myapp:latest"}` | ||
| 127 | 127 | ||||
| @@ -181,7 +181,7 @@ func TestHandler_RoutesPut_RequiresWriteScope(t *testing.T) { | |||||
| 181 | secretSvc := inmocks.NewMockSecretService(t) | 181 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 182 | registrySvc := inmocks.NewMockRegistryService(t) | 182 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 183 | 183 | ||||
| 184 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 184 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 185 | 185 | ||||
| 186 | routeJSON := `{"image": "myapp:v2"}` | 186 | routeJSON := `{"image": "myapp:v2"}` | ||
| 187 | 187 | ||||
| @@ -227,7 +227,7 @@ func TestHandler_RoutesPost_AllowsRouteCreationWithoutRegistryManifest(t *testin | |||||
| 227 | registrySvc := inmocks.NewMockRegistryService(t) | 227 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 228 | route := domain.Route{Domain: "app.example.com", Image: "myapp:latest"} | 228 | route := domain.Route{Domain: "app.example.com", Image: "myapp:latest"} | ||
| 229 | 229 | ||||
| 230 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 230 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 231 | 231 | ||||
| 232 | configSvc.EXPECT().AddRoute(mock.Anything, route).Return(nil).Once() | 232 | configSvc.EXPECT().AddRoute(mock.Anything, route).Return(nil).Once() | ||
| 233 | 233 | ||||
| @@ -250,7 +250,7 @@ func TestHandler_RoutesPut_AllowsRouteUpdateWithoutRegistryManifest(t *testing.T | |||||
| 250 | registrySvc := inmocks.NewMockRegistryService(t) | 250 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 251 | route := domain.Route{Domain: "app.example.com", Image: "myapp:v2"} | 251 | route := domain.Route{Domain: "app.example.com", Image: "myapp:v2"} | ||
| 252 | 252 | ||||
| 253 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 253 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 254 | 254 | ||||
| 255 | configSvc.EXPECT().UpdateRoute(mock.Anything, route).Return(nil).Once() | 255 | configSvc.EXPECT().UpdateRoute(mock.Anything, route).Return(nil).Once() | ||
| 256 | 256 | ||||
| @@ -271,7 +271,7 @@ func TestHandler_AttachmentsByImageGet_OneTarget(t *testing.T) { | |||||
| 271 | containerSvc := inmocks.NewMockContainerService(t) | 271 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 272 | secretSvc := inmocks.NewMockSecretService(t) | 272 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 273 | 273 | ||||
| 274 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 274 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 275 | 275 | ||||
| 276 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "postgres:16").Return([]string{"app.example.com"}).Once() | 276 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "postgres:16").Return([]string{"app.example.com"}).Once() | ||
| 277 | 277 | ||||
| @@ -291,7 +291,7 @@ func TestHandler_AttachmentsByImageGet_MultipleTargets(t *testing.T) { | |||||
| 291 | containerSvc := inmocks.NewMockContainerService(t) | 291 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 292 | secretSvc := inmocks.NewMockSecretService(t) | 292 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 293 | 293 | ||||
| 294 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 294 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 295 | 295 | ||||
| 296 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "postgres").Return([]string{"app.example.com", "workers"}).Once() | 296 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "postgres").Return([]string{"app.example.com", "workers"}).Once() | ||
| 297 | 297 | ||||
| @@ -311,7 +311,7 @@ func TestHandler_AttachmentsByImageGet_URLDecodedImage(t *testing.T) { | |||||
| 311 | containerSvc := inmocks.NewMockContainerService(t) | 311 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 312 | secretSvc := inmocks.NewMockSecretService(t) | 312 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 313 | 313 | ||||
| 314 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 314 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 315 | 315 | ||||
| 316 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "registry/org/image:tag").Return([]string{"app.example.com"}).Once() | 316 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "registry/org/image:tag").Return([]string{"app.example.com"}).Once() | ||
| 317 | 317 | ||||
| @@ -331,7 +331,7 @@ func TestHandler_AttachmentsByImageGet_EmptyResultSet(t *testing.T) { | |||||
| 331 | containerSvc := inmocks.NewMockContainerService(t) | 331 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 332 | secretSvc := inmocks.NewMockSecretService(t) | 332 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 333 | 333 | ||||
| 334 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 334 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 335 | 335 | ||||
| 336 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "missing:latest").Return([]string{}).Once() | 336 | configSvc.EXPECT().FindAttachmentTargetsByImage(mock.Anything, "missing:latest").Return([]string{}).Once() | ||
| 337 | 337 | ||||
| @@ -351,7 +351,7 @@ func TestHandler_AttachmentsByImageGet_MissingImagePath(t *testing.T) { | |||||
| 351 | containerSvc := inmocks.NewMockContainerService(t) | 351 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 352 | secretSvc := inmocks.NewMockSecretService(t) | 352 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 353 | 353 | ||||
| 354 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 354 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 355 | 355 | ||||
| 356 | req := httptest.NewRequest("GET", "/admin/attachments/by-image", nil) | 356 | req := httptest.NewRequest("GET", "/admin/attachments/by-image", nil) | ||
| 357 | req = req.WithContext(ctxWithScopes("admin:config:read")) | 357 | req = req.WithContext(ctxWithScopes("admin:config:read")) | ||
| @@ -369,7 +369,7 @@ func TestHandler_AttachmentsByImageGet_InvalidEncoding(t *testing.T) { | |||||
| 369 | containerSvc := inmocks.NewMockContainerService(t) | 369 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 370 | secretSvc := inmocks.NewMockSecretService(t) | 370 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 371 | 371 | ||||
| 372 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 372 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 373 | 373 | ||||
| 374 | req := httptest.NewRequest("GET", "/admin/attachments/by-image/placeholder", nil) | 374 | req := httptest.NewRequest("GET", "/admin/attachments/by-image/placeholder", nil) | ||
| 375 | req.URL = &url.URL{Path: "/admin/attachments/by-image/%zz"} | 375 | req.URL = &url.URL{Path: "/admin/attachments/by-image/%zz"} | ||
| @@ -388,7 +388,7 @@ func TestHandler_AttachmentsByImageGet_RequiresReadScope(t *testing.T) { | |||||
| 388 | containerSvc := inmocks.NewMockContainerService(t) | 388 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 389 | secretSvc := inmocks.NewMockSecretService(t) | 389 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 390 | 390 | ||||
| 391 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 391 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 392 | 392 | ||||
| 393 | req := httptest.NewRequest("GET", "/admin/attachments/by-image/postgres:16", nil) | 393 | req := httptest.NewRequest("GET", "/admin/attachments/by-image/postgres:16", nil) | ||
| 394 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | 394 | req = req.WithContext(ctxWithScopes("admin:routes:read")) | ||
| @@ -406,7 +406,7 @@ func TestHandler_RoutesDelete_RequiresWriteScope(t *testing.T) { | |||||
| 406 | containerSvc := inmocks.NewMockContainerService(t) | 406 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 407 | secretSvc := inmocks.NewMockSecretService(t) | 407 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 408 | 408 | ||||
| 409 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 409 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 410 | 410 | ||||
| 411 | tests := []struct { | 411 | tests := []struct { | ||
| 412 | name string | 412 | name string | ||
| @@ -450,7 +450,7 @@ func TestHandler_SecretsGet_RequiresReadScope(t *testing.T) { | |||||
| 450 | containerSvc := inmocks.NewMockContainerService(t) | 450 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 451 | secretSvc := inmocks.NewMockSecretService(t) | 451 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 452 | 452 | ||||
| 453 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 453 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 454 | 454 | ||||
| 455 | tests := []struct { | 455 | tests := []struct { | ||
| 456 | name string | 456 | name string | ||
| @@ -507,7 +507,7 @@ func TestHandler_SecretsPost_RequiresWriteScope(t *testing.T) { | |||||
| 507 | containerSvc := inmocks.NewMockContainerService(t) | 507 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 508 | secretSvc := inmocks.NewMockSecretService(t) | 508 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 509 | 509 | ||||
| 510 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 510 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 511 | 511 | ||||
| 512 | secretsJSON := `{"API_KEY": "secret123"}` | 512 | secretsJSON := `{"API_KEY": "secret123"}` | ||
| 513 | 513 | ||||
| @@ -556,7 +556,7 @@ func TestHandler_SecretsDelete_RequiresWriteScope(t *testing.T) { | |||||
| 556 | containerSvc := inmocks.NewMockContainerService(t) | 556 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 557 | secretSvc := inmocks.NewMockSecretService(t) | 557 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 558 | 558 | ||||
| 559 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 559 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 560 | 560 | ||||
| 561 | tests := []struct { | 561 | tests := []struct { | ||
| 562 | name string | 562 | name string | ||
| @@ -600,7 +600,7 @@ func TestHandler_AttachmentSecretsPost(t *testing.T) { | |||||
| 600 | containerSvc := inmocks.NewMockContainerService(t) | 600 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 601 | secretSvc := inmocks.NewMockSecretService(t) | 601 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 602 | 602 | ||||
| 603 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 603 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 604 | 604 | ||||
| 605 | secretSvc.EXPECT().SetAttachment(mock.Anything, "app.example.com", "redis", mock.AnythingOfType("map[string]string")).Return(nil) | 605 | secretSvc.EXPECT().SetAttachment(mock.Anything, "app.example.com", "redis", mock.AnythingOfType("map[string]string")).Return(nil) | ||
| 606 | 606 | ||||
| @@ -626,7 +626,7 @@ func TestHandler_AttachmentSecretsDelete(t *testing.T) { | |||||
| 626 | containerSvc := inmocks.NewMockContainerService(t) | 626 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 627 | secretSvc := inmocks.NewMockSecretService(t) | 627 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 628 | 628 | ||||
| 629 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 629 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 630 | 630 | ||||
| 631 | secretSvc.EXPECT().DeleteAttachment(mock.Anything, "app.example.com", "redis", "REDIS_URL").Return(nil) | 631 | secretSvc.EXPECT().DeleteAttachment(mock.Anything, "app.example.com", "redis", "REDIS_URL").Return(nil) | ||
| 632 | 632 | ||||
| @@ -652,7 +652,7 @@ func TestHandler_Bootstrap_HappyPath(t *testing.T) { | |||||
| 652 | secretSvc := inmocks.NewMockSecretService(t) | 652 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 653 | registrySvc := inmocks.NewMockRegistryService(t) | 653 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 654 | 654 | ||||
| 655 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 655 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 656 | 656 | ||||
| 657 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | 657 | configSvc.EXPECT().GetRegistryDomain().Return("reg.bnema.dev").Once() | ||
| 658 | configSvc.EXPECT().AddRoute(mock.Anything, domain.Route{Domain: "app.example.com", Image: "reg.bnema.dev/myapp"}).Return(nil).Once() | 658 | configSvc.EXPECT().AddRoute(mock.Anything, domain.Route{Domain: "app.example.com", Image: "reg.bnema.dev/myapp"}).Return(nil).Once() | ||
| @@ -699,7 +699,7 @@ func TestHandler_Bootstrap_RequiresPermissions(t *testing.T) { | |||||
| 699 | containerSvc := inmocks.NewMockContainerService(t) | 699 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 700 | secretSvc := inmocks.NewMockSecretService(t) | 700 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 701 | 701 | ||||
| 702 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 702 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 703 | 703 | ||||
| 704 | tests := []struct { | 704 | tests := []struct { | ||
| 705 | name string | 705 | name string | ||
| @@ -741,7 +741,7 @@ func TestHandler_Status_RequiresReadScope(t *testing.T) { | |||||
| 741 | containerSvc := inmocks.NewMockContainerService(t) | 741 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 742 | secretSvc := inmocks.NewMockSecretService(t) | 742 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 743 | 743 | ||||
| 744 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 744 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 745 | 745 | ||||
| 746 | tests := []struct { | 746 | tests := []struct { | ||
| 747 | name string | 747 | name string | ||
| @@ -795,7 +795,7 @@ func TestHandler_Config_RequiresReadScope(t *testing.T) { | |||||
| 795 | containerSvc := inmocks.NewMockContainerService(t) | 795 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 796 | secretSvc := inmocks.NewMockSecretService(t) | 796 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 797 | 797 | ||||
| 798 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 798 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 799 | 799 | ||||
| 800 | tests := []struct { | 800 | tests := []struct { | ||
| 801 | name string | 801 | name string | ||
| @@ -852,7 +852,7 @@ func TestHandler_Reload_RequiresWriteScope(t *testing.T) { | |||||
| 852 | containerSvc := inmocks.NewMockContainerService(t) | 852 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 853 | secretSvc := inmocks.NewMockSecretService(t) | 853 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 854 | 854 | ||||
| 855 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 855 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 856 | 856 | ||||
| 857 | tests := []struct { | 857 | tests := []struct { | ||
| 858 | name string | 858 | name string | ||
| @@ -906,7 +906,7 @@ func TestHandler_RoutesGet_ReturnsRoutes(t *testing.T) { | |||||
| 906 | containerSvc := inmocks.NewMockContainerService(t) | 906 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 907 | secretSvc := inmocks.NewMockSecretService(t) | 907 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 908 | 908 | ||||
| 909 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 909 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 910 | 910 | ||||
| 911 | expectedRoutes := []domain.Route{ | 911 | expectedRoutes := []domain.Route{ | ||
| 912 | {Domain: "app1.example.com", Image: "app1:latest"}, | 912 | {Domain: "app1.example.com", Image: "app1:latest"}, | ||
| @@ -941,7 +941,7 @@ func TestHandler_RoutesGet_SingleRoute(t *testing.T) { | |||||
| 941 | containerSvc := inmocks.NewMockContainerService(t) | 941 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 942 | secretSvc := inmocks.NewMockSecretService(t) | 942 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 943 | 943 | ||||
| 944 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 944 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 945 | 945 | ||||
| 946 | configSvc.EXPECT().GetRoute(mock.Anything, "app.example.com").Return(&domain.Route{ | 946 | configSvc.EXPECT().GetRoute(mock.Anything, "app.example.com").Return(&domain.Route{ | ||
| 947 | Domain: "app.example.com", Image: "app:latest", | 947 | Domain: "app.example.com", Image: "app:latest", | ||
| @@ -972,7 +972,7 @@ func TestHandler_RoutesGet_NotFound(t *testing.T) { | |||||
| 972 | containerSvc := inmocks.NewMockContainerService(t) | 972 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 973 | secretSvc := inmocks.NewMockSecretService(t) | 973 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 974 | 974 | ||||
| 975 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 975 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 976 | 976 | ||||
| 977 | configSvc.EXPECT().GetRoute(mock.Anything, "nonexistent.example.com").Return(nil, domain.ErrRouteNotFound) | 977 | configSvc.EXPECT().GetRoute(mock.Anything, "nonexistent.example.com").Return(nil, domain.ErrRouteNotFound) | ||
| 978 | 978 | ||||
| @@ -1016,7 +1016,7 @@ func TestHandler_RoutesPost_MissingFields(t *testing.T) { | |||||
| 1016 | secretSvc := inmocks.NewMockSecretService(t) | 1016 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1017 | registrySvc := inmocks.NewMockRegistryService(t) | 1017 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 1018 | 1018 | ||||
| 1019 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 1019 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 1020 | 1020 | ||||
| 1021 | configSvc.EXPECT().AddRoute(mock.Anything, mock.Anything).Return(tt.mockErr) | 1021 | configSvc.EXPECT().AddRoute(mock.Anything, mock.Anything).Return(tt.mockErr) | ||
| 1022 | 1022 | ||||
| @@ -1037,7 +1037,7 @@ func TestHandler_RoutesPut_MissingDomain(t *testing.T) { | |||||
| 1037 | containerSvc := inmocks.NewMockContainerService(t) | 1037 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1038 | secretSvc := inmocks.NewMockSecretService(t) | 1038 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1039 | 1039 | ||||
| 1040 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1040 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1041 | 1041 | ||||
| 1042 | req := httptest.NewRequest("PUT", "/admin/routes/", bytes.NewBufferString(`{"image": "app:latest"}`)) | 1042 | req := httptest.NewRequest("PUT", "/admin/routes/", bytes.NewBufferString(`{"image": "app:latest"}`)) | ||
| 1043 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | 1043 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | ||
| @@ -1054,7 +1054,7 @@ func TestHandler_RoutesDelete_MissingDomain(t *testing.T) { | |||||
| 1054 | containerSvc := inmocks.NewMockContainerService(t) | 1054 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1055 | secretSvc := inmocks.NewMockSecretService(t) | 1055 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1056 | 1056 | ||||
| 1057 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1057 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1058 | 1058 | ||||
| 1059 | req := httptest.NewRequest("DELETE", "/admin/routes/", nil) | 1059 | req := httptest.NewRequest("DELETE", "/admin/routes/", nil) | ||
| 1060 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | 1060 | req = req.WithContext(ctxWithScopes("admin:routes:write")) | ||
| @@ -1071,7 +1071,7 @@ func TestHandler_Secrets_MissingDomain(t *testing.T) { | |||||
| 1071 | containerSvc := inmocks.NewMockContainerService(t) | 1071 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1072 | secretSvc := inmocks.NewMockSecretService(t) | 1072 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1073 | 1073 | ||||
| 1074 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1074 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1075 | 1075 | ||||
| 1076 | req := httptest.NewRequest("GET", "/admin/secrets/", nil) | 1076 | req := httptest.NewRequest("GET", "/admin/secrets/", nil) | ||
| 1077 | req = req.WithContext(ctxWithScopes("admin:secrets:read")) | 1077 | req = req.WithContext(ctxWithScopes("admin:secrets:read")) | ||
| @@ -1088,7 +1088,7 @@ func TestHandler_SecretsDelete_MissingKey(t *testing.T) { | |||||
| 1088 | containerSvc := inmocks.NewMockContainerService(t) | 1088 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1089 | secretSvc := inmocks.NewMockSecretService(t) | 1089 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1090 | 1090 | ||||
| 1091 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1091 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1092 | 1092 | ||||
| 1093 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com", nil) | 1093 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com", nil) | ||
| 1094 | req = req.WithContext(ctxWithScopes("admin:secrets:write")) | 1094 | req = req.WithContext(ctxWithScopes("admin:secrets:write")) | ||
| @@ -1105,7 +1105,7 @@ func TestHandler_MethodNotAllowed(t *testing.T) { | |||||
| 1105 | containerSvc := inmocks.NewMockContainerService(t) | 1105 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1106 | secretSvc := inmocks.NewMockSecretService(t) | 1106 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1107 | 1107 | ||||
| 1108 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1108 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1109 | 1109 | ||||
| 1110 | tests := []struct { | 1110 | tests := []struct { | ||
| 1111 | method string | 1111 | method string | ||
| @@ -1138,7 +1138,7 @@ func TestHandler_Restart_InvalidDomain(t *testing.T) { | |||||
| 1138 | containerSvc := inmocks.NewMockContainerService(t) | 1138 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1139 | secretSvc := inmocks.NewMockSecretService(t) | 1139 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1140 | 1140 | ||||
| 1141 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1141 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1142 | 1142 | ||||
| 1143 | tests := []struct { | 1143 | tests := []struct { | ||
| 1144 | name string | 1144 | name string | ||
| @@ -1168,7 +1168,7 @@ func TestHandler_Restart_ContainerNotFound(t *testing.T) { | |||||
| 1168 | containerSvc := inmocks.NewMockContainerService(t) | 1168 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1169 | secretSvc := inmocks.NewMockSecretService(t) | 1169 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1170 | 1170 | ||||
| 1171 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1171 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1172 | 1172 | ||||
| 1173 | containerSvc.EXPECT().Restart(mock.Anything, "nonexistent.example.com", false).Return(domain.ErrContainerNotFound) | 1173 | containerSvc.EXPECT().Restart(mock.Anything, "nonexistent.example.com", false).Return(domain.ErrContainerNotFound) | ||
| 1174 | 1174 | ||||
| @@ -1187,7 +1187,7 @@ func TestHandler_Restart_GenericError(t *testing.T) { | |||||
| 1187 | containerSvc := inmocks.NewMockContainerService(t) | 1187 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1188 | secretSvc := inmocks.NewMockSecretService(t) | 1188 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1189 | 1189 | ||||
| 1190 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1190 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1191 | 1191 | ||||
| 1192 | containerSvc.EXPECT().Restart(mock.Anything, "test.example.com", false).Return(fmt.Errorf("docker daemon error: secret details")) | 1192 | containerSvc.EXPECT().Restart(mock.Anything, "test.example.com", false).Return(fmt.Errorf("docker daemon error: secret details")) | ||
| 1193 | 1193 | ||||
| @@ -1209,7 +1209,7 @@ func TestHandler_Restart_Success(t *testing.T) { | |||||
| 1209 | containerSvc := inmocks.NewMockContainerService(t) | 1209 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1210 | secretSvc := inmocks.NewMockSecretService(t) | 1210 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1211 | 1211 | ||||
| 1212 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1212 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1213 | 1213 | ||||
| 1214 | tests := []struct { | 1214 | tests := []struct { | ||
| 1215 | name string | 1215 | name string | ||
| @@ -1256,7 +1256,7 @@ func TestHandler_Tags_InvalidRepository(t *testing.T) { | |||||
| 1256 | containerSvc := inmocks.NewMockContainerService(t) | 1256 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1257 | secretSvc := inmocks.NewMockSecretService(t) | 1257 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1258 | 1258 | ||||
| 1259 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1259 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1260 | 1260 | ||||
| 1261 | tests := []struct { | 1261 | tests := []struct { | ||
| 1262 | name string | 1262 | name string | ||
| @@ -1287,7 +1287,7 @@ func TestHandler_Tags_Success(t *testing.T) { | |||||
| 1287 | secretSvc := inmocks.NewMockSecretService(t) | 1287 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1288 | registrySvc := inmocks.NewMockRegistryService(t) | 1288 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 1289 | 1289 | ||||
| 1290 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 1290 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 1291 | 1291 | ||||
| 1292 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return([]string{"latest", "v1.0"}, nil) | 1292 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return([]string{"latest", "v1.0"}, nil) | ||
| 1293 | 1293 | ||||
| @@ -1314,7 +1314,7 @@ func TestHandler_Tags_Error(t *testing.T) { | |||||
| 1314 | secretSvc := inmocks.NewMockSecretService(t) | 1314 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1315 | registrySvc := inmocks.NewMockRegistryService(t) | 1315 | registrySvc := inmocks.NewMockRegistryService(t) | ||
| 1316 | 1316 | ||||
| 1317 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil) | 1317 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, registrySvc, nil, testLogger(), nil, nil) |
| 1318 | 1318 | ||||
| 1319 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return(nil, fmt.Errorf("registry error")) | 1319 | registrySvc.EXPECT().ListTags(mock.Anything, "myapp").Return(nil, fmt.Errorf("registry error")) | ||
| 1320 | 1320 | ||||
| @@ -1334,7 +1334,7 @@ func TestHandler_AttachmentSecretsPost_RequiresWriteScope(t *testing.T) { | |||||
| 1334 | containerSvc := inmocks.NewMockContainerService(t) | 1334 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1335 | secretSvc := inmocks.NewMockSecretService(t) | 1335 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1336 | 1336 | ||||
| 1337 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1337 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1338 | 1338 | ||||
| 1339 | body := `{"REDIS_URL": "redis://localhost:6379"}` | 1339 | body := `{"REDIS_URL": "redis://localhost:6379"}` | ||
| 1340 | req := httptest.NewRequest("POST", "/admin/secrets/app.example.com/attachments/redis", bytes.NewBufferString(body)) | 1340 | req := httptest.NewRequest("POST", "/admin/secrets/app.example.com/attachments/redis", bytes.NewBufferString(body)) | ||
| @@ -1352,7 +1352,7 @@ func TestHandler_AttachmentSecretsDelete_RequiresWriteScope(t *testing.T) { | |||||
| 1352 | containerSvc := inmocks.NewMockContainerService(t) | 1352 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1353 | secretSvc := inmocks.NewMockSecretService(t) | 1353 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1354 | 1354 | ||||
| 1355 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1355 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1356 | 1356 | ||||
| 1357 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com/attachments/redis/REDIS_URL", nil) | 1357 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com/attachments/redis/REDIS_URL", nil) | ||
| 1358 | req = req.WithContext(ctxWithScopes("admin:secrets:read")) | 1358 | req = req.WithContext(ctxWithScopes("admin:secrets:read")) | ||
| @@ -1369,7 +1369,7 @@ func TestHandler_AttachmentSecretsDelete_RequiresKey(t *testing.T) { | |||||
| 1369 | containerSvc := inmocks.NewMockContainerService(t) | 1369 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1370 | secretSvc := inmocks.NewMockSecretService(t) | 1370 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1371 | 1371 | ||||
| 1372 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1372 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1373 | 1373 | ||||
| 1374 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com/attachments/redis", nil) | 1374 | req := httptest.NewRequest("DELETE", "/admin/secrets/app.example.com/attachments/redis", nil) | ||
| 1375 | req = req.WithContext(ctxWithScopes("admin:secrets:write")) | 1375 | req = req.WithContext(ctxWithScopes("admin:secrets:write")) | ||
| @@ -1387,7 +1387,7 @@ func TestHandler_AttachmentSecretsPost_InvalidJSON(t *testing.T) { | |||||
| 1387 | containerSvc := inmocks.NewMockContainerService(t) | 1387 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1388 | secretSvc := inmocks.NewMockSecretService(t) | 1388 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1389 | 1389 | ||||
| 1390 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1390 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1391 | 1391 | ||||
| 1392 | body := `{not valid json` | 1392 | body := `{not valid json` | ||
| 1393 | req := httptest.NewRequest("POST", "/admin/secrets/app.example.com/attachments/redis", bytes.NewBufferString(body)) | 1393 | req := httptest.NewRequest("POST", "/admin/secrets/app.example.com/attachments/redis", bytes.NewBufferString(body)) | ||
| @@ -1406,7 +1406,7 @@ func TestHandler_NotFound(t *testing.T) { | |||||
| 1406 | containerSvc := inmocks.NewMockContainerService(t) | 1406 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1407 | secretSvc := inmocks.NewMockSecretService(t) | 1407 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1408 | 1408 | ||||
| 1409 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1409 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1410 | 1410 | ||||
| 1411 | req := httptest.NewRequest("GET", "/admin/unknown", nil) | 1411 | req := httptest.NewRequest("GET", "/admin/unknown", nil) | ||
| 1412 | req = req.WithContext(ctxWithScopes("admin:*:*")) | 1412 | req = req.WithContext(ctxWithScopes("admin:*:*")) | ||
| @@ -1426,7 +1426,7 @@ func TestHandler_BackupsStatus(t *testing.T) { | |||||
| 1426 | 1426 | ||||
| 1427 | backupSvc.EXPECT().Status(mock.Anything).Return([]domain.BackupJob{{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted, FilePath: "/var/lib/gordon/backups/private.bak"}}, nil) | 1427 | backupSvc.EXPECT().Status(mock.Anything).Return([]domain.BackupJob{{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted, FilePath: "/var/lib/gordon/backups/private.bak"}}, nil) | ||
| 1428 | 1428 | ||||
| 1429 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | 1429 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) |
| 1430 | 1430 | ||||
| 1431 | req := httptest.NewRequest("GET", "/admin/backups/status", nil) | 1431 | req := httptest.NewRequest("GET", "/admin/backups/status", nil) | ||
| 1432 | req = req.WithContext(ctxWithScopes("admin:status:read")) | 1432 | req = req.WithContext(ctxWithScopes("admin:status:read")) | ||
| @@ -1448,7 +1448,7 @@ func TestHandler_BackupsListDomain(t *testing.T) { | |||||
| 1448 | 1448 | ||||
| 1449 | backupSvc.EXPECT().ListBackups(mock.Anything, "app.example.com").Return([]domain.BackupJob{{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | 1449 | backupSvc.EXPECT().ListBackups(mock.Anything, "app.example.com").Return([]domain.BackupJob{{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | ||
| 1450 | 1450 | ||||
| 1451 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | 1451 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) |
| 1452 | 1452 | ||||
| 1453 | req := httptest.NewRequest("GET", "/admin/backups/app.example.com", nil) | 1453 | req := httptest.NewRequest("GET", "/admin/backups/app.example.com", nil) | ||
| 1454 | req = req.WithContext(ctxWithScopes("admin:status:read")) | 1454 | req = req.WithContext(ctxWithScopes("admin:status:read")) | ||
| @@ -1469,7 +1469,7 @@ func TestHandler_BackupsRunDomain(t *testing.T) { | |||||
| 1469 | 1469 | ||||
| 1470 | backupSvc.EXPECT().RunBackup(mock.Anything, "app.example.com", "postgres").Return(&domain.BackupResult{Job: domain.BackupJob{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | 1470 | backupSvc.EXPECT().RunBackup(mock.Anything, "app.example.com", "postgres").Return(&domain.BackupResult{Job: domain.BackupJob{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | ||
| 1471 | 1471 | ||||
| 1472 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | 1472 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) |
| 1473 | 1473 | ||||
| 1474 | body := bytes.NewBufferString(`{"db":"postgres"}`) | 1474 | body := bytes.NewBufferString(`{"db":"postgres"}`) | ||
| 1475 | req := httptest.NewRequest("POST", "/admin/backups/app.example.com", body) | 1475 | req := httptest.NewRequest("POST", "/admin/backups/app.example.com", body) | ||
| @@ -1491,7 +1491,7 @@ func TestHandler_BackupsRunDomain_ChunkedBodyIsDecoded(t *testing.T) { | |||||
| 1491 | 1491 | ||||
| 1492 | backupSvc.EXPECT().RunBackup(mock.Anything, "app.example.com", "postgres").Return(&domain.BackupResult{Job: domain.BackupJob{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | 1492 | backupSvc.EXPECT().RunBackup(mock.Anything, "app.example.com", "postgres").Return(&domain.BackupResult{Job: domain.BackupJob{Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted}}, nil) | ||
| 1493 | 1493 | ||||
| 1494 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | 1494 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) |
| 1495 | 1495 | ||||
| 1496 | body := bytes.NewBufferString(`{"db":"postgres"}`) | 1496 | body := bytes.NewBufferString(`{"db":"postgres"}`) | ||
| 1497 | req := httptest.NewRequest("POST", "/admin/backups/app.example.com", body) | 1497 | req := httptest.NewRequest("POST", "/admin/backups/app.example.com", body) | ||
| @@ -1514,7 +1514,7 @@ func TestHandler_BackupsDetectDomain(t *testing.T) { | |||||
| 1514 | 1514 | ||||
| 1515 | backupSvc.EXPECT().DetectDatabases(mock.Anything, "app.example.com").Return([]domain.DBInfo{{Type: domain.DBTypePostgreSQL, Name: "postgres", Port: 5432}}, nil) | 1515 | backupSvc.EXPECT().DetectDatabases(mock.Anything, "app.example.com").Return([]domain.DBInfo{{Type: domain.DBTypePostgreSQL, Name: "postgres", Port: 5432}}, nil) | ||
| 1516 | 1516 | ||||
| 1517 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc) | 1517 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), backupSvc, nil) |
| 1518 | 1518 | ||||
| 1519 | req := httptest.NewRequest("GET", "/admin/backups/app.example.com/detect", nil) | 1519 | req := httptest.NewRequest("GET", "/admin/backups/app.example.com/detect", nil) | ||
| 1520 | req = req.WithContext(ctxWithScopes("admin:status:read")) | 1520 | req = req.WithContext(ctxWithScopes("admin:status:read")) | ||
| @@ -1556,7 +1556,7 @@ func TestHandler_ImagesGet_ReturnsMappedList(t *testing.T) { | |||||
| 1556 | }, | 1556 | }, | ||
| 1557 | } | 1557 | } | ||
| 1558 | 1558 | ||||
| 1559 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | 1559 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) |
| 1560 | 1560 | ||||
| 1561 | req := httptest.NewRequest("GET", "/admin/images", nil) | 1561 | req := httptest.NewRequest("GET", "/admin/images", nil) | ||
| 1562 | req = req.WithContext(ctxWithScopes("admin:status:read")) | 1562 | req = req.WithContext(ctxWithScopes("admin:status:read")) | ||
| @@ -1617,7 +1617,7 @@ func TestHandler_ImagesPrune_AcceptsOptionalKeepLast(t *testing.T) { | |||||
| 1617 | }, | 1617 | }, | ||
| 1618 | } | 1618 | } | ||
| 1619 | 1619 | ||||
| 1620 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | 1620 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) |
| 1621 | 1621 | ||||
| 1622 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(tt.body)) | 1622 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(tt.body)) | ||
| 1623 | req = req.WithContext(ctxWithScopes("admin:config:write")) | 1623 | req = req.WithContext(ctxWithScopes("admin:config:write")) | ||
| @@ -1646,7 +1646,7 @@ func TestHandler_Images_ErrorMappingForServiceFailures(t *testing.T) { | |||||
| 1646 | }, | 1646 | }, | ||
| 1647 | } | 1647 | } | ||
| 1648 | 1648 | ||||
| 1649 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | 1649 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) |
| 1650 | 1650 | ||||
| 1651 | req := httptest.NewRequest("GET", "/admin/images", nil) | 1651 | req := httptest.NewRequest("GET", "/admin/images", nil) | ||
| 1652 | req = req.WithContext(ctxWithScopes("admin:status:read")) | 1652 | req = req.WithContext(ctxWithScopes("admin:status:read")) | ||
| @@ -1671,7 +1671,7 @@ func TestHandler_Images_ErrorMappingForServiceFailures(t *testing.T) { | |||||
| 1671 | }, | 1671 | }, | ||
| 1672 | } | 1672 | } | ||
| 1673 | 1673 | ||||
| 1674 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | 1674 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) |
| 1675 | 1675 | ||||
| 1676 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(`{"keep_last": 2}`)) | 1676 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(`{"keep_last": 2}`)) | ||
| 1677 | req = req.WithContext(ctxWithScopes("admin:config:write")) | 1677 | req = req.WithContext(ctxWithScopes("admin:config:write")) | ||
| @@ -1692,7 +1692,7 @@ func TestHandler_ImagesPrune_ValidationAndAvailability(t *testing.T) { | |||||
| 1692 | containerSvc := inmocks.NewMockContainerService(t) | 1692 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1693 | secretSvc := inmocks.NewMockSecretService(t) | 1693 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1694 | 1694 | ||||
| 1695 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, &stubImageService{}) | 1695 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, &stubImageService{}) |
| 1696 | 1696 | ||||
| 1697 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(`{"keep_last": -1}`)) | 1697 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(`{"keep_last": -1}`)) | ||
| 1698 | req = req.WithContext(ctxWithScopes("admin:config:write")) | 1698 | req = req.WithContext(ctxWithScopes("admin:config:write")) | ||
| @@ -1710,7 +1710,7 @@ func TestHandler_ImagesPrune_ValidationAndAvailability(t *testing.T) { | |||||
| 1710 | containerSvc := inmocks.NewMockContainerService(t) | 1710 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1711 | secretSvc := inmocks.NewMockSecretService(t) | 1711 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1712 | 1712 | ||||
| 1713 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil) | 1713 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil) |
| 1714 | 1714 | ||||
| 1715 | req := httptest.NewRequest("GET", "/admin/images", nil) | 1715 | req := httptest.NewRequest("GET", "/admin/images", nil) | ||
| 1716 | req = req.WithContext(ctxWithScopes("admin:status:read")) | 1716 | req = req.WithContext(ctxWithScopes("admin:status:read")) | ||
| @@ -1781,7 +1781,7 @@ func TestHandler_ImagesPrune_ScopeDefaults(t *testing.T) { | |||||
| 1781 | }, | 1781 | }, | ||
| 1782 | } | 1782 | } | ||
| 1783 | 1783 | ||||
| 1784 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, imageSvc) | 1784 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, imageSvc) |
| 1785 | 1785 | ||||
| 1786 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(tt.body)) | 1786 | req := httptest.NewRequest("POST", "/admin/images/prune", bytes.NewBufferString(tt.body)) | ||
| 1787 | req = req.WithContext(ctxWithScopes("admin:config:write")) | 1787 | req = req.WithContext(ctxWithScopes("admin:config:write")) | ||
| @@ -1806,7 +1806,7 @@ func TestHandler_Images_Authorization(t *testing.T) { | |||||
| 1806 | containerSvc := inmocks.NewMockContainerService(t) | 1806 | containerSvc := inmocks.NewMockContainerService(t) | ||
| 1807 | secretSvc := inmocks.NewMockSecretService(t) | 1807 | secretSvc := inmocks.NewMockSecretService(t) | ||
| 1808 | 1808 | ||||
| 1809 | - | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, &stubImageService{}) | 1809 | + | handler := NewHandler(configSvc, authSvc, containerSvc, inmocks.NewMockHealthService(t), secretSvc, nil, inmocks.NewMockRegistryService(t), nil, testLogger(), nil, nil, &stubImageService{}) |
| 1810 | 1810 | ||||
| 1811 | t.Run("images list requires status:read", func(t *testing.T) { | 1811 | t.Run("images list requires status:read", func(t *testing.T) { | ||
| 1812 | req := httptest.NewRequest("GET", "/admin/images", nil) | 1812 | req := httptest.NewRequest("GET", "/admin/images", nil) | ||
A internal/adapters/in/http/admin/preview_handler.go +140 -0
| @@ -0,0 +1,140 @@ | |||
| 1 | + | package admin | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "encoding/json" | |
| 6 | + | "net/http" | |
| 7 | + | "strings" | |
| 8 | + | "time" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/zerowrap" | |
| 11 | + | ||
| 12 | + | "github.com/bnema/gordon/internal/domain" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // previewService is a minimal interface for preview management used by the admin handler. | |
| 16 | + | type previewService interface { | |
| 17 | + | List(ctx context.Context) ([]domain.PreviewRoute, error) | |
| 18 | + | Delete(ctx context.Context, name string) error | |
| 19 | + | Extend(ctx context.Context, name string, ttl time.Duration) error | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | // previewListResponse is the JSON payload returned by GET /admin/previews. | |
| 23 | + | type previewListResponse struct { | |
| 24 | + | Previews []domain.PreviewRoute `json:"previews"` | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | // previewExtendRequest is the JSON body for PATCH /admin/preview/{name}. | |
| 28 | + | type previewExtendRequest struct { | |
| 29 | + | TTL string `json:"ttl"` // e.g. "24h", "30m" | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | // handlePreviewList handles GET /admin/previews. | |
| 33 | + | // Returns a JSON array of all active preview environments. | |
| 34 | + | func (h *Handler) handlePreviewList(w http.ResponseWriter, r *http.Request, _ string) { | |
| 35 | + | if r.Method != http.MethodGet { | |
| 36 | + | h.sendError(w, http.StatusMethodNotAllowed, "method not allowed") | |
| 37 | + | return | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | ctx := r.Context() | |
| 41 | + | ||
| 42 | + | if !HasAccess(ctx, domain.AdminResourceStatus, domain.AdminActionRead) { | |
| 43 | + | h.sendError(w, http.StatusForbidden, "insufficient permissions for status:read") | |
| 44 | + | return | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | if h.previewSvc == nil { | |
| 48 | + | h.sendError(w, http.StatusServiceUnavailable, "preview service not available") | |
| 49 | + | return | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | previews, err := h.previewSvc.List(ctx) | |
| 53 | + | if err != nil { | |
| 54 | + | log := zerowrap.FromCtx(ctx) | |
| 55 | + | log.Error().Err(err).Msg("failed to list previews") | |
| 56 | + | h.sendError(w, http.StatusInternalServerError, "failed to list previews") | |
| 57 | + | return | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | if previews == nil { | |
| 61 | + | previews = []domain.PreviewRoute{} | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | h.sendJSON(w, http.StatusOK, previewListResponse{Previews: previews}) | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // handlePreviewAction handles DELETE and PATCH on /admin/preview/{name}. | |
| 68 | + | // | |
| 69 | + | // DELETE /admin/preview/{name} — tear down the preview | |
| 70 | + | // PATCH /admin/preview/{name} — extend TTL (body: {"ttl":"24h"}) | |
| 71 | + | func (h *Handler) handlePreviewAction(w http.ResponseWriter, r *http.Request, path string) { | |
| 72 | + | ctx := r.Context() | |
| 73 | + | log := zerowrap.FromCtx(ctx) | |
| 74 | + | ||
| 75 | + | if h.previewSvc == nil { | |
| 76 | + | h.sendError(w, http.StatusServiceUnavailable, "preview service not available") | |
| 77 | + | return | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | name := strings.TrimPrefix(path, "/preview/") | |
| 81 | + | if name == "" || name == "/preview" { | |
| 82 | + | h.sendError(w, http.StatusBadRequest, "preview name required in path") | |
| 83 | + | return | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | switch r.Method { | |
| 87 | + | case http.MethodDelete: | |
| 88 | + | if !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) { | |
| 89 | + | h.sendError(w, http.StatusForbidden, "insufficient permissions for config:write") | |
| 90 | + | return | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | if err := h.previewSvc.Delete(ctx, name); err != nil { | |
| 94 | + | log.Error().Err(err).Str("name", name).Msg("failed to delete preview") | |
| 95 | + | h.sendError(w, http.StatusNotFound, "preview not found") | |
| 96 | + | return | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | log.Info().Str("name", name).Msg("preview deleted via admin API") | |
| 100 | + | h.sendJSON(w, http.StatusOK, map[string]string{"status": "deleted", "name": name}) | |
| 101 | + | ||
| 102 | + | case http.MethodPatch: | |
| 103 | + | if !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) { | |
| 104 | + | h.sendError(w, http.StatusForbidden, "insufficient permissions for config:write") | |
| 105 | + | return | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | r.Body = http.MaxBytesReader(w, r.Body, maxAdminRequestSize) | |
| 109 | + | ||
| 110 | + | var req previewExtendRequest | |
| 111 | + | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | |
| 112 | + | log.Warn().Err(err).Msg("invalid preview extend JSON") | |
| 113 | + | h.sendError(w, http.StatusBadRequest, "invalid JSON") | |
| 114 | + | return | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | if req.TTL == "" { | |
| 118 | + | h.sendError(w, http.StatusBadRequest, "ttl is required") | |
| 119 | + | return | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | ttl, err := time.ParseDuration(req.TTL) | |
| 123 | + | if err != nil { | |
| 124 | + | h.sendError(w, http.StatusBadRequest, "invalid ttl: use Go duration format, e.g. 24h or 30m") | |
| 125 | + | return | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | if err := h.previewSvc.Extend(ctx, name, ttl); err != nil { | |
| 129 | + | log.Error().Err(err).Str("name", name).Dur("ttl", ttl).Msg("failed to extend preview TTL") | |
| 130 | + | h.sendError(w, http.StatusNotFound, "preview not found") | |
| 131 | + | return | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | log.Info().Str("name", name).Dur("ttl", ttl).Msg("preview TTL extended via admin API") | |
| 135 | + | h.sendJSON(w, http.StatusOK, map[string]string{"status": "extended", "name": name, "ttl": req.TTL}) | |
| 136 | + | ||
| 137 | + | default: | |
| 138 | + | h.sendError(w, http.StatusMethodNotAllowed, "method not allowed") | |
| 139 | + | } | |
| 140 | + | } | |
| @@ -0,0 +1,140 @@ | |||||
| 1 | + | package admin | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "encoding/json" | |||
| 6 | + | "net/http" | |||
| 7 | + | "strings" | |||
| 8 | + | "time" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/zerowrap" | |||
| 11 | + | ||||
| 12 | + | "github.com/bnema/gordon/internal/domain" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | // previewService is a minimal interface for preview management used by the admin handler. | |||
| 16 | + | type previewService interface { | |||
| 17 | + | List(ctx context.Context) ([]domain.PreviewRoute, error) | |||
| 18 | + | Delete(ctx context.Context, name string) error | |||
| 19 | + | Extend(ctx context.Context, name string, ttl time.Duration) error | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | // previewListResponse is the JSON payload returned by GET /admin/previews. | |||
| 23 | + | type previewListResponse struct { | |||
| 24 | + | Previews []domain.PreviewRoute `json:"previews"` | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | // previewExtendRequest is the JSON body for PATCH /admin/preview/{name}. | |||
| 28 | + | type previewExtendRequest struct { | |||
| 29 | + | TTL string `json:"ttl"` // e.g. "24h", "30m" | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | // handlePreviewList handles GET /admin/previews. | |||
| 33 | + | // Returns a JSON array of all active preview environments. | |||
| 34 | + | func (h *Handler) handlePreviewList(w http.ResponseWriter, r *http.Request, _ string) { | |||
| 35 | + | if r.Method != http.MethodGet { | |||
| 36 | + | h.sendError(w, http.StatusMethodNotAllowed, "method not allowed") | |||
| 37 | + | return | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | ctx := r.Context() | |||
| 41 | + | ||||
| 42 | + | if !HasAccess(ctx, domain.AdminResourceStatus, domain.AdminActionRead) { | |||
| 43 | + | h.sendError(w, http.StatusForbidden, "insufficient permissions for status:read") | |||
| 44 | + | return | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | if h.previewSvc == nil { | |||
| 48 | + | h.sendError(w, http.StatusServiceUnavailable, "preview service not available") | |||
| 49 | + | return | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | previews, err := h.previewSvc.List(ctx) | |||
| 53 | + | if err != nil { | |||
| 54 | + | log := zerowrap.FromCtx(ctx) | |||
| 55 | + | log.Error().Err(err).Msg("failed to list previews") | |||
| 56 | + | h.sendError(w, http.StatusInternalServerError, "failed to list previews") | |||
| 57 | + | return | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | if previews == nil { | |||
| 61 | + | previews = []domain.PreviewRoute{} | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | h.sendJSON(w, http.StatusOK, previewListResponse{Previews: previews}) | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | // handlePreviewAction handles DELETE and PATCH on /admin/preview/{name}. | |||
| 68 | + | // | |||
| 69 | + | // DELETE /admin/preview/{name} — tear down the preview | |||
| 70 | + | // PATCH /admin/preview/{name} — extend TTL (body: {"ttl":"24h"}) | |||
| 71 | + | func (h *Handler) handlePreviewAction(w http.ResponseWriter, r *http.Request, path string) { | |||
| 72 | + | ctx := r.Context() | |||
| 73 | + | log := zerowrap.FromCtx(ctx) | |||
| 74 | + | ||||
| 75 | + | if h.previewSvc == nil { | |||
| 76 | + | h.sendError(w, http.StatusServiceUnavailable, "preview service not available") | |||
| 77 | + | return | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | name := strings.TrimPrefix(path, "/preview/") | |||
| 81 | + | if name == "" || name == "/preview" { | |||
| 82 | + | h.sendError(w, http.StatusBadRequest, "preview name required in path") | |||
| 83 | + | return | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | switch r.Method { | |||
| 87 | + | case http.MethodDelete: | |||
| 88 | + | if !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) { | |||
| 89 | + | h.sendError(w, http.StatusForbidden, "insufficient permissions for config:write") | |||
| 90 | + | return | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | if err := h.previewSvc.Delete(ctx, name); err != nil { | |||
| 94 | + | log.Error().Err(err).Str("name", name).Msg("failed to delete preview") | |||
| 95 | + | h.sendError(w, http.StatusNotFound, "preview not found") | |||
| 96 | + | return | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | log.Info().Str("name", name).Msg("preview deleted via admin API") | |||
| 100 | + | h.sendJSON(w, http.StatusOK, map[string]string{"status": "deleted", "name": name}) | |||
| 101 | + | ||||
| 102 | + | case http.MethodPatch: | |||
| 103 | + | if !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) { | |||
| 104 | + | h.sendError(w, http.StatusForbidden, "insufficient permissions for config:write") | |||
| 105 | + | return | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | r.Body = http.MaxBytesReader(w, r.Body, maxAdminRequestSize) | |||
| 109 | + | ||||
| 110 | + | var req previewExtendRequest | |||
| 111 | + | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | |||
| 112 | + | log.Warn().Err(err).Msg("invalid preview extend JSON") | |||
| 113 | + | h.sendError(w, http.StatusBadRequest, "invalid JSON") | |||
| 114 | + | return | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | if req.TTL == "" { | |||
| 118 | + | h.sendError(w, http.StatusBadRequest, "ttl is required") | |||
| 119 | + | return | |||
| 120 | + | } | |||
| 121 | + | ||||
| 122 | + | ttl, err := time.ParseDuration(req.TTL) | |||
| 123 | + | if err != nil { | |||
| 124 | + | h.sendError(w, http.StatusBadRequest, "invalid ttl: use Go duration format, e.g. 24h or 30m") | |||
| 125 | + | return | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | if err := h.previewSvc.Extend(ctx, name, ttl); err != nil { | |||
| 129 | + | log.Error().Err(err).Str("name", name).Dur("ttl", ttl).Msg("failed to extend preview TTL") | |||
| 130 | + | h.sendError(w, http.StatusNotFound, "preview not found") | |||
| 131 | + | return | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | log.Info().Str("name", name).Dur("ttl", ttl).Msg("preview TTL extended via admin API") | |||
| 135 | + | h.sendJSON(w, http.StatusOK, map[string]string{"status": "extended", "name": name, "ttl": req.TTL}) | |||
| 136 | + | ||||
| 137 | + | default: | |||
| 138 | + | h.sendError(w, http.StatusMethodNotAllowed, "method not allowed") | |||
| 139 | + | } | |||
| 140 | + | } | |||
M internal/app/run.go +1 -0
| @@ -553,6 +553,7 @@ func createServices(ctx context.Context, v *viper.Viper, cfg Config, log zerowra | |||
| 553 | 553 | svc.eventBus, | |
| 554 | 554 | log, | |
| 555 | 555 | svc.backupSvc, | |
| 556 | + | nil, // previewSvc: wired in Task 13 | |
| 556 | 557 | svc.imageSvc, | |
| 557 | 558 | ).WithVolumeService(svc.volumeSvc) | |
| 558 | 559 | ||
| @@ -553,6 +553,7 @@ func createServices(ctx context.Context, v *viper.Viper, cfg Config, log zerowra | |||||
| 553 | svc.eventBus, | 553 | svc.eventBus, | ||
| 554 | log, | 554 | log, | ||
| 555 | svc.backupSvc, | 555 | svc.backupSvc, | ||
| 556 | + | nil, // previewSvc: wired in Task 13 | |||
| 556 | svc.imageSvc, | 557 | svc.imageSvc, | ||
| 557 | ).WithVolumeService(svc.volumeSvc) | 558 | ).WithVolumeService(svc.volumeSvc) | ||
| 558 | 559 | ||||