Merge pull request #201 from bnema/chore/cli-mocks-diagnostics
test(cli): replace hand-written fakes with mocks
12 files · +545 -908
M .mockery.yaml +2 -0
| @@ -53,3 +53,5 @@ packages: | |||
| 53 | 53 | interfaces: | |
| 54 | 54 | pushImageOps: | |
| 55 | 55 | ControlPlane: | |
| 56 | + | imagesClient: | |
| 57 | + | volumesClient: | |
| @@ -53,3 +53,5 @@ packages: | |||||
| 53 | interfaces: | 53 | interfaces: | ||
| 54 | pushImageOps: | 54 | pushImageOps: | ||
| 55 | ControlPlane: | 55 | ControlPlane: | ||
| 56 | + | imagesClient: | |||
| 57 | + | volumesClient: | |||
M internal/adapters/in/cli/attachments_push_test.go +19 -225
| @@ -5,207 +5,19 @@ import ( | |||
| 5 | 5 | "context" | |
| 6 | 6 | "testing" | |
| 7 | 7 | ||
| 8 | - | "github.com/bnema/gordon/internal/adapters/dto" | |
| 9 | 8 | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 10 | 9 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | |
| 11 | - | "github.com/bnema/gordon/internal/domain" | |
| 12 | 10 | "github.com/stretchr/testify/assert" | |
| 13 | 11 | "github.com/stretchr/testify/mock" | |
| 14 | 12 | "github.com/stretchr/testify/require" | |
| 15 | 13 | ) | |
| 16 | 14 | ||
| 17 | - | type attachmentPushTestControlPlane struct { | |
| 18 | - | findAttachmentTargets func(context.Context, string) ([]string, error) | |
| 19 | - | getStatus func(context.Context) (*remote.Status, error) | |
| 20 | - | } | |
| 21 | - | ||
| 22 | - | var _ ControlPlane = (*attachmentPushTestControlPlane)(nil) | |
| 23 | - | ||
| 24 | - | func (c *attachmentPushTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |
| 25 | - | panic("unexpected call") | |
| 26 | - | } | |
| 27 | - | ||
| 28 | - | func (c *attachmentPushTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 29 | - | panic("unexpected call") | |
| 30 | - | } | |
| 31 | - | ||
| 32 | - | func (c *attachmentPushTestControlPlane) GetRoute(context.Context, string) (*domain.Route, error) { | |
| 33 | - | panic("unexpected call") | |
| 34 | - | } | |
| 35 | - | ||
| 36 | - | func (c *attachmentPushTestControlPlane) FindRoutesByImage(context.Context, string) ([]domain.Route, error) { | |
| 37 | - | panic("unexpected call") | |
| 38 | - | } | |
| 39 | - | ||
| 40 | - | func (c *attachmentPushTestControlPlane) AddRoute(context.Context, domain.Route) error { | |
| 41 | - | panic("unexpected call") | |
| 42 | - | } | |
| 43 | - | ||
| 44 | - | func (c *attachmentPushTestControlPlane) UpdateRoute(context.Context, domain.Route) error { | |
| 45 | - | panic("unexpected call") | |
| 46 | - | } | |
| 47 | - | ||
| 48 | - | func (c *attachmentPushTestControlPlane) RemoveRoute(context.Context, string) error { | |
| 49 | - | panic("unexpected call") | |
| 50 | - | } | |
| 51 | - | ||
| 52 | - | func (c *attachmentPushTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) { | |
| 53 | - | panic("unexpected call") | |
| 54 | - | } | |
| 55 | - | ||
| 56 | - | func (c *attachmentPushTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) { | |
| 57 | - | panic("unexpected call") | |
| 58 | - | } | |
| 59 | - | ||
| 60 | - | func (c *attachmentPushTestControlPlane) SetSecrets(context.Context, string, map[string]string) error { | |
| 61 | - | panic("unexpected call") | |
| 62 | - | } | |
| 63 | - | ||
| 64 | - | func (c *attachmentPushTestControlPlane) DeleteSecret(context.Context, string, string) error { | |
| 65 | - | panic("unexpected call") | |
| 66 | - | } | |
| 67 | - | ||
| 68 | - | func (c *attachmentPushTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error { | |
| 69 | - | panic("unexpected call") | |
| 70 | - | } | |
| 71 | - | ||
| 72 | - | func (c *attachmentPushTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error { | |
| 73 | - | panic("unexpected call") | |
| 74 | - | } | |
| 75 | - | ||
| 76 | - | func (c *attachmentPushTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) { | |
| 77 | - | panic("unexpected call") | |
| 78 | - | } | |
| 79 | - | ||
| 80 | - | func (c *attachmentPushTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) { | |
| 81 | - | panic("unexpected call") | |
| 82 | - | } | |
| 83 | - | ||
| 84 | - | func (c *attachmentPushTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |
| 85 | - | panic("unexpected call") | |
| 86 | - | } | |
| 87 | - | ||
| 88 | - | func (c *attachmentPushTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |
| 89 | - | panic("unexpected call") | |
| 90 | - | } | |
| 91 | - | ||
| 92 | - | func (c *attachmentPushTestControlPlane) FindAttachmentTargetsByImage(ctx context.Context, imageName string) ([]string, error) { | |
| 93 | - | if c.findAttachmentTargets != nil { | |
| 94 | - | return c.findAttachmentTargets(ctx, imageName) | |
| 95 | - | } | |
| 96 | - | return nil, nil | |
| 97 | - | } | |
| 98 | - | ||
| 99 | - | func (c *attachmentPushTestControlPlane) AddAttachment(context.Context, string, string) error { | |
| 100 | - | panic("unexpected call") | |
| 101 | - | } | |
| 102 | - | ||
| 103 | - | func (c *attachmentPushTestControlPlane) RemoveAttachment(context.Context, string, string) error { | |
| 104 | - | panic("unexpected call") | |
| 105 | - | } | |
| 106 | - | ||
| 107 | - | func (c *attachmentPushTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) { | |
| 108 | - | panic("unexpected call") | |
| 109 | - | } | |
| 110 | - | ||
| 111 | - | func (c *attachmentPushTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error { | |
| 112 | - | panic("unexpected call") | |
| 113 | - | } | |
| 114 | - | ||
| 115 | - | func (c *attachmentPushTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error { | |
| 116 | - | panic("unexpected call") | |
| 117 | - | } | |
| 118 | - | ||
| 119 | - | func (c *attachmentPushTestControlPlane) GetTLSStatus(context.Context) (*dto.TLSStatusResponse, error) { | |
| 120 | - | return &dto.TLSStatusResponse{}, nil | |
| 121 | - | } | |
| 122 | - | ||
| 123 | - | func (c *attachmentPushTestControlPlane) GetStatus(ctx context.Context) (*remote.Status, error) { | |
| 124 | - | if c.getStatus != nil { | |
| 125 | - | return c.getStatus(ctx) | |
| 126 | - | } | |
| 127 | - | panic("unexpected call") | |
| 128 | - | } | |
| 129 | - | ||
| 130 | - | func (c *attachmentPushTestControlPlane) Reload(context.Context) error { | |
| 131 | - | panic("unexpected call") | |
| 132 | - | } | |
| 133 | - | ||
| 134 | - | func (c *attachmentPushTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) { | |
| 135 | - | panic("unexpected call") | |
| 136 | - | } | |
| 137 | - | ||
| 138 | - | func (c *attachmentPushTestControlPlane) GetConfig(context.Context) (*remote.Config, error) { | |
| 139 | - | panic("unexpected call") | |
| 140 | - | } | |
| 141 | - | ||
| 142 | - | func (c *attachmentPushTestControlPlane) DeployIntent(context.Context, string) error { | |
| 143 | - | panic("unexpected call") | |
| 144 | - | } | |
| 145 | - | ||
| 146 | - | func (c *attachmentPushTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) { | |
| 147 | - | panic("unexpected call") | |
| 148 | - | } | |
| 149 | - | ||
| 150 | - | func (c *attachmentPushTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) { | |
| 151 | - | panic("unexpected call") | |
| 152 | - | } | |
| 153 | - | ||
| 154 | - | func (c *attachmentPushTestControlPlane) ListTags(context.Context, string) ([]string, error) { | |
| 155 | - | panic("unexpected call") | |
| 156 | - | } | |
| 157 | - | ||
| 158 | - | func (c *attachmentPushTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) { | |
| 159 | - | panic("unexpected call") | |
| 160 | - | } | |
| 161 | - | ||
| 162 | - | func (c *attachmentPushTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) { | |
| 163 | - | panic("unexpected call") | |
| 164 | - | } | |
| 165 | - | ||
| 166 | - | func (c *attachmentPushTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) { | |
| 167 | - | panic("unexpected call") | |
| 168 | - | } | |
| 169 | - | ||
| 170 | - | func (c *attachmentPushTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) { | |
| 171 | - | panic("unexpected call") | |
| 172 | - | } | |
| 173 | - | ||
| 174 | - | func (c *attachmentPushTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) { | |
| 175 | - | panic("unexpected call") | |
| 176 | - | } | |
| 177 | - | ||
| 178 | - | func (c *attachmentPushTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) { | |
| 179 | - | panic("unexpected call") | |
| 180 | - | } | |
| 181 | - | ||
| 182 | - | func (c *attachmentPushTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) { | |
| 183 | - | panic("unexpected call") | |
| 184 | - | } | |
| 185 | - | ||
| 186 | - | func (c *attachmentPushTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) { | |
| 187 | - | panic("unexpected call") | |
| 188 | - | } | |
| 189 | - | ||
| 190 | - | func (c *attachmentPushTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) { | |
| 191 | - | panic("unexpected call") | |
| 192 | - | } | |
| 193 | - | ||
| 194 | - | func (c *attachmentPushTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |
| 195 | - | panic("unexpected call") | |
| 196 | - | } | |
| 197 | - | ||
| 198 | 15 | func TestResolveAttachmentImage_OneTarget(t *testing.T) { | |
| 199 | - | cp := &attachmentPushTestControlPlane{ | |
| 200 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | |
| 201 | - | return []string{"app.example.com"}, nil | |
| 202 | - | }, | |
| 203 | - | getStatus: func(context.Context) (*remote.Status, error) { | |
| 204 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |
| 205 | - | }, | |
| 206 | - | } | |
| 16 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 17 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres").Return([]string{"app.example.com"}, nil).Once() | |
| 18 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() | |
| 207 | 19 | ||
| 208 | - | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "postgres") | |
| 20 | + | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cpMock, "postgres") | |
| 209 | 21 | ||
| 210 | 22 | require.NoError(t, err) | |
| 211 | 23 | assert.Equal(t, "registry.example.com", registry) | |
| @@ -214,16 +26,11 @@ func TestResolveAttachmentImage_OneTarget(t *testing.T) { | |||
| 214 | 26 | } | |
| 215 | 27 | ||
| 216 | 28 | func TestResolveAttachmentImage_MultipleTargets(t *testing.T) { | |
| 217 | - | cp := &attachmentPushTestControlPlane{ | |
| 218 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | |
| 219 | - | return []string{"app.example.com", "backend"}, nil | |
| 220 | - | }, | |
| 221 | - | getStatus: func(context.Context) (*remote.Status, error) { | |
| 222 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |
| 223 | - | }, | |
| 224 | - | } | |
| 29 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 30 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "redis").Return([]string{"app.example.com", "backend"}, nil).Once() | |
| 31 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() | |
| 225 | 32 | ||
| 226 | - | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "redis") | |
| 33 | + | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cpMock, "redis") | |
| 227 | 34 | ||
| 228 | 35 | require.NoError(t, err) | |
| 229 | 36 | assert.Equal(t, "registry.example.com", registry) | |
| @@ -232,13 +39,10 @@ func TestResolveAttachmentImage_MultipleTargets(t *testing.T) { | |||
| 232 | 39 | } | |
| 233 | 40 | ||
| 234 | 41 | func TestResolveAttachmentImage_NotConfigured(t *testing.T) { | |
| 235 | - | cp := &attachmentPushTestControlPlane{ | |
| 236 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | |
| 237 | - | return nil, nil | |
| 238 | - | }, | |
| 239 | - | } | |
| 42 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 43 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres").Return(nil, nil).Once() | |
| 240 | 44 | ||
| 241 | - | _, _, _, err := resolveAttachmentImage(context.Background(), cp, "postgres") | |
| 45 | + | _, _, _, err := resolveAttachmentImage(context.Background(), cpMock, "postgres") | |
| 242 | 46 | ||
| 243 | 47 | require.Error(t, err) | |
| 244 | 48 | assert.Contains(t, err.Error(), "not configured as an attachment") | |
| @@ -254,17 +58,12 @@ func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |||
| 254 | 58 | newImageOpsFn = origNewImageOps | |
| 255 | 59 | }) | |
| 256 | 60 | ||
| 257 | - | cp := &attachmentPushTestControlPlane{ | |
| 258 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | |
| 259 | - | return []string{"app.example.com"}, nil | |
| 260 | - | }, | |
| 261 | - | getStatus: func(context.Context) (*remote.Status, error) { | |
| 262 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |
| 263 | - | }, | |
| 264 | - | } | |
| 61 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 62 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres").Return([]string{"app.example.com"}, nil).Once() | |
| 63 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() | |
| 265 | 64 | ||
| 266 | 65 | resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) { | |
| 267 | - | return &controlPlaneHandle{plane: cp}, nil | |
| 66 | + | return &controlPlaneHandle{plane: cpMock}, nil | |
| 268 | 67 | } | |
| 269 | 68 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | |
| 270 | 69 | pushCalled := false | |
| @@ -301,17 +100,12 @@ func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | |||
| 301 | 100 | newImageOpsFn = origNewImageOps | |
| 302 | 101 | }) | |
| 303 | 102 | ||
| 304 | - | cp := &attachmentPushTestControlPlane{ | |
| 305 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | |
| 306 | - | return []string{"app.example.com"}, nil | |
| 307 | - | }, | |
| 308 | - | getStatus: func(context.Context) (*remote.Status, error) { | |
| 309 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |
| 310 | - | }, | |
| 311 | - | } | |
| 103 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 104 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres:18").Return([]string{"app.example.com"}, nil).Once() | |
| 105 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() | |
| 312 | 106 | ||
| 313 | 107 | resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) { | |
| 314 | - | return &controlPlaneHandle{plane: cp}, nil | |
| 108 | + | return &controlPlaneHandle{plane: cpMock}, nil | |
| 315 | 109 | } | |
| 316 | 110 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | |
| 317 | 111 | ||
| @@ -5,207 +5,19 @@ import ( | |||||
| 5 | "context" | 5 | "context" | ||
| 6 | "testing" | 6 | "testing" | ||
| 7 | 7 | ||||
| 8 | - | "github.com/bnema/gordon/internal/adapters/dto" | |||
| 9 | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | 8 | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | ||
| 10 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | 9 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | ||
| 11 | - | "github.com/bnema/gordon/internal/domain" | |||
| 12 | "github.com/stretchr/testify/assert" | 10 | "github.com/stretchr/testify/assert" | ||
| 13 | "github.com/stretchr/testify/mock" | 11 | "github.com/stretchr/testify/mock" | ||
| 14 | "github.com/stretchr/testify/require" | 12 | "github.com/stretchr/testify/require" | ||
| 15 | ) | 13 | ) | ||
| 16 | 14 | ||||
| 17 | - | type attachmentPushTestControlPlane struct { | |||
| 18 | - | findAttachmentTargets func(context.Context, string) ([]string, error) | |||
| 19 | - | getStatus func(context.Context) (*remote.Status, error) | |||
| 20 | - | } | |||
| 21 | - | ||||
| 22 | - | var _ ControlPlane = (*attachmentPushTestControlPlane)(nil) | |||
| 23 | - | ||||
| 24 | - | func (c *attachmentPushTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |||
| 25 | - | panic("unexpected call") | |||
| 26 | - | } | |||
| 27 | - | ||||
| 28 | - | func (c *attachmentPushTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 29 | - | panic("unexpected call") | |||
| 30 | - | } | |||
| 31 | - | ||||
| 32 | - | func (c *attachmentPushTestControlPlane) GetRoute(context.Context, string) (*domain.Route, error) { | |||
| 33 | - | panic("unexpected call") | |||
| 34 | - | } | |||
| 35 | - | ||||
| 36 | - | func (c *attachmentPushTestControlPlane) FindRoutesByImage(context.Context, string) ([]domain.Route, error) { | |||
| 37 | - | panic("unexpected call") | |||
| 38 | - | } | |||
| 39 | - | ||||
| 40 | - | func (c *attachmentPushTestControlPlane) AddRoute(context.Context, domain.Route) error { | |||
| 41 | - | panic("unexpected call") | |||
| 42 | - | } | |||
| 43 | - | ||||
| 44 | - | func (c *attachmentPushTestControlPlane) UpdateRoute(context.Context, domain.Route) error { | |||
| 45 | - | panic("unexpected call") | |||
| 46 | - | } | |||
| 47 | - | ||||
| 48 | - | func (c *attachmentPushTestControlPlane) RemoveRoute(context.Context, string) error { | |||
| 49 | - | panic("unexpected call") | |||
| 50 | - | } | |||
| 51 | - | ||||
| 52 | - | func (c *attachmentPushTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) { | |||
| 53 | - | panic("unexpected call") | |||
| 54 | - | } | |||
| 55 | - | ||||
| 56 | - | func (c *attachmentPushTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) { | |||
| 57 | - | panic("unexpected call") | |||
| 58 | - | } | |||
| 59 | - | ||||
| 60 | - | func (c *attachmentPushTestControlPlane) SetSecrets(context.Context, string, map[string]string) error { | |||
| 61 | - | panic("unexpected call") | |||
| 62 | - | } | |||
| 63 | - | ||||
| 64 | - | func (c *attachmentPushTestControlPlane) DeleteSecret(context.Context, string, string) error { | |||
| 65 | - | panic("unexpected call") | |||
| 66 | - | } | |||
| 67 | - | ||||
| 68 | - | func (c *attachmentPushTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error { | |||
| 69 | - | panic("unexpected call") | |||
| 70 | - | } | |||
| 71 | - | ||||
| 72 | - | func (c *attachmentPushTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error { | |||
| 73 | - | panic("unexpected call") | |||
| 74 | - | } | |||
| 75 | - | ||||
| 76 | - | func (c *attachmentPushTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) { | |||
| 77 | - | panic("unexpected call") | |||
| 78 | - | } | |||
| 79 | - | ||||
| 80 | - | func (c *attachmentPushTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) { | |||
| 81 | - | panic("unexpected call") | |||
| 82 | - | } | |||
| 83 | - | ||||
| 84 | - | func (c *attachmentPushTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |||
| 85 | - | panic("unexpected call") | |||
| 86 | - | } | |||
| 87 | - | ||||
| 88 | - | func (c *attachmentPushTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |||
| 89 | - | panic("unexpected call") | |||
| 90 | - | } | |||
| 91 | - | ||||
| 92 | - | func (c *attachmentPushTestControlPlane) FindAttachmentTargetsByImage(ctx context.Context, imageName string) ([]string, error) { | |||
| 93 | - | if c.findAttachmentTargets != nil { | |||
| 94 | - | return c.findAttachmentTargets(ctx, imageName) | |||
| 95 | - | } | |||
| 96 | - | return nil, nil | |||
| 97 | - | } | |||
| 98 | - | ||||
| 99 | - | func (c *attachmentPushTestControlPlane) AddAttachment(context.Context, string, string) error { | |||
| 100 | - | panic("unexpected call") | |||
| 101 | - | } | |||
| 102 | - | ||||
| 103 | - | func (c *attachmentPushTestControlPlane) RemoveAttachment(context.Context, string, string) error { | |||
| 104 | - | panic("unexpected call") | |||
| 105 | - | } | |||
| 106 | - | ||||
| 107 | - | func (c *attachmentPushTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) { | |||
| 108 | - | panic("unexpected call") | |||
| 109 | - | } | |||
| 110 | - | ||||
| 111 | - | func (c *attachmentPushTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error { | |||
| 112 | - | panic("unexpected call") | |||
| 113 | - | } | |||
| 114 | - | ||||
| 115 | - | func (c *attachmentPushTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error { | |||
| 116 | - | panic("unexpected call") | |||
| 117 | - | } | |||
| 118 | - | ||||
| 119 | - | func (c *attachmentPushTestControlPlane) GetTLSStatus(context.Context) (*dto.TLSStatusResponse, error) { | |||
| 120 | - | return &dto.TLSStatusResponse{}, nil | |||
| 121 | - | } | |||
| 122 | - | ||||
| 123 | - | func (c *attachmentPushTestControlPlane) GetStatus(ctx context.Context) (*remote.Status, error) { | |||
| 124 | - | if c.getStatus != nil { | |||
| 125 | - | return c.getStatus(ctx) | |||
| 126 | - | } | |||
| 127 | - | panic("unexpected call") | |||
| 128 | - | } | |||
| 129 | - | ||||
| 130 | - | func (c *attachmentPushTestControlPlane) Reload(context.Context) error { | |||
| 131 | - | panic("unexpected call") | |||
| 132 | - | } | |||
| 133 | - | ||||
| 134 | - | func (c *attachmentPushTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) { | |||
| 135 | - | panic("unexpected call") | |||
| 136 | - | } | |||
| 137 | - | ||||
| 138 | - | func (c *attachmentPushTestControlPlane) GetConfig(context.Context) (*remote.Config, error) { | |||
| 139 | - | panic("unexpected call") | |||
| 140 | - | } | |||
| 141 | - | ||||
| 142 | - | func (c *attachmentPushTestControlPlane) DeployIntent(context.Context, string) error { | |||
| 143 | - | panic("unexpected call") | |||
| 144 | - | } | |||
| 145 | - | ||||
| 146 | - | func (c *attachmentPushTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) { | |||
| 147 | - | panic("unexpected call") | |||
| 148 | - | } | |||
| 149 | - | ||||
| 150 | - | func (c *attachmentPushTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) { | |||
| 151 | - | panic("unexpected call") | |||
| 152 | - | } | |||
| 153 | - | ||||
| 154 | - | func (c *attachmentPushTestControlPlane) ListTags(context.Context, string) ([]string, error) { | |||
| 155 | - | panic("unexpected call") | |||
| 156 | - | } | |||
| 157 | - | ||||
| 158 | - | func (c *attachmentPushTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) { | |||
| 159 | - | panic("unexpected call") | |||
| 160 | - | } | |||
| 161 | - | ||||
| 162 | - | func (c *attachmentPushTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) { | |||
| 163 | - | panic("unexpected call") | |||
| 164 | - | } | |||
| 165 | - | ||||
| 166 | - | func (c *attachmentPushTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) { | |||
| 167 | - | panic("unexpected call") | |||
| 168 | - | } | |||
| 169 | - | ||||
| 170 | - | func (c *attachmentPushTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) { | |||
| 171 | - | panic("unexpected call") | |||
| 172 | - | } | |||
| 173 | - | ||||
| 174 | - | func (c *attachmentPushTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) { | |||
| 175 | - | panic("unexpected call") | |||
| 176 | - | } | |||
| 177 | - | ||||
| 178 | - | func (c *attachmentPushTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) { | |||
| 179 | - | panic("unexpected call") | |||
| 180 | - | } | |||
| 181 | - | ||||
| 182 | - | func (c *attachmentPushTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) { | |||
| 183 | - | panic("unexpected call") | |||
| 184 | - | } | |||
| 185 | - | ||||
| 186 | - | func (c *attachmentPushTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) { | |||
| 187 | - | panic("unexpected call") | |||
| 188 | - | } | |||
| 189 | - | ||||
| 190 | - | func (c *attachmentPushTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) { | |||
| 191 | - | panic("unexpected call") | |||
| 192 | - | } | |||
| 193 | - | ||||
| 194 | - | func (c *attachmentPushTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |||
| 195 | - | panic("unexpected call") | |||
| 196 | - | } | |||
| 197 | - | ||||
| 198 | func TestResolveAttachmentImage_OneTarget(t *testing.T) { | 15 | func TestResolveAttachmentImage_OneTarget(t *testing.T) { | ||
| 199 | - | cp := &attachmentPushTestControlPlane{ | 16 | + | cpMock := climocks.NewMockControlPlane(t) |
| 200 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | 17 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres").Return([]string{"app.example.com"}, nil).Once() |
| 201 | - | return []string{"app.example.com"}, nil | 18 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() |
| 202 | - | }, | |||
| 203 | - | getStatus: func(context.Context) (*remote.Status, error) { | |||
| 204 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |||
| 205 | - | }, | |||
| 206 | - | } | |||
| 207 | 19 | ||||
| 208 | - | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "postgres") | 20 | + | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cpMock, "postgres") |
| 209 | 21 | ||||
| 210 | require.NoError(t, err) | 22 | require.NoError(t, err) | ||
| 211 | assert.Equal(t, "registry.example.com", registry) | 23 | assert.Equal(t, "registry.example.com", registry) | ||
| @@ -214,16 +26,11 @@ func TestResolveAttachmentImage_OneTarget(t *testing.T) { | |||||
| 214 | } | 26 | } | ||
| 215 | 27 | ||||
| 216 | func TestResolveAttachmentImage_MultipleTargets(t *testing.T) { | 28 | func TestResolveAttachmentImage_MultipleTargets(t *testing.T) { | ||
| 217 | - | cp := &attachmentPushTestControlPlane{ | 29 | + | cpMock := climocks.NewMockControlPlane(t) |
| 218 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | 30 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "redis").Return([]string{"app.example.com", "backend"}, nil).Once() |
| 219 | - | return []string{"app.example.com", "backend"}, nil | 31 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() |
| 220 | - | }, | |||
| 221 | - | getStatus: func(context.Context) (*remote.Status, error) { | |||
| 222 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |||
| 223 | - | }, | |||
| 224 | - | } | |||
| 225 | 32 | ||||
| 226 | - | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "redis") | 33 | + | registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cpMock, "redis") |
| 227 | 34 | ||||
| 228 | require.NoError(t, err) | 35 | require.NoError(t, err) | ||
| 229 | assert.Equal(t, "registry.example.com", registry) | 36 | assert.Equal(t, "registry.example.com", registry) | ||
| @@ -232,13 +39,10 @@ func TestResolveAttachmentImage_MultipleTargets(t *testing.T) { | |||||
| 232 | } | 39 | } | ||
| 233 | 40 | ||||
| 234 | func TestResolveAttachmentImage_NotConfigured(t *testing.T) { | 41 | func TestResolveAttachmentImage_NotConfigured(t *testing.T) { | ||
| 235 | - | cp := &attachmentPushTestControlPlane{ | 42 | + | cpMock := climocks.NewMockControlPlane(t) |
| 236 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | 43 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres").Return(nil, nil).Once() |
| 237 | - | return nil, nil | |||
| 238 | - | }, | |||
| 239 | - | } | |||
| 240 | 44 | ||||
| 241 | - | _, _, _, err := resolveAttachmentImage(context.Background(), cp, "postgres") | 45 | + | _, _, _, err := resolveAttachmentImage(context.Background(), cpMock, "postgres") |
| 242 | 46 | ||||
| 243 | require.Error(t, err) | 47 | require.Error(t, err) | ||
| 244 | assert.Contains(t, err.Error(), "not configured as an attachment") | 48 | assert.Contains(t, err.Error(), "not configured as an attachment") | ||
| @@ -254,17 +58,12 @@ func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |||||
| 254 | newImageOpsFn = origNewImageOps | 58 | newImageOpsFn = origNewImageOps | ||
| 255 | }) | 59 | }) | ||
| 256 | 60 | ||||
| 257 | - | cp := &attachmentPushTestControlPlane{ | 61 | + | cpMock := climocks.NewMockControlPlane(t) |
| 258 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | 62 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres").Return([]string{"app.example.com"}, nil).Once() |
| 259 | - | return []string{"app.example.com"}, nil | 63 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() |
| 260 | - | }, | |||
| 261 | - | getStatus: func(context.Context) (*remote.Status, error) { | |||
| 262 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |||
| 263 | - | }, | |||
| 264 | - | } | |||
| 265 | 64 | ||||
| 266 | resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) { | 65 | resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) { | ||
| 267 | - | return &controlPlaneHandle{plane: cp}, nil | 66 | + | return &controlPlaneHandle{plane: cpMock}, nil |
| 268 | } | 67 | } | ||
| 269 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | 68 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | ||
| 270 | pushCalled := false | 69 | pushCalled := false | ||
| @@ -301,17 +100,12 @@ func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | |||||
| 301 | newImageOpsFn = origNewImageOps | 100 | newImageOpsFn = origNewImageOps | ||
| 302 | }) | 101 | }) | ||
| 303 | 102 | ||||
| 304 | - | cp := &attachmentPushTestControlPlane{ | 103 | + | cpMock := climocks.NewMockControlPlane(t) |
| 305 | - | findAttachmentTargets: func(context.Context, string) ([]string, error) { | 104 | + | cpMock.EXPECT().FindAttachmentTargetsByImage(context.Background(), "postgres:18").Return([]string{"app.example.com"}, nil).Once() |
| 306 | - | return []string{"app.example.com"}, nil | 105 | + | cpMock.EXPECT().GetStatus(context.Background()).Return(&remote.Status{RegistryDomain: "registry.example.com"}, nil).Once() |
| 307 | - | }, | |||
| 308 | - | getStatus: func(context.Context) (*remote.Status, error) { | |||
| 309 | - | return &remote.Status{RegistryDomain: "registry.example.com"}, nil | |||
| 310 | - | }, | |||
| 311 | - | } | |||
| 312 | 106 | ||||
| 313 | resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) { | 107 | resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) { | ||
| 314 | - | return &controlPlaneHandle{plane: cp}, nil | 108 | + | return &controlPlaneHandle{plane: cpMock}, nil |
| 315 | } | 109 | } | ||
| 316 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | 110 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | ||
| 317 | 111 | ||||
M internal/adapters/in/cli/images_test.go +27 -27
| @@ -16,7 +16,7 @@ import ( | |||
| 16 | 16 | "github.com/bnema/gordon/internal/domain" | |
| 17 | 17 | ) | |
| 18 | 18 | ||
| 19 | - | type fakeImagesClient struct { | |
| 19 | + | type imagesClientMock struct { | |
| 20 | 20 | listImagesResp []dto.Image | |
| 21 | 21 | listImagesErr error | |
| 22 | 22 | pruneResp *dto.ImagePruneResponse | |
| @@ -27,7 +27,7 @@ type fakeImagesClient struct { | |||
| 27 | 27 | lastPruneOpts dto.ImagePruneRequest | |
| 28 | 28 | } | |
| 29 | 29 | ||
| 30 | - | func (f *fakeImagesClient) ListImages(_ context.Context) ([]dto.Image, error) { | |
| 30 | + | func (f *imagesClientMock) ListImages(_ context.Context) ([]dto.Image, error) { | |
| 31 | 31 | f.listImagesCalls++ | |
| 32 | 32 | if f.listImagesErr != nil { | |
| 33 | 33 | return nil, f.listImagesErr | |
| @@ -35,7 +35,7 @@ func (f *fakeImagesClient) ListImages(_ context.Context) ([]dto.Image, error) { | |||
| 35 | 35 | return f.listImagesResp, nil | |
| 36 | 36 | } | |
| 37 | 37 | ||
| 38 | - | func (f *fakeImagesClient) PruneImages(_ context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | |
| 38 | + | func (f *imagesClientMock) PruneImages(_ context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | |
| 39 | 39 | f.pruneCalls++ | |
| 40 | 40 | f.lastPruneOpts = req | |
| 41 | 41 | if f.pruneErr != nil { | |
| @@ -50,7 +50,7 @@ func (f *fakeImagesClient) PruneImages(_ context.Context, req dto.ImagePruneRequ | |||
| 50 | 50 | ||
| 51 | 51 | func TestRunImagesList_PrintsRowsAndSummary(t *testing.T) { | |
| 52 | 52 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | |
| 53 | - | client := &fakeImagesClient{ | |
| 53 | + | client := &imagesClientMock{ | |
| 54 | 54 | listImagesResp: []dto.Image{ | |
| 55 | 55 | {Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}, | |
| 56 | 56 | {Repository: "<none>", Tag: "<none>", Size: 512_000, Created: createdAt, ID: "sha256:2222", Dangling: true}, | |
| @@ -71,7 +71,7 @@ func TestRunImagesList_PrintsRowsAndSummary(t *testing.T) { | |||
| 71 | 71 | ||
| 72 | 72 | func TestRunImagesList_HeaderOrderAndSummaryLine(t *testing.T) { | |
| 73 | 73 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | |
| 74 | - | client := &fakeImagesClient{ | |
| 74 | + | client := &imagesClientMock{ | |
| 75 | 75 | listImagesResp: []dto.Image{ | |
| 76 | 76 | {Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}, | |
| 77 | 77 | }, | |
| @@ -96,7 +96,7 @@ func TestRunImagesList_TruncatesLongValuesAndKeepsBoundedRowShape(t *testing.T) | |||
| 96 | 96 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | |
| 97 | 97 | longRepository := "registry.example.com/team/this-is-a-very-long-service-name-with-extra-segments/and/more/segments/than/anyone/should/reasonably/use" | |
| 98 | 98 | longImageID := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef-extra-suffix-for-overflow" | |
| 99 | - | client := &fakeImagesClient{ | |
| 99 | + | client := &imagesClientMock{ | |
| 100 | 100 | listImagesResp: []dto.Image{ | |
| 101 | 101 | {Repository: longRepository, Tag: "latest", Size: 12_000_000, Created: createdAt, ID: longImageID, Dangling: false}, | |
| 102 | 102 | }, | |
| @@ -138,7 +138,7 @@ func TestRunImagesList_LongDigestLikeValuesAndEmptyFormattedFields(t *testing.T) | |||
| 138 | 138 | longRepository := "registry.example.com/very/long/repository/path/with/deep/nesting/and-digest-like-ref@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef-extra" | |
| 139 | 139 | longTag := "release-2026-02-12-build-metadata-with-a-very-very-long-tag-name" | |
| 140 | 140 | longImageID := "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" | |
| 141 | - | client := &fakeImagesClient{ | |
| 141 | + | client := &imagesClientMock{ | |
| 142 | 142 | listImagesResp: []dto.Image{ | |
| 143 | 143 | {Repository: longRepository, Tag: longTag, Size: 0, Created: time.Time{}, ID: longImageID, Dangling: true}, | |
| 144 | 144 | }, | |
| @@ -174,7 +174,7 @@ func TestRunImagesList_LongDigestLikeValuesAndEmptyFormattedFields(t *testing.T) | |||
| 174 | 174 | } | |
| 175 | 175 | ||
| 176 | 176 | func TestRunImagesList_EmptyOutput(t *testing.T) { | |
| 177 | - | client := &fakeImagesClient{listImagesResp: []dto.Image{}} | |
| 177 | + | client := &imagesClientMock{listImagesResp: []dto.Image{}} | |
| 178 | 178 | ||
| 179 | 179 | var out bytes.Buffer | |
| 180 | 180 | err := runImagesList(context.Background(), client, &out, false) | |
| @@ -192,7 +192,7 @@ func TestRunImagesList_EmptyOutput(t *testing.T) { | |||
| 192 | 192 | ||
| 193 | 193 | func TestRunImagesPrune_DefaultScopeIsBothDanglingAndRegistry(t *testing.T) { | |
| 194 | 194 | // No scope flags → both scopes enabled, keep-releases defaults to domain default. | |
| 195 | - | client := &fakeImagesClient{ | |
| 195 | + | client := &imagesClientMock{ | |
| 196 | 196 | pruneResp: &dto.ImagePruneResponse{ | |
| 197 | 197 | Runtime: dto.RuntimePruneResult{DeletedCount: 1, SpaceReclaimed: 512}, | |
| 198 | 198 | Registry: dto.RegistryPruneResult{TagsRemoved: 2, BlobsRemoved: 1, SpaceReclaimed: 1024}, | |
| @@ -218,7 +218,7 @@ func TestRunImagesPrune_DefaultScopeIsBothDanglingAndRegistry(t *testing.T) { | |||
| 218 | 218 | } | |
| 219 | 219 | ||
| 220 | 220 | func TestRunImagesPrune_DanglingOnlyScopeSkipsRegistry(t *testing.T) { | |
| 221 | - | client := &fakeImagesClient{ | |
| 221 | + | client := &imagesClientMock{ | |
| 222 | 222 | pruneResp: &dto.ImagePruneResponse{ | |
| 223 | 223 | Runtime: dto.RuntimePruneResult{DeletedCount: 3, SpaceReclaimed: 2048}, | |
| 224 | 224 | }, | |
| @@ -241,7 +241,7 @@ func TestRunImagesPrune_DanglingOnlyScopeSkipsRegistry(t *testing.T) { | |||
| 241 | 241 | } | |
| 242 | 242 | ||
| 243 | 243 | func TestRunImagesPrune_RegistryOnlyScopeSkipsRuntime(t *testing.T) { | |
| 244 | - | client := &fakeImagesClient{ | |
| 244 | + | client := &imagesClientMock{ | |
| 245 | 245 | pruneResp: &dto.ImagePruneResponse{ | |
| 246 | 246 | Registry: dto.RegistryPruneResult{TagsRemoved: 4, BlobsRemoved: 2, SpaceReclaimed: 4096}, | |
| 247 | 247 | }, | |
| @@ -266,7 +266,7 @@ func TestRunImagesPrune_RegistryOnlyScopeSkipsRuntime(t *testing.T) { | |||
| 266 | 266 | } | |
| 267 | 267 | ||
| 268 | 268 | func TestRunImagesPrune_BothScopeFlagsExplicit(t *testing.T) { | |
| 269 | - | client := &fakeImagesClient{ | |
| 269 | + | client := &imagesClientMock{ | |
| 270 | 270 | pruneResp: &dto.ImagePruneResponse{ | |
| 271 | 271 | Runtime: dto.RuntimePruneResult{DeletedCount: 1, SpaceReclaimed: 100}, | |
| 272 | 272 | Registry: dto.RegistryPruneResult{TagsRemoved: 1, BlobsRemoved: 0, SpaceReclaimed: 200}, | |
| @@ -296,7 +296,7 @@ func TestRunImagesPrune_BothScopeFlagsExplicit(t *testing.T) { | |||
| 296 | 296 | // --------------------------------------------------------------------------- | |
| 297 | 297 | ||
| 298 | 298 | func TestRunImagesPrune_DryRunBothScopes(t *testing.T) { | |
| 299 | - | client := &fakeImagesClient{ | |
| 299 | + | client := &imagesClientMock{ | |
| 300 | 300 | listImagesResp: []dto.Image{{Dangling: true}, {Dangling: false}, {Dangling: true}}, | |
| 301 | 301 | } | |
| 302 | 302 | ||
| @@ -314,7 +314,7 @@ func TestRunImagesPrune_DryRunBothScopes(t *testing.T) { | |||
| 314 | 314 | } | |
| 315 | 315 | ||
| 316 | 316 | func TestRunImagesPrune_DryRunDanglingOnlyScope(t *testing.T) { | |
| 317 | - | client := &fakeImagesClient{ | |
| 317 | + | client := &imagesClientMock{ | |
| 318 | 318 | listImagesResp: []dto.Image{{Dangling: true}}, | |
| 319 | 319 | } | |
| 320 | 320 | ||
| @@ -330,7 +330,7 @@ func TestRunImagesPrune_DryRunDanglingOnlyScope(t *testing.T) { | |||
| 330 | 330 | } | |
| 331 | 331 | ||
| 332 | 332 | func TestRunImagesPrune_DryRunRegistryOnlyScope(t *testing.T) { | |
| 333 | - | client := &fakeImagesClient{ | |
| 333 | + | client := &imagesClientMock{ | |
| 334 | 334 | listImagesResp: []dto.Image{{Dangling: true}, {Dangling: false}}, | |
| 335 | 335 | } | |
| 336 | 336 | ||
| @@ -350,7 +350,7 @@ func TestRunImagesPrune_DryRunRegistryOnlyScope(t *testing.T) { | |||
| 350 | 350 | // --------------------------------------------------------------------------- | |
| 351 | 351 | ||
| 352 | 352 | func TestRunImagesPrune_KeepReleasesControlsRetention(t *testing.T) { | |
| 353 | - | client := &fakeImagesClient{ | |
| 353 | + | client := &imagesClientMock{ | |
| 354 | 354 | pruneResp: &dto.ImagePruneResponse{ | |
| 355 | 355 | Runtime: dto.RuntimePruneResult{DeletedCount: 0}, | |
| 356 | 356 | Registry: dto.RegistryPruneResult{TagsRemoved: 7, BlobsRemoved: 3, SpaceReclaimed: 9000}, | |
| @@ -367,7 +367,7 @@ func TestRunImagesPrune_KeepReleasesControlsRetention(t *testing.T) { | |||
| 367 | 367 | } | |
| 368 | 368 | ||
| 369 | 369 | func TestRunImagesPrune_RejectsNegativeKeepReleases(t *testing.T) { | |
| 370 | - | err := runImagesPrune(context.Background(), &fakeImagesClient{}, imagesPruneOptions{KeepReleases: -1}, &bytes.Buffer{}) | |
| 370 | + | err := runImagesPrune(context.Background(), &imagesClientMock{}, imagesPruneOptions{KeepReleases: -1}, &bytes.Buffer{}) | |
| 371 | 371 | require.Error(t, err) | |
| 372 | 372 | assert.Contains(t, err.Error(), "--keep-releases must be >= 0") | |
| 373 | 373 | } | |
| @@ -385,7 +385,7 @@ func TestRunImagesPrune_NonDryRunCallsConfirm(t *testing.T) { | |||
| 385 | 385 | } | |
| 386 | 386 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 387 | 387 | ||
| 388 | - | client := &fakeImagesClient{ | |
| 388 | + | client := &imagesClientMock{ | |
| 389 | 389 | pruneResp: &dto.ImagePruneResponse{ | |
| 390 | 390 | Runtime: dto.RuntimePruneResult{DeletedCount: 1}, | |
| 391 | 391 | Registry: dto.RegistryPruneResult{TagsRemoved: 1}, | |
| @@ -411,7 +411,7 @@ func TestRunImagesPrune_ConfirmPromptIncludesScopePreviewCounts(t *testing.T) { | |||
| 411 | 411 | } | |
| 412 | 412 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 413 | 413 | ||
| 414 | - | client := &fakeImagesClient{ | |
| 414 | + | client := &imagesClientMock{ | |
| 415 | 415 | listImagesResp: []dto.Image{ | |
| 416 | 416 | {Repository: "registry.example.com/app", Tag: "latest", Created: created}, | |
| 417 | 417 | {Repository: "registry.example.com/app", Tag: "v3", Created: created.Add(-1 * time.Hour)}, | |
| @@ -439,7 +439,7 @@ func TestRunImagesPrune_ConfirmRejectCancelsOperation(t *testing.T) { | |||
| 439 | 439 | } | |
| 440 | 440 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 441 | 441 | ||
| 442 | - | client := &fakeImagesClient{pruneResp: &dto.ImagePruneResponse{}} | |
| 442 | + | client := &imagesClientMock{pruneResp: &dto.ImagePruneResponse{}} | |
| 443 | 443 | ||
| 444 | 444 | var out bytes.Buffer | |
| 445 | 445 | opts := imagesPruneOptions{KeepReleases: domain.DefaultImagePruneKeepLast} | |
| @@ -459,7 +459,7 @@ func TestRunImagesPrune_NoConfirmSkipsPrompt(t *testing.T) { | |||
| 459 | 459 | } | |
| 460 | 460 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 461 | 461 | ||
| 462 | - | client := &fakeImagesClient{ | |
| 462 | + | client := &imagesClientMock{ | |
| 463 | 463 | pruneResp: &dto.ImagePruneResponse{ | |
| 464 | 464 | Runtime: dto.RuntimePruneResult{DeletedCount: 1}, | |
| 465 | 465 | }, | |
| @@ -483,7 +483,7 @@ func TestRunImagesPrune_DryRunNeverPrompts(t *testing.T) { | |||
| 483 | 483 | } | |
| 484 | 484 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 485 | 485 | ||
| 486 | - | client := &fakeImagesClient{ | |
| 486 | + | client := &imagesClientMock{ | |
| 487 | 487 | listImagesResp: []dto.Image{{Dangling: true}}, | |
| 488 | 488 | } | |
| 489 | 489 | ||
| @@ -504,7 +504,7 @@ func TestRunImagesPrune_ReturnsRemoteErrors(t *testing.T) { | |||
| 504 | 504 | pruneConfirmFunc = func(_ string) (bool, error) { return true, nil } | |
| 505 | 505 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 506 | 506 | ||
| 507 | - | client := &fakeImagesClient{pruneErr: errors.New("request failed")} | |
| 507 | + | client := &imagesClientMock{pruneErr: errors.New("request failed")} | |
| 508 | 508 | ||
| 509 | 509 | err := runImagesPrune(context.Background(), client, imagesPruneOptions{KeepReleases: 2}, &bytes.Buffer{}) | |
| 510 | 510 | require.Error(t, err) | |
| @@ -516,7 +516,7 @@ func TestRunImagesPrune_RejectsEmptyResponse(t *testing.T) { | |||
| 516 | 516 | pruneConfirmFunc = func(_ string) (bool, error) { return true, nil } | |
| 517 | 517 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | |
| 518 | 518 | ||
| 519 | - | client := &fakeImagesClient{pruneResp: nil} | |
| 519 | + | client := &imagesClientMock{pruneResp: nil} | |
| 520 | 520 | err := runImagesPrune(context.Background(), client, imagesPruneOptions{KeepReleases: 1}, &bytes.Buffer{}) | |
| 521 | 521 | require.Error(t, err) | |
| 522 | 522 | assert.Contains(t, err.Error(), "empty response") | |
| @@ -532,7 +532,7 @@ func stripANSI(value string) string { | |||
| 532 | 532 | } | |
| 533 | 533 | ||
| 534 | 534 | func findHeaderLine(text string) string { | |
| 535 | - | for _, line := range strings.Split(text, "\n") { | |
| 535 | + | for line := range strings.SplitSeq(text, "\n") { | |
| 536 | 536 | if strings.Contains(line, "REPOSITORY") && strings.Contains(line, "DANGLING") { | |
| 537 | 537 | return line | |
| 538 | 538 | } | |
| @@ -542,7 +542,7 @@ func findHeaderLine(text string) string { | |||
| 542 | 542 | } | |
| 543 | 543 | ||
| 544 | 544 | func findImageListRowLine(text string, tag string) string { | |
| 545 | - | for _, line := range strings.Split(text, "\n") { | |
| 545 | + | for line := range strings.SplitSeq(text, "\n") { | |
| 546 | 546 | trimmed := strings.TrimSpace(line) | |
| 547 | 547 | if trimmed == "" { | |
| 548 | 548 | continue | |
| @@ -562,7 +562,7 @@ func findImageListRowLine(text string, tag string) string { | |||
| 562 | 562 | ||
| 563 | 563 | func findImageListRows(text string) [][]string { | |
| 564 | 564 | rows := make([][]string, 0) | |
| 565 | - | for _, line := range strings.Split(text, "\n") { | |
| 565 | + | for line := range strings.SplitSeq(text, "\n") { | |
| 566 | 566 | trimmed := strings.TrimSpace(line) | |
| 567 | 567 | if trimmed == "" { | |
| 568 | 568 | continue | |
| @@ -16,7 +16,7 @@ import ( | |||||
| 16 | "github.com/bnema/gordon/internal/domain" | 16 | "github.com/bnema/gordon/internal/domain" | ||
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| 19 | - | type fakeImagesClient struct { | 19 | + | type imagesClientMock struct { |
| 20 | listImagesResp []dto.Image | 20 | listImagesResp []dto.Image | ||
| 21 | listImagesErr error | 21 | listImagesErr error | ||
| 22 | pruneResp *dto.ImagePruneResponse | 22 | pruneResp *dto.ImagePruneResponse | ||
| @@ -27,7 +27,7 @@ type fakeImagesClient struct { | |||||
| 27 | lastPruneOpts dto.ImagePruneRequest | 27 | lastPruneOpts dto.ImagePruneRequest | ||
| 28 | } | 28 | } | ||
| 29 | 29 | ||||
| 30 | - | func (f *fakeImagesClient) ListImages(_ context.Context) ([]dto.Image, error) { | 30 | + | func (f *imagesClientMock) ListImages(_ context.Context) ([]dto.Image, error) { |
| 31 | f.listImagesCalls++ | 31 | f.listImagesCalls++ | ||
| 32 | if f.listImagesErr != nil { | 32 | if f.listImagesErr != nil { | ||
| 33 | return nil, f.listImagesErr | 33 | return nil, f.listImagesErr | ||
| @@ -35,7 +35,7 @@ func (f *fakeImagesClient) ListImages(_ context.Context) ([]dto.Image, error) { | |||||
| 35 | return f.listImagesResp, nil | 35 | return f.listImagesResp, nil | ||
| 36 | } | 36 | } | ||
| 37 | 37 | ||||
| 38 | - | func (f *fakeImagesClient) PruneImages(_ context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | 38 | + | func (f *imagesClientMock) PruneImages(_ context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { |
| 39 | f.pruneCalls++ | 39 | f.pruneCalls++ | ||
| 40 | f.lastPruneOpts = req | 40 | f.lastPruneOpts = req | ||
| 41 | if f.pruneErr != nil { | 41 | if f.pruneErr != nil { | ||
| @@ -50,7 +50,7 @@ func (f *fakeImagesClient) PruneImages(_ context.Context, req dto.ImagePruneRequ | |||||
| 50 | 50 | ||||
| 51 | func TestRunImagesList_PrintsRowsAndSummary(t *testing.T) { | 51 | func TestRunImagesList_PrintsRowsAndSummary(t *testing.T) { | ||
| 52 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | 52 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | ||
| 53 | - | client := &fakeImagesClient{ | 53 | + | client := &imagesClientMock{ |
| 54 | listImagesResp: []dto.Image{ | 54 | listImagesResp: []dto.Image{ | ||
| 55 | {Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}, | 55 | {Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}, | ||
| 56 | {Repository: "<none>", Tag: "<none>", Size: 512_000, Created: createdAt, ID: "sha256:2222", Dangling: true}, | 56 | {Repository: "<none>", Tag: "<none>", Size: 512_000, Created: createdAt, ID: "sha256:2222", Dangling: true}, | ||
| @@ -71,7 +71,7 @@ func TestRunImagesList_PrintsRowsAndSummary(t *testing.T) { | |||||
| 71 | 71 | ||||
| 72 | func TestRunImagesList_HeaderOrderAndSummaryLine(t *testing.T) { | 72 | func TestRunImagesList_HeaderOrderAndSummaryLine(t *testing.T) { | ||
| 73 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | 73 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | ||
| 74 | - | client := &fakeImagesClient{ | 74 | + | client := &imagesClientMock{ |
| 75 | listImagesResp: []dto.Image{ | 75 | listImagesResp: []dto.Image{ | ||
| 76 | {Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}, | 76 | {Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}, | ||
| 77 | }, | 77 | }, | ||
| @@ -96,7 +96,7 @@ func TestRunImagesList_TruncatesLongValuesAndKeepsBoundedRowShape(t *testing.T) | |||||
| 96 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | 96 | createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC) | ||
| 97 | longRepository := "registry.example.com/team/this-is-a-very-long-service-name-with-extra-segments/and/more/segments/than/anyone/should/reasonably/use" | 97 | longRepository := "registry.example.com/team/this-is-a-very-long-service-name-with-extra-segments/and/more/segments/than/anyone/should/reasonably/use" | ||
| 98 | longImageID := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef-extra-suffix-for-overflow" | 98 | longImageID := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef-extra-suffix-for-overflow" | ||
| 99 | - | client := &fakeImagesClient{ | 99 | + | client := &imagesClientMock{ |
| 100 | listImagesResp: []dto.Image{ | 100 | listImagesResp: []dto.Image{ | ||
| 101 | {Repository: longRepository, Tag: "latest", Size: 12_000_000, Created: createdAt, ID: longImageID, Dangling: false}, | 101 | {Repository: longRepository, Tag: "latest", Size: 12_000_000, Created: createdAt, ID: longImageID, Dangling: false}, | ||
| 102 | }, | 102 | }, | ||
| @@ -138,7 +138,7 @@ func TestRunImagesList_LongDigestLikeValuesAndEmptyFormattedFields(t *testing.T) | |||||
| 138 | longRepository := "registry.example.com/very/long/repository/path/with/deep/nesting/and-digest-like-ref@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef-extra" | 138 | longRepository := "registry.example.com/very/long/repository/path/with/deep/nesting/and-digest-like-ref@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef-extra" | ||
| 139 | longTag := "release-2026-02-12-build-metadata-with-a-very-very-long-tag-name" | 139 | longTag := "release-2026-02-12-build-metadata-with-a-very-very-long-tag-name" | ||
| 140 | longImageID := "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" | 140 | longImageID := "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" | ||
| 141 | - | client := &fakeImagesClient{ | 141 | + | client := &imagesClientMock{ |
| 142 | listImagesResp: []dto.Image{ | 142 | listImagesResp: []dto.Image{ | ||
| 143 | {Repository: longRepository, Tag: longTag, Size: 0, Created: time.Time{}, ID: longImageID, Dangling: true}, | 143 | {Repository: longRepository, Tag: longTag, Size: 0, Created: time.Time{}, ID: longImageID, Dangling: true}, | ||
| 144 | }, | 144 | }, | ||
| @@ -174,7 +174,7 @@ func TestRunImagesList_LongDigestLikeValuesAndEmptyFormattedFields(t *testing.T) | |||||
| 174 | } | 174 | } | ||
| 175 | 175 | ||||
| 176 | func TestRunImagesList_EmptyOutput(t *testing.T) { | 176 | func TestRunImagesList_EmptyOutput(t *testing.T) { | ||
| 177 | - | client := &fakeImagesClient{listImagesResp: []dto.Image{}} | 177 | + | client := &imagesClientMock{listImagesResp: []dto.Image{}} |
| 178 | 178 | ||||
| 179 | var out bytes.Buffer | 179 | var out bytes.Buffer | ||
| 180 | err := runImagesList(context.Background(), client, &out, false) | 180 | err := runImagesList(context.Background(), client, &out, false) | ||
| @@ -192,7 +192,7 @@ func TestRunImagesList_EmptyOutput(t *testing.T) { | |||||
| 192 | 192 | ||||
| 193 | func TestRunImagesPrune_DefaultScopeIsBothDanglingAndRegistry(t *testing.T) { | 193 | func TestRunImagesPrune_DefaultScopeIsBothDanglingAndRegistry(t *testing.T) { | ||
| 194 | // No scope flags → both scopes enabled, keep-releases defaults to domain default. | 194 | // No scope flags → both scopes enabled, keep-releases defaults to domain default. | ||
| 195 | - | client := &fakeImagesClient{ | 195 | + | client := &imagesClientMock{ |
| 196 | pruneResp: &dto.ImagePruneResponse{ | 196 | pruneResp: &dto.ImagePruneResponse{ | ||
| 197 | Runtime: dto.RuntimePruneResult{DeletedCount: 1, SpaceReclaimed: 512}, | 197 | Runtime: dto.RuntimePruneResult{DeletedCount: 1, SpaceReclaimed: 512}, | ||
| 198 | Registry: dto.RegistryPruneResult{TagsRemoved: 2, BlobsRemoved: 1, SpaceReclaimed: 1024}, | 198 | Registry: dto.RegistryPruneResult{TagsRemoved: 2, BlobsRemoved: 1, SpaceReclaimed: 1024}, | ||
| @@ -218,7 +218,7 @@ func TestRunImagesPrune_DefaultScopeIsBothDanglingAndRegistry(t *testing.T) { | |||||
| 218 | } | 218 | } | ||
| 219 | 219 | ||||
| 220 | func TestRunImagesPrune_DanglingOnlyScopeSkipsRegistry(t *testing.T) { | 220 | func TestRunImagesPrune_DanglingOnlyScopeSkipsRegistry(t *testing.T) { | ||
| 221 | - | client := &fakeImagesClient{ | 221 | + | client := &imagesClientMock{ |
| 222 | pruneResp: &dto.ImagePruneResponse{ | 222 | pruneResp: &dto.ImagePruneResponse{ | ||
| 223 | Runtime: dto.RuntimePruneResult{DeletedCount: 3, SpaceReclaimed: 2048}, | 223 | Runtime: dto.RuntimePruneResult{DeletedCount: 3, SpaceReclaimed: 2048}, | ||
| 224 | }, | 224 | }, | ||
| @@ -241,7 +241,7 @@ func TestRunImagesPrune_DanglingOnlyScopeSkipsRegistry(t *testing.T) { | |||||
| 241 | } | 241 | } | ||
| 242 | 242 | ||||
| 243 | func TestRunImagesPrune_RegistryOnlyScopeSkipsRuntime(t *testing.T) { | 243 | func TestRunImagesPrune_RegistryOnlyScopeSkipsRuntime(t *testing.T) { | ||
| 244 | - | client := &fakeImagesClient{ | 244 | + | client := &imagesClientMock{ |
| 245 | pruneResp: &dto.ImagePruneResponse{ | 245 | pruneResp: &dto.ImagePruneResponse{ | ||
| 246 | Registry: dto.RegistryPruneResult{TagsRemoved: 4, BlobsRemoved: 2, SpaceReclaimed: 4096}, | 246 | Registry: dto.RegistryPruneResult{TagsRemoved: 4, BlobsRemoved: 2, SpaceReclaimed: 4096}, | ||
| 247 | }, | 247 | }, | ||
| @@ -266,7 +266,7 @@ func TestRunImagesPrune_RegistryOnlyScopeSkipsRuntime(t *testing.T) { | |||||
| 266 | } | 266 | } | ||
| 267 | 267 | ||||
| 268 | func TestRunImagesPrune_BothScopeFlagsExplicit(t *testing.T) { | 268 | func TestRunImagesPrune_BothScopeFlagsExplicit(t *testing.T) { | ||
| 269 | - | client := &fakeImagesClient{ | 269 | + | client := &imagesClientMock{ |
| 270 | pruneResp: &dto.ImagePruneResponse{ | 270 | pruneResp: &dto.ImagePruneResponse{ | ||
| 271 | Runtime: dto.RuntimePruneResult{DeletedCount: 1, SpaceReclaimed: 100}, | 271 | Runtime: dto.RuntimePruneResult{DeletedCount: 1, SpaceReclaimed: 100}, | ||
| 272 | Registry: dto.RegistryPruneResult{TagsRemoved: 1, BlobsRemoved: 0, SpaceReclaimed: 200}, | 272 | Registry: dto.RegistryPruneResult{TagsRemoved: 1, BlobsRemoved: 0, SpaceReclaimed: 200}, | ||
| @@ -296,7 +296,7 @@ func TestRunImagesPrune_BothScopeFlagsExplicit(t *testing.T) { | |||||
| 296 | // --------------------------------------------------------------------------- | 296 | // --------------------------------------------------------------------------- | ||
| 297 | 297 | ||||
| 298 | func TestRunImagesPrune_DryRunBothScopes(t *testing.T) { | 298 | func TestRunImagesPrune_DryRunBothScopes(t *testing.T) { | ||
| 299 | - | client := &fakeImagesClient{ | 299 | + | client := &imagesClientMock{ |
| 300 | listImagesResp: []dto.Image{{Dangling: true}, {Dangling: false}, {Dangling: true}}, | 300 | listImagesResp: []dto.Image{{Dangling: true}, {Dangling: false}, {Dangling: true}}, | ||
| 301 | } | 301 | } | ||
| 302 | 302 | ||||
| @@ -314,7 +314,7 @@ func TestRunImagesPrune_DryRunBothScopes(t *testing.T) { | |||||
| 314 | } | 314 | } | ||
| 315 | 315 | ||||
| 316 | func TestRunImagesPrune_DryRunDanglingOnlyScope(t *testing.T) { | 316 | func TestRunImagesPrune_DryRunDanglingOnlyScope(t *testing.T) { | ||
| 317 | - | client := &fakeImagesClient{ | 317 | + | client := &imagesClientMock{ |
| 318 | listImagesResp: []dto.Image{{Dangling: true}}, | 318 | listImagesResp: []dto.Image{{Dangling: true}}, | ||
| 319 | } | 319 | } | ||
| 320 | 320 | ||||
| @@ -330,7 +330,7 @@ func TestRunImagesPrune_DryRunDanglingOnlyScope(t *testing.T) { | |||||
| 330 | } | 330 | } | ||
| 331 | 331 | ||||
| 332 | func TestRunImagesPrune_DryRunRegistryOnlyScope(t *testing.T) { | 332 | func TestRunImagesPrune_DryRunRegistryOnlyScope(t *testing.T) { | ||
| 333 | - | client := &fakeImagesClient{ | 333 | + | client := &imagesClientMock{ |
| 334 | listImagesResp: []dto.Image{{Dangling: true}, {Dangling: false}}, | 334 | listImagesResp: []dto.Image{{Dangling: true}, {Dangling: false}}, | ||
| 335 | } | 335 | } | ||
| 336 | 336 | ||||
| @@ -350,7 +350,7 @@ func TestRunImagesPrune_DryRunRegistryOnlyScope(t *testing.T) { | |||||
| 350 | // --------------------------------------------------------------------------- | 350 | // --------------------------------------------------------------------------- | ||
| 351 | 351 | ||||
| 352 | func TestRunImagesPrune_KeepReleasesControlsRetention(t *testing.T) { | 352 | func TestRunImagesPrune_KeepReleasesControlsRetention(t *testing.T) { | ||
| 353 | - | client := &fakeImagesClient{ | 353 | + | client := &imagesClientMock{ |
| 354 | pruneResp: &dto.ImagePruneResponse{ | 354 | pruneResp: &dto.ImagePruneResponse{ | ||
| 355 | Runtime: dto.RuntimePruneResult{DeletedCount: 0}, | 355 | Runtime: dto.RuntimePruneResult{DeletedCount: 0}, | ||
| 356 | Registry: dto.RegistryPruneResult{TagsRemoved: 7, BlobsRemoved: 3, SpaceReclaimed: 9000}, | 356 | Registry: dto.RegistryPruneResult{TagsRemoved: 7, BlobsRemoved: 3, SpaceReclaimed: 9000}, | ||
| @@ -367,7 +367,7 @@ func TestRunImagesPrune_KeepReleasesControlsRetention(t *testing.T) { | |||||
| 367 | } | 367 | } | ||
| 368 | 368 | ||||
| 369 | func TestRunImagesPrune_RejectsNegativeKeepReleases(t *testing.T) { | 369 | func TestRunImagesPrune_RejectsNegativeKeepReleases(t *testing.T) { | ||
| 370 | - | err := runImagesPrune(context.Background(), &fakeImagesClient{}, imagesPruneOptions{KeepReleases: -1}, &bytes.Buffer{}) | 370 | + | err := runImagesPrune(context.Background(), &imagesClientMock{}, imagesPruneOptions{KeepReleases: -1}, &bytes.Buffer{}) |
| 371 | require.Error(t, err) | 371 | require.Error(t, err) | ||
| 372 | assert.Contains(t, err.Error(), "--keep-releases must be >= 0") | 372 | assert.Contains(t, err.Error(), "--keep-releases must be >= 0") | ||
| 373 | } | 373 | } | ||
| @@ -385,7 +385,7 @@ func TestRunImagesPrune_NonDryRunCallsConfirm(t *testing.T) { | |||||
| 385 | } | 385 | } | ||
| 386 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 386 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 387 | 387 | ||||
| 388 | - | client := &fakeImagesClient{ | 388 | + | client := &imagesClientMock{ |
| 389 | pruneResp: &dto.ImagePruneResponse{ | 389 | pruneResp: &dto.ImagePruneResponse{ | ||
| 390 | Runtime: dto.RuntimePruneResult{DeletedCount: 1}, | 390 | Runtime: dto.RuntimePruneResult{DeletedCount: 1}, | ||
| 391 | Registry: dto.RegistryPruneResult{TagsRemoved: 1}, | 391 | Registry: dto.RegistryPruneResult{TagsRemoved: 1}, | ||
| @@ -411,7 +411,7 @@ func TestRunImagesPrune_ConfirmPromptIncludesScopePreviewCounts(t *testing.T) { | |||||
| 411 | } | 411 | } | ||
| 412 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 412 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 413 | 413 | ||||
| 414 | - | client := &fakeImagesClient{ | 414 | + | client := &imagesClientMock{ |
| 415 | listImagesResp: []dto.Image{ | 415 | listImagesResp: []dto.Image{ | ||
| 416 | {Repository: "registry.example.com/app", Tag: "latest", Created: created}, | 416 | {Repository: "registry.example.com/app", Tag: "latest", Created: created}, | ||
| 417 | {Repository: "registry.example.com/app", Tag: "v3", Created: created.Add(-1 * time.Hour)}, | 417 | {Repository: "registry.example.com/app", Tag: "v3", Created: created.Add(-1 * time.Hour)}, | ||
| @@ -439,7 +439,7 @@ func TestRunImagesPrune_ConfirmRejectCancelsOperation(t *testing.T) { | |||||
| 439 | } | 439 | } | ||
| 440 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 440 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 441 | 441 | ||||
| 442 | - | client := &fakeImagesClient{pruneResp: &dto.ImagePruneResponse{}} | 442 | + | client := &imagesClientMock{pruneResp: &dto.ImagePruneResponse{}} |
| 443 | 443 | ||||
| 444 | var out bytes.Buffer | 444 | var out bytes.Buffer | ||
| 445 | opts := imagesPruneOptions{KeepReleases: domain.DefaultImagePruneKeepLast} | 445 | opts := imagesPruneOptions{KeepReleases: domain.DefaultImagePruneKeepLast} | ||
| @@ -459,7 +459,7 @@ func TestRunImagesPrune_NoConfirmSkipsPrompt(t *testing.T) { | |||||
| 459 | } | 459 | } | ||
| 460 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 460 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 461 | 461 | ||||
| 462 | - | client := &fakeImagesClient{ | 462 | + | client := &imagesClientMock{ |
| 463 | pruneResp: &dto.ImagePruneResponse{ | 463 | pruneResp: &dto.ImagePruneResponse{ | ||
| 464 | Runtime: dto.RuntimePruneResult{DeletedCount: 1}, | 464 | Runtime: dto.RuntimePruneResult{DeletedCount: 1}, | ||
| 465 | }, | 465 | }, | ||
| @@ -483,7 +483,7 @@ func TestRunImagesPrune_DryRunNeverPrompts(t *testing.T) { | |||||
| 483 | } | 483 | } | ||
| 484 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 484 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 485 | 485 | ||||
| 486 | - | client := &fakeImagesClient{ | 486 | + | client := &imagesClientMock{ |
| 487 | listImagesResp: []dto.Image{{Dangling: true}}, | 487 | listImagesResp: []dto.Image{{Dangling: true}}, | ||
| 488 | } | 488 | } | ||
| 489 | 489 | ||||
| @@ -504,7 +504,7 @@ func TestRunImagesPrune_ReturnsRemoteErrors(t *testing.T) { | |||||
| 504 | pruneConfirmFunc = func(_ string) (bool, error) { return true, nil } | 504 | pruneConfirmFunc = func(_ string) (bool, error) { return true, nil } | ||
| 505 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 505 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 506 | 506 | ||||
| 507 | - | client := &fakeImagesClient{pruneErr: errors.New("request failed")} | 507 | + | client := &imagesClientMock{pruneErr: errors.New("request failed")} |
| 508 | 508 | ||||
| 509 | err := runImagesPrune(context.Background(), client, imagesPruneOptions{KeepReleases: 2}, &bytes.Buffer{}) | 509 | err := runImagesPrune(context.Background(), client, imagesPruneOptions{KeepReleases: 2}, &bytes.Buffer{}) | ||
| 510 | require.Error(t, err) | 510 | require.Error(t, err) | ||
| @@ -516,7 +516,7 @@ func TestRunImagesPrune_RejectsEmptyResponse(t *testing.T) { | |||||
| 516 | pruneConfirmFunc = func(_ string) (bool, error) { return true, nil } | 516 | pruneConfirmFunc = func(_ string) (bool, error) { return true, nil } | ||
| 517 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | 517 | t.Cleanup(func() { pruneConfirmFunc = origConfirm }) | ||
| 518 | 518 | ||||
| 519 | - | client := &fakeImagesClient{pruneResp: nil} | 519 | + | client := &imagesClientMock{pruneResp: nil} |
| 520 | err := runImagesPrune(context.Background(), client, imagesPruneOptions{KeepReleases: 1}, &bytes.Buffer{}) | 520 | err := runImagesPrune(context.Background(), client, imagesPruneOptions{KeepReleases: 1}, &bytes.Buffer{}) | ||
| 521 | require.Error(t, err) | 521 | require.Error(t, err) | ||
| 522 | assert.Contains(t, err.Error(), "empty response") | 522 | assert.Contains(t, err.Error(), "empty response") | ||
| @@ -532,7 +532,7 @@ func stripANSI(value string) string { | |||||
| 532 | } | 532 | } | ||
| 533 | 533 | ||||
| 534 | func findHeaderLine(text string) string { | 534 | func findHeaderLine(text string) string { | ||
| 535 | - | for _, line := range strings.Split(text, "\n") { | 535 | + | for line := range strings.SplitSeq(text, "\n") { |
| 536 | if strings.Contains(line, "REPOSITORY") && strings.Contains(line, "DANGLING") { | 536 | if strings.Contains(line, "REPOSITORY") && strings.Contains(line, "DANGLING") { | ||
| 537 | return line | 537 | return line | ||
| 538 | } | 538 | } | ||
| @@ -542,7 +542,7 @@ func findHeaderLine(text string) string { | |||||
| 542 | } | 542 | } | ||
| 543 | 543 | ||||
| 544 | func findImageListRowLine(text string, tag string) string { | 544 | func findImageListRowLine(text string, tag string) string { | ||
| 545 | - | for _, line := range strings.Split(text, "\n") { | 545 | + | for line := range strings.SplitSeq(text, "\n") { |
| 546 | trimmed := strings.TrimSpace(line) | 546 | trimmed := strings.TrimSpace(line) | ||
| 547 | if trimmed == "" { | 547 | if trimmed == "" { | ||
| 548 | continue | 548 | continue | ||
| @@ -562,7 +562,7 @@ func findImageListRowLine(text string, tag string) string { | |||||
| 562 | 562 | ||||
| 563 | func findImageListRows(text string) [][]string { | 563 | func findImageListRows(text string) [][]string { | ||
| 564 | rows := make([][]string, 0) | 564 | rows := make([][]string, 0) | ||
| 565 | - | for _, line := range strings.Split(text, "\n") { | 565 | + | for line := range strings.SplitSeq(text, "\n") { |
| 566 | trimmed := strings.TrimSpace(line) | 566 | trimmed := strings.TrimSpace(line) | ||
| 567 | if trimmed == "" { | 567 | if trimmed == "" { | ||
| 568 | continue | 568 | continue | ||
A internal/adapters/in/cli/mocks/mock_images_client.go +169 -0
| @@ -0,0 +1,169 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package mocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/gordon/internal/adapters/dto" | |
| 11 | + | mock "github.com/stretchr/testify/mock" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // NewMockimagesClient creates a new instance of MockimagesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 15 | + | // The first argument is typically a *testing.T value. | |
| 16 | + | func NewMockimagesClient(t interface { | |
| 17 | + | mock.TestingT | |
| 18 | + | Cleanup(func()) | |
| 19 | + | }) *MockimagesClient { | |
| 20 | + | mock := &MockimagesClient{} | |
| 21 | + | mock.Mock.Test(t) | |
| 22 | + | ||
| 23 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 24 | + | ||
| 25 | + | return mock | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // MockimagesClient is an autogenerated mock type for the imagesClient type | |
| 29 | + | type MockimagesClient struct { | |
| 30 | + | mock.Mock | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | type MockimagesClient_Expecter struct { | |
| 34 | + | mock *mock.Mock | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | func (_m *MockimagesClient) EXPECT() *MockimagesClient_Expecter { | |
| 38 | + | return &MockimagesClient_Expecter{mock: &_m.Mock} | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | // ListImages provides a mock function for the type MockimagesClient | |
| 42 | + | func (_mock *MockimagesClient) ListImages(ctx context.Context) ([]dto.Image, error) { | |
| 43 | + | ret := _mock.Called(ctx) | |
| 44 | + | ||
| 45 | + | if len(ret) == 0 { | |
| 46 | + | panic("no return value specified for ListImages") | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | var r0 []dto.Image | |
| 50 | + | var r1 error | |
| 51 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]dto.Image, error)); ok { | |
| 52 | + | return returnFunc(ctx) | |
| 53 | + | } | |
| 54 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []dto.Image); ok { | |
| 55 | + | r0 = returnFunc(ctx) | |
| 56 | + | } else { | |
| 57 | + | if ret.Get(0) != nil { | |
| 58 | + | r0 = ret.Get(0).([]dto.Image) | |
| 59 | + | } | |
| 60 | + | } | |
| 61 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |
| 62 | + | r1 = returnFunc(ctx) | |
| 63 | + | } else { | |
| 64 | + | r1 = ret.Error(1) | |
| 65 | + | } | |
| 66 | + | return r0, r1 | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // MockimagesClient_ListImages_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListImages' | |
| 70 | + | type MockimagesClient_ListImages_Call struct { | |
| 71 | + | *mock.Call | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | // ListImages is a helper method to define mock.On call | |
| 75 | + | // - ctx context.Context | |
| 76 | + | func (_e *MockimagesClient_Expecter) ListImages(ctx interface{}) *MockimagesClient_ListImages_Call { | |
| 77 | + | return &MockimagesClient_ListImages_Call{Call: _e.mock.On("ListImages", ctx)} | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | func (_c *MockimagesClient_ListImages_Call) Run(run func(ctx context.Context)) *MockimagesClient_ListImages_Call { | |
| 81 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 82 | + | var arg0 context.Context | |
| 83 | + | if args[0] != nil { | |
| 84 | + | arg0 = args[0].(context.Context) | |
| 85 | + | } | |
| 86 | + | run( | |
| 87 | + | arg0, | |
| 88 | + | ) | |
| 89 | + | }) | |
| 90 | + | return _c | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | func (_c *MockimagesClient_ListImages_Call) Return(images []dto.Image, err error) *MockimagesClient_ListImages_Call { | |
| 94 | + | _c.Call.Return(images, err) | |
| 95 | + | return _c | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | func (_c *MockimagesClient_ListImages_Call) RunAndReturn(run func(ctx context.Context) ([]dto.Image, error)) *MockimagesClient_ListImages_Call { | |
| 99 | + | _c.Call.Return(run) | |
| 100 | + | return _c | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // PruneImages provides a mock function for the type MockimagesClient | |
| 104 | + | func (_mock *MockimagesClient) PruneImages(ctx context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | |
| 105 | + | ret := _mock.Called(ctx, req) | |
| 106 | + | ||
| 107 | + | if len(ret) == 0 { | |
| 108 | + | panic("no return value specified for PruneImages") | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | var r0 *dto.ImagePruneResponse | |
| 112 | + | var r1 error | |
| 113 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.ImagePruneRequest) (*dto.ImagePruneResponse, error)); ok { | |
| 114 | + | return returnFunc(ctx, req) | |
| 115 | + | } | |
| 116 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.ImagePruneRequest) *dto.ImagePruneResponse); ok { | |
| 117 | + | r0 = returnFunc(ctx, req) | |
| 118 | + | } else { | |
| 119 | + | if ret.Get(0) != nil { | |
| 120 | + | r0 = ret.Get(0).(*dto.ImagePruneResponse) | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, dto.ImagePruneRequest) error); ok { | |
| 124 | + | r1 = returnFunc(ctx, req) | |
| 125 | + | } else { | |
| 126 | + | r1 = ret.Error(1) | |
| 127 | + | } | |
| 128 | + | return r0, r1 | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // MockimagesClient_PruneImages_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PruneImages' | |
| 132 | + | type MockimagesClient_PruneImages_Call struct { | |
| 133 | + | *mock.Call | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | // PruneImages is a helper method to define mock.On call | |
| 137 | + | // - ctx context.Context | |
| 138 | + | // - req dto.ImagePruneRequest | |
| 139 | + | func (_e *MockimagesClient_Expecter) PruneImages(ctx interface{}, req interface{}) *MockimagesClient_PruneImages_Call { | |
| 140 | + | return &MockimagesClient_PruneImages_Call{Call: _e.mock.On("PruneImages", ctx, req)} | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | func (_c *MockimagesClient_PruneImages_Call) Run(run func(ctx context.Context, req dto.ImagePruneRequest)) *MockimagesClient_PruneImages_Call { | |
| 144 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 145 | + | var arg0 context.Context | |
| 146 | + | if args[0] != nil { | |
| 147 | + | arg0 = args[0].(context.Context) | |
| 148 | + | } | |
| 149 | + | var arg1 dto.ImagePruneRequest | |
| 150 | + | if args[1] != nil { | |
| 151 | + | arg1 = args[1].(dto.ImagePruneRequest) | |
| 152 | + | } | |
| 153 | + | run( | |
| 154 | + | arg0, | |
| 155 | + | arg1, | |
| 156 | + | ) | |
| 157 | + | }) | |
| 158 | + | return _c | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | func (_c *MockimagesClient_PruneImages_Call) Return(imagePruneResponse *dto.ImagePruneResponse, err error) *MockimagesClient_PruneImages_Call { | |
| 162 | + | _c.Call.Return(imagePruneResponse, err) | |
| 163 | + | return _c | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | func (_c *MockimagesClient_PruneImages_Call) RunAndReturn(run func(ctx context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error)) *MockimagesClient_PruneImages_Call { | |
| 167 | + | _c.Call.Return(run) | |
| 168 | + | return _c | |
| 169 | + | } | |
| @@ -0,0 +1,169 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package mocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "context" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/gordon/internal/adapters/dto" | |||
| 11 | + | mock "github.com/stretchr/testify/mock" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // NewMockimagesClient creates a new instance of MockimagesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 15 | + | // The first argument is typically a *testing.T value. | |||
| 16 | + | func NewMockimagesClient(t interface { | |||
| 17 | + | mock.TestingT | |||
| 18 | + | Cleanup(func()) | |||
| 19 | + | }) *MockimagesClient { | |||
| 20 | + | mock := &MockimagesClient{} | |||
| 21 | + | mock.Mock.Test(t) | |||
| 22 | + | ||||
| 23 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 24 | + | ||||
| 25 | + | return mock | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | // MockimagesClient is an autogenerated mock type for the imagesClient type | |||
| 29 | + | type MockimagesClient struct { | |||
| 30 | + | mock.Mock | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | type MockimagesClient_Expecter struct { | |||
| 34 | + | mock *mock.Mock | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | func (_m *MockimagesClient) EXPECT() *MockimagesClient_Expecter { | |||
| 38 | + | return &MockimagesClient_Expecter{mock: &_m.Mock} | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | // ListImages provides a mock function for the type MockimagesClient | |||
| 42 | + | func (_mock *MockimagesClient) ListImages(ctx context.Context) ([]dto.Image, error) { | |||
| 43 | + | ret := _mock.Called(ctx) | |||
| 44 | + | ||||
| 45 | + | if len(ret) == 0 { | |||
| 46 | + | panic("no return value specified for ListImages") | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | var r0 []dto.Image | |||
| 50 | + | var r1 error | |||
| 51 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]dto.Image, error)); ok { | |||
| 52 | + | return returnFunc(ctx) | |||
| 53 | + | } | |||
| 54 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []dto.Image); ok { | |||
| 55 | + | r0 = returnFunc(ctx) | |||
| 56 | + | } else { | |||
| 57 | + | if ret.Get(0) != nil { | |||
| 58 | + | r0 = ret.Get(0).([]dto.Image) | |||
| 59 | + | } | |||
| 60 | + | } | |||
| 61 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |||
| 62 | + | r1 = returnFunc(ctx) | |||
| 63 | + | } else { | |||
| 64 | + | r1 = ret.Error(1) | |||
| 65 | + | } | |||
| 66 | + | return r0, r1 | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // MockimagesClient_ListImages_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListImages' | |||
| 70 | + | type MockimagesClient_ListImages_Call struct { | |||
| 71 | + | *mock.Call | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | // ListImages is a helper method to define mock.On call | |||
| 75 | + | // - ctx context.Context | |||
| 76 | + | func (_e *MockimagesClient_Expecter) ListImages(ctx interface{}) *MockimagesClient_ListImages_Call { | |||
| 77 | + | return &MockimagesClient_ListImages_Call{Call: _e.mock.On("ListImages", ctx)} | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | func (_c *MockimagesClient_ListImages_Call) Run(run func(ctx context.Context)) *MockimagesClient_ListImages_Call { | |||
| 81 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 82 | + | var arg0 context.Context | |||
| 83 | + | if args[0] != nil { | |||
| 84 | + | arg0 = args[0].(context.Context) | |||
| 85 | + | } | |||
| 86 | + | run( | |||
| 87 | + | arg0, | |||
| 88 | + | ) | |||
| 89 | + | }) | |||
| 90 | + | return _c | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | func (_c *MockimagesClient_ListImages_Call) Return(images []dto.Image, err error) *MockimagesClient_ListImages_Call { | |||
| 94 | + | _c.Call.Return(images, err) | |||
| 95 | + | return _c | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | func (_c *MockimagesClient_ListImages_Call) RunAndReturn(run func(ctx context.Context) ([]dto.Image, error)) *MockimagesClient_ListImages_Call { | |||
| 99 | + | _c.Call.Return(run) | |||
| 100 | + | return _c | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // PruneImages provides a mock function for the type MockimagesClient | |||
| 104 | + | func (_mock *MockimagesClient) PruneImages(ctx context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | |||
| 105 | + | ret := _mock.Called(ctx, req) | |||
| 106 | + | ||||
| 107 | + | if len(ret) == 0 { | |||
| 108 | + | panic("no return value specified for PruneImages") | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | var r0 *dto.ImagePruneResponse | |||
| 112 | + | var r1 error | |||
| 113 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.ImagePruneRequest) (*dto.ImagePruneResponse, error)); ok { | |||
| 114 | + | return returnFunc(ctx, req) | |||
| 115 | + | } | |||
| 116 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.ImagePruneRequest) *dto.ImagePruneResponse); ok { | |||
| 117 | + | r0 = returnFunc(ctx, req) | |||
| 118 | + | } else { | |||
| 119 | + | if ret.Get(0) != nil { | |||
| 120 | + | r0 = ret.Get(0).(*dto.ImagePruneResponse) | |||
| 121 | + | } | |||
| 122 | + | } | |||
| 123 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, dto.ImagePruneRequest) error); ok { | |||
| 124 | + | r1 = returnFunc(ctx, req) | |||
| 125 | + | } else { | |||
| 126 | + | r1 = ret.Error(1) | |||
| 127 | + | } | |||
| 128 | + | return r0, r1 | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // MockimagesClient_PruneImages_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PruneImages' | |||
| 132 | + | type MockimagesClient_PruneImages_Call struct { | |||
| 133 | + | *mock.Call | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | // PruneImages is a helper method to define mock.On call | |||
| 137 | + | // - ctx context.Context | |||
| 138 | + | // - req dto.ImagePruneRequest | |||
| 139 | + | func (_e *MockimagesClient_Expecter) PruneImages(ctx interface{}, req interface{}) *MockimagesClient_PruneImages_Call { | |||
| 140 | + | return &MockimagesClient_PruneImages_Call{Call: _e.mock.On("PruneImages", ctx, req)} | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | func (_c *MockimagesClient_PruneImages_Call) Run(run func(ctx context.Context, req dto.ImagePruneRequest)) *MockimagesClient_PruneImages_Call { | |||
| 144 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 145 | + | var arg0 context.Context | |||
| 146 | + | if args[0] != nil { | |||
| 147 | + | arg0 = args[0].(context.Context) | |||
| 148 | + | } | |||
| 149 | + | var arg1 dto.ImagePruneRequest | |||
| 150 | + | if args[1] != nil { | |||
| 151 | + | arg1 = args[1].(dto.ImagePruneRequest) | |||
| 152 | + | } | |||
| 153 | + | run( | |||
| 154 | + | arg0, | |||
| 155 | + | arg1, | |||
| 156 | + | ) | |||
| 157 | + | }) | |||
| 158 | + | return _c | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | func (_c *MockimagesClient_PruneImages_Call) Return(imagePruneResponse *dto.ImagePruneResponse, err error) *MockimagesClient_PruneImages_Call { | |||
| 162 | + | _c.Call.Return(imagePruneResponse, err) | |||
| 163 | + | return _c | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | func (_c *MockimagesClient_PruneImages_Call) RunAndReturn(run func(ctx context.Context, req dto.ImagePruneRequest) (*dto.ImagePruneResponse, error)) *MockimagesClient_PruneImages_Call { | |||
| 167 | + | _c.Call.Return(run) | |||
| 168 | + | return _c | |||
| 169 | + | } | |||
A internal/adapters/in/cli/mocks/mock_volumes_client.go +169 -0
| @@ -0,0 +1,169 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package mocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/gordon/internal/adapters/dto" | |
| 11 | + | mock "github.com/stretchr/testify/mock" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // NewMockvolumesClient creates a new instance of MockvolumesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 15 | + | // The first argument is typically a *testing.T value. | |
| 16 | + | func NewMockvolumesClient(t interface { | |
| 17 | + | mock.TestingT | |
| 18 | + | Cleanup(func()) | |
| 19 | + | }) *MockvolumesClient { | |
| 20 | + | mock := &MockvolumesClient{} | |
| 21 | + | mock.Mock.Test(t) | |
| 22 | + | ||
| 23 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 24 | + | ||
| 25 | + | return mock | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // MockvolumesClient is an autogenerated mock type for the volumesClient type | |
| 29 | + | type MockvolumesClient struct { | |
| 30 | + | mock.Mock | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | type MockvolumesClient_Expecter struct { | |
| 34 | + | mock *mock.Mock | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | func (_m *MockvolumesClient) EXPECT() *MockvolumesClient_Expecter { | |
| 38 | + | return &MockvolumesClient_Expecter{mock: &_m.Mock} | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | // ListVolumes provides a mock function for the type MockvolumesClient | |
| 42 | + | func (_mock *MockvolumesClient) ListVolumes(ctx context.Context) ([]dto.Volume, error) { | |
| 43 | + | ret := _mock.Called(ctx) | |
| 44 | + | ||
| 45 | + | if len(ret) == 0 { | |
| 46 | + | panic("no return value specified for ListVolumes") | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | var r0 []dto.Volume | |
| 50 | + | var r1 error | |
| 51 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]dto.Volume, error)); ok { | |
| 52 | + | return returnFunc(ctx) | |
| 53 | + | } | |
| 54 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []dto.Volume); ok { | |
| 55 | + | r0 = returnFunc(ctx) | |
| 56 | + | } else { | |
| 57 | + | if ret.Get(0) != nil { | |
| 58 | + | r0 = ret.Get(0).([]dto.Volume) | |
| 59 | + | } | |
| 60 | + | } | |
| 61 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |
| 62 | + | r1 = returnFunc(ctx) | |
| 63 | + | } else { | |
| 64 | + | r1 = ret.Error(1) | |
| 65 | + | } | |
| 66 | + | return r0, r1 | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // MockvolumesClient_ListVolumes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVolumes' | |
| 70 | + | type MockvolumesClient_ListVolumes_Call struct { | |
| 71 | + | *mock.Call | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | // ListVolumes is a helper method to define mock.On call | |
| 75 | + | // - ctx context.Context | |
| 76 | + | func (_e *MockvolumesClient_Expecter) ListVolumes(ctx interface{}) *MockvolumesClient_ListVolumes_Call { | |
| 77 | + | return &MockvolumesClient_ListVolumes_Call{Call: _e.mock.On("ListVolumes", ctx)} | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | func (_c *MockvolumesClient_ListVolumes_Call) Run(run func(ctx context.Context)) *MockvolumesClient_ListVolumes_Call { | |
| 81 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 82 | + | var arg0 context.Context | |
| 83 | + | if args[0] != nil { | |
| 84 | + | arg0 = args[0].(context.Context) | |
| 85 | + | } | |
| 86 | + | run( | |
| 87 | + | arg0, | |
| 88 | + | ) | |
| 89 | + | }) | |
| 90 | + | return _c | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | func (_c *MockvolumesClient_ListVolumes_Call) Return(volumes []dto.Volume, err error) *MockvolumesClient_ListVolumes_Call { | |
| 94 | + | _c.Call.Return(volumes, err) | |
| 95 | + | return _c | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | func (_c *MockvolumesClient_ListVolumes_Call) RunAndReturn(run func(ctx context.Context) ([]dto.Volume, error)) *MockvolumesClient_ListVolumes_Call { | |
| 99 | + | _c.Call.Return(run) | |
| 100 | + | return _c | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // PruneVolumes provides a mock function for the type MockvolumesClient | |
| 104 | + | func (_mock *MockvolumesClient) PruneVolumes(ctx context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |
| 105 | + | ret := _mock.Called(ctx, req) | |
| 106 | + | ||
| 107 | + | if len(ret) == 0 { | |
| 108 | + | panic("no return value specified for PruneVolumes") | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | var r0 *dto.VolumePruneResponse | |
| 112 | + | var r1 error | |
| 113 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error)); ok { | |
| 114 | + | return returnFunc(ctx, req) | |
| 115 | + | } | |
| 116 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.VolumePruneRequest) *dto.VolumePruneResponse); ok { | |
| 117 | + | r0 = returnFunc(ctx, req) | |
| 118 | + | } else { | |
| 119 | + | if ret.Get(0) != nil { | |
| 120 | + | r0 = ret.Get(0).(*dto.VolumePruneResponse) | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, dto.VolumePruneRequest) error); ok { | |
| 124 | + | r1 = returnFunc(ctx, req) | |
| 125 | + | } else { | |
| 126 | + | r1 = ret.Error(1) | |
| 127 | + | } | |
| 128 | + | return r0, r1 | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // MockvolumesClient_PruneVolumes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PruneVolumes' | |
| 132 | + | type MockvolumesClient_PruneVolumes_Call struct { | |
| 133 | + | *mock.Call | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | // PruneVolumes is a helper method to define mock.On call | |
| 137 | + | // - ctx context.Context | |
| 138 | + | // - req dto.VolumePruneRequest | |
| 139 | + | func (_e *MockvolumesClient_Expecter) PruneVolumes(ctx interface{}, req interface{}) *MockvolumesClient_PruneVolumes_Call { | |
| 140 | + | return &MockvolumesClient_PruneVolumes_Call{Call: _e.mock.On("PruneVolumes", ctx, req)} | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | func (_c *MockvolumesClient_PruneVolumes_Call) Run(run func(ctx context.Context, req dto.VolumePruneRequest)) *MockvolumesClient_PruneVolumes_Call { | |
| 144 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 145 | + | var arg0 context.Context | |
| 146 | + | if args[0] != nil { | |
| 147 | + | arg0 = args[0].(context.Context) | |
| 148 | + | } | |
| 149 | + | var arg1 dto.VolumePruneRequest | |
| 150 | + | if args[1] != nil { | |
| 151 | + | arg1 = args[1].(dto.VolumePruneRequest) | |
| 152 | + | } | |
| 153 | + | run( | |
| 154 | + | arg0, | |
| 155 | + | arg1, | |
| 156 | + | ) | |
| 157 | + | }) | |
| 158 | + | return _c | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | func (_c *MockvolumesClient_PruneVolumes_Call) Return(volumePruneResponse *dto.VolumePruneResponse, err error) *MockvolumesClient_PruneVolumes_Call { | |
| 162 | + | _c.Call.Return(volumePruneResponse, err) | |
| 163 | + | return _c | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | func (_c *MockvolumesClient_PruneVolumes_Call) RunAndReturn(run func(ctx context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error)) *MockvolumesClient_PruneVolumes_Call { | |
| 167 | + | _c.Call.Return(run) | |
| 168 | + | return _c | |
| 169 | + | } | |
| @@ -0,0 +1,169 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package mocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "context" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/gordon/internal/adapters/dto" | |||
| 11 | + | mock "github.com/stretchr/testify/mock" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // NewMockvolumesClient creates a new instance of MockvolumesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 15 | + | // The first argument is typically a *testing.T value. | |||
| 16 | + | func NewMockvolumesClient(t interface { | |||
| 17 | + | mock.TestingT | |||
| 18 | + | Cleanup(func()) | |||
| 19 | + | }) *MockvolumesClient { | |||
| 20 | + | mock := &MockvolumesClient{} | |||
| 21 | + | mock.Mock.Test(t) | |||
| 22 | + | ||||
| 23 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 24 | + | ||||
| 25 | + | return mock | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | // MockvolumesClient is an autogenerated mock type for the volumesClient type | |||
| 29 | + | type MockvolumesClient struct { | |||
| 30 | + | mock.Mock | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | type MockvolumesClient_Expecter struct { | |||
| 34 | + | mock *mock.Mock | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | func (_m *MockvolumesClient) EXPECT() *MockvolumesClient_Expecter { | |||
| 38 | + | return &MockvolumesClient_Expecter{mock: &_m.Mock} | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | // ListVolumes provides a mock function for the type MockvolumesClient | |||
| 42 | + | func (_mock *MockvolumesClient) ListVolumes(ctx context.Context) ([]dto.Volume, error) { | |||
| 43 | + | ret := _mock.Called(ctx) | |||
| 44 | + | ||||
| 45 | + | if len(ret) == 0 { | |||
| 46 | + | panic("no return value specified for ListVolumes") | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | var r0 []dto.Volume | |||
| 50 | + | var r1 error | |||
| 51 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]dto.Volume, error)); ok { | |||
| 52 | + | return returnFunc(ctx) | |||
| 53 | + | } | |||
| 54 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []dto.Volume); ok { | |||
| 55 | + | r0 = returnFunc(ctx) | |||
| 56 | + | } else { | |||
| 57 | + | if ret.Get(0) != nil { | |||
| 58 | + | r0 = ret.Get(0).([]dto.Volume) | |||
| 59 | + | } | |||
| 60 | + | } | |||
| 61 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |||
| 62 | + | r1 = returnFunc(ctx) | |||
| 63 | + | } else { | |||
| 64 | + | r1 = ret.Error(1) | |||
| 65 | + | } | |||
| 66 | + | return r0, r1 | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // MockvolumesClient_ListVolumes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVolumes' | |||
| 70 | + | type MockvolumesClient_ListVolumes_Call struct { | |||
| 71 | + | *mock.Call | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | // ListVolumes is a helper method to define mock.On call | |||
| 75 | + | // - ctx context.Context | |||
| 76 | + | func (_e *MockvolumesClient_Expecter) ListVolumes(ctx interface{}) *MockvolumesClient_ListVolumes_Call { | |||
| 77 | + | return &MockvolumesClient_ListVolumes_Call{Call: _e.mock.On("ListVolumes", ctx)} | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | func (_c *MockvolumesClient_ListVolumes_Call) Run(run func(ctx context.Context)) *MockvolumesClient_ListVolumes_Call { | |||
| 81 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 82 | + | var arg0 context.Context | |||
| 83 | + | if args[0] != nil { | |||
| 84 | + | arg0 = args[0].(context.Context) | |||
| 85 | + | } | |||
| 86 | + | run( | |||
| 87 | + | arg0, | |||
| 88 | + | ) | |||
| 89 | + | }) | |||
| 90 | + | return _c | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | func (_c *MockvolumesClient_ListVolumes_Call) Return(volumes []dto.Volume, err error) *MockvolumesClient_ListVolumes_Call { | |||
| 94 | + | _c.Call.Return(volumes, err) | |||
| 95 | + | return _c | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | func (_c *MockvolumesClient_ListVolumes_Call) RunAndReturn(run func(ctx context.Context) ([]dto.Volume, error)) *MockvolumesClient_ListVolumes_Call { | |||
| 99 | + | _c.Call.Return(run) | |||
| 100 | + | return _c | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // PruneVolumes provides a mock function for the type MockvolumesClient | |||
| 104 | + | func (_mock *MockvolumesClient) PruneVolumes(ctx context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |||
| 105 | + | ret := _mock.Called(ctx, req) | |||
| 106 | + | ||||
| 107 | + | if len(ret) == 0 { | |||
| 108 | + | panic("no return value specified for PruneVolumes") | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | var r0 *dto.VolumePruneResponse | |||
| 112 | + | var r1 error | |||
| 113 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error)); ok { | |||
| 114 | + | return returnFunc(ctx, req) | |||
| 115 | + | } | |||
| 116 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, dto.VolumePruneRequest) *dto.VolumePruneResponse); ok { | |||
| 117 | + | r0 = returnFunc(ctx, req) | |||
| 118 | + | } else { | |||
| 119 | + | if ret.Get(0) != nil { | |||
| 120 | + | r0 = ret.Get(0).(*dto.VolumePruneResponse) | |||
| 121 | + | } | |||
| 122 | + | } | |||
| 123 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, dto.VolumePruneRequest) error); ok { | |||
| 124 | + | r1 = returnFunc(ctx, req) | |||
| 125 | + | } else { | |||
| 126 | + | r1 = ret.Error(1) | |||
| 127 | + | } | |||
| 128 | + | return r0, r1 | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // MockvolumesClient_PruneVolumes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PruneVolumes' | |||
| 132 | + | type MockvolumesClient_PruneVolumes_Call struct { | |||
| 133 | + | *mock.Call | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | // PruneVolumes is a helper method to define mock.On call | |||
| 137 | + | // - ctx context.Context | |||
| 138 | + | // - req dto.VolumePruneRequest | |||
| 139 | + | func (_e *MockvolumesClient_Expecter) PruneVolumes(ctx interface{}, req interface{}) *MockvolumesClient_PruneVolumes_Call { | |||
| 140 | + | return &MockvolumesClient_PruneVolumes_Call{Call: _e.mock.On("PruneVolumes", ctx, req)} | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | func (_c *MockvolumesClient_PruneVolumes_Call) Run(run func(ctx context.Context, req dto.VolumePruneRequest)) *MockvolumesClient_PruneVolumes_Call { | |||
| 144 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 145 | + | var arg0 context.Context | |||
| 146 | + | if args[0] != nil { | |||
| 147 | + | arg0 = args[0].(context.Context) | |||
| 148 | + | } | |||
| 149 | + | var arg1 dto.VolumePruneRequest | |||
| 150 | + | if args[1] != nil { | |||
| 151 | + | arg1 = args[1].(dto.VolumePruneRequest) | |||
| 152 | + | } | |||
| 153 | + | run( | |||
| 154 | + | arg0, | |||
| 155 | + | arg1, | |||
| 156 | + | ) | |||
| 157 | + | }) | |||
| 158 | + | return _c | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | func (_c *MockvolumesClient_PruneVolumes_Call) Return(volumePruneResponse *dto.VolumePruneResponse, err error) *MockvolumesClient_PruneVolumes_Call { | |||
| 162 | + | _c.Call.Return(volumePruneResponse, err) | |||
| 163 | + | return _c | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | func (_c *MockvolumesClient_PruneVolumes_Call) RunAndReturn(run func(ctx context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error)) *MockvolumesClient_PruneVolumes_Call { | |||
| 167 | + | _c.Call.Return(run) | |||
| 168 | + | return _c | |||
| 169 | + | } | |||
M internal/adapters/in/cli/push_image_ops.go +1 -2
| @@ -123,8 +123,7 @@ func isAuthError(err error) bool { | |||
| 123 | 123 | if err == nil { | |
| 124 | 124 | return false | |
| 125 | 125 | } | |
| 126 | - | var httpErr *remote.HTTPError | |
| 127 | - | if errors.As(err, &httpErr) { | |
| 126 | + | if httpErr, ok := errors.AsType[*remote.HTTPError](err); ok { | |
| 128 | 127 | return httpErr.StatusCode == http.StatusUnauthorized | |
| 129 | 128 | } | |
| 130 | 129 | msg := err.Error() | |
| @@ -123,8 +123,7 @@ func isAuthError(err error) bool { | |||||
| 123 | if err == nil { | 123 | if err == nil { | ||
| 124 | return false | 124 | return false | ||
| 125 | } | 125 | } | ||
| 126 | - | var httpErr *remote.HTTPError | 126 | + | if httpErr, ok := errors.AsType[*remote.HTTPError](err); ok { |
| 127 | - | if errors.As(err, &httpErr) { | |||
| 128 | return httpErr.StatusCode == http.StatusUnauthorized | 127 | return httpErr.StatusCode == http.StatusUnauthorized | ||
| 129 | } | 128 | } | ||
| 130 | msg := err.Error() | 129 | msg := err.Error() | ||
M internal/adapters/in/cli/push_test.go +24 -241
| @@ -11,193 +11,13 @@ import ( | |||
| 11 | 11 | "strings" | |
| 12 | 12 | "testing" | |
| 13 | 13 | ||
| 14 | - | "github.com/bnema/gordon/internal/adapters/dto" | |
| 14 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 15 | 15 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | |
| 16 | 16 | "github.com/bnema/gordon/internal/domain" | |
| 17 | 17 | "github.com/stretchr/testify/assert" | |
| 18 | + | "github.com/stretchr/testify/mock" | |
| 18 | 19 | ) | |
| 19 | 20 | ||
| 20 | - | type resolveFromImageTestControlPlane struct { | |
| 21 | - | getRoute func(context.Context, string) (*domain.Route, error) | |
| 22 | - | findRoutesByImage func(context.Context, string) ([]domain.Route, error) | |
| 23 | - | } | |
| 24 | - | ||
| 25 | - | var _ ControlPlane = (*resolveFromImageTestControlPlane)(nil) | |
| 26 | - | ||
| 27 | - | func (c *resolveFromImageTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |
| 28 | - | panic("unexpected call") | |
| 29 | - | } | |
| 30 | - | ||
| 31 | - | func (c *resolveFromImageTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 32 | - | panic("unexpected call") | |
| 33 | - | } | |
| 34 | - | ||
| 35 | - | func (c *resolveFromImageTestControlPlane) GetRoute(ctx context.Context, domainName string) (*domain.Route, error) { | |
| 36 | - | if c.getRoute != nil { | |
| 37 | - | return c.getRoute(ctx, domainName) | |
| 38 | - | } | |
| 39 | - | panic("unexpected call") | |
| 40 | - | } | |
| 41 | - | ||
| 42 | - | func (c *resolveFromImageTestControlPlane) FindRoutesByImage(ctx context.Context, imageName string) ([]domain.Route, error) { | |
| 43 | - | if c.findRoutesByImage != nil { | |
| 44 | - | return c.findRoutesByImage(ctx, imageName) | |
| 45 | - | } | |
| 46 | - | return nil, nil | |
| 47 | - | } | |
| 48 | - | ||
| 49 | - | func (c *resolveFromImageTestControlPlane) AddRoute(context.Context, domain.Route) error { | |
| 50 | - | panic("unexpected call") | |
| 51 | - | } | |
| 52 | - | ||
| 53 | - | func (c *resolveFromImageTestControlPlane) UpdateRoute(context.Context, domain.Route) error { | |
| 54 | - | panic("unexpected call") | |
| 55 | - | } | |
| 56 | - | ||
| 57 | - | func (c *resolveFromImageTestControlPlane) RemoveRoute(context.Context, string) error { | |
| 58 | - | panic("unexpected call") | |
| 59 | - | } | |
| 60 | - | ||
| 61 | - | func (c *resolveFromImageTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) { | |
| 62 | - | panic("unexpected call") | |
| 63 | - | } | |
| 64 | - | ||
| 65 | - | func (c *resolveFromImageTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) { | |
| 66 | - | panic("unexpected call") | |
| 67 | - | } | |
| 68 | - | ||
| 69 | - | func (c *resolveFromImageTestControlPlane) SetSecrets(context.Context, string, map[string]string) error { | |
| 70 | - | panic("unexpected call") | |
| 71 | - | } | |
| 72 | - | ||
| 73 | - | func (c *resolveFromImageTestControlPlane) DeleteSecret(context.Context, string, string) error { | |
| 74 | - | panic("unexpected call") | |
| 75 | - | } | |
| 76 | - | ||
| 77 | - | func (c *resolveFromImageTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error { | |
| 78 | - | panic("unexpected call") | |
| 79 | - | } | |
| 80 | - | ||
| 81 | - | func (c *resolveFromImageTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error { | |
| 82 | - | panic("unexpected call") | |
| 83 | - | } | |
| 84 | - | ||
| 85 | - | func (c *resolveFromImageTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) { | |
| 86 | - | panic("unexpected call") | |
| 87 | - | } | |
| 88 | - | ||
| 89 | - | func (c *resolveFromImageTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) { | |
| 90 | - | panic("unexpected call") | |
| 91 | - | } | |
| 92 | - | ||
| 93 | - | func (c *resolveFromImageTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |
| 94 | - | panic("unexpected call") | |
| 95 | - | } | |
| 96 | - | ||
| 97 | - | func (c *resolveFromImageTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |
| 98 | - | panic("unexpected call") | |
| 99 | - | } | |
| 100 | - | ||
| 101 | - | func (c *resolveFromImageTestControlPlane) FindAttachmentTargetsByImage(context.Context, string) ([]string, error) { | |
| 102 | - | panic("unexpected call") | |
| 103 | - | } | |
| 104 | - | ||
| 105 | - | func (c *resolveFromImageTestControlPlane) AddAttachment(context.Context, string, string) error { | |
| 106 | - | panic("unexpected call") | |
| 107 | - | } | |
| 108 | - | ||
| 109 | - | func (c *resolveFromImageTestControlPlane) RemoveAttachment(context.Context, string, string) error { | |
| 110 | - | panic("unexpected call") | |
| 111 | - | } | |
| 112 | - | ||
| 113 | - | func (c *resolveFromImageTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) { | |
| 114 | - | panic("unexpected call") | |
| 115 | - | } | |
| 116 | - | ||
| 117 | - | func (c *resolveFromImageTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error { | |
| 118 | - | panic("unexpected call") | |
| 119 | - | } | |
| 120 | - | ||
| 121 | - | func (c *resolveFromImageTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error { | |
| 122 | - | panic("unexpected call") | |
| 123 | - | } | |
| 124 | - | ||
| 125 | - | func (c *resolveFromImageTestControlPlane) GetTLSStatus(context.Context) (*dto.TLSStatusResponse, error) { | |
| 126 | - | return &dto.TLSStatusResponse{}, nil | |
| 127 | - | } | |
| 128 | - | ||
| 129 | - | func (c *resolveFromImageTestControlPlane) GetStatus(context.Context) (*remote.Status, error) { | |
| 130 | - | panic("unexpected call") | |
| 131 | - | } | |
| 132 | - | ||
| 133 | - | func (c *resolveFromImageTestControlPlane) Reload(context.Context) error { | |
| 134 | - | panic("unexpected call") | |
| 135 | - | } | |
| 136 | - | ||
| 137 | - | func (c *resolveFromImageTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) { | |
| 138 | - | panic("unexpected call") | |
| 139 | - | } | |
| 140 | - | ||
| 141 | - | func (c *resolveFromImageTestControlPlane) GetConfig(context.Context) (*remote.Config, error) { | |
| 142 | - | panic("unexpected call") | |
| 143 | - | } | |
| 144 | - | ||
| 145 | - | func (c *resolveFromImageTestControlPlane) DeployIntent(context.Context, string) error { | |
| 146 | - | panic("unexpected call") | |
| 147 | - | } | |
| 148 | - | ||
| 149 | - | func (c *resolveFromImageTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) { | |
| 150 | - | panic("unexpected call") | |
| 151 | - | } | |
| 152 | - | ||
| 153 | - | func (c *resolveFromImageTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) { | |
| 154 | - | panic("unexpected call") | |
| 155 | - | } | |
| 156 | - | ||
| 157 | - | func (c *resolveFromImageTestControlPlane) ListTags(context.Context, string) ([]string, error) { | |
| 158 | - | panic("unexpected call") | |
| 159 | - | } | |
| 160 | - | ||
| 161 | - | func (c *resolveFromImageTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) { | |
| 162 | - | panic("unexpected call") | |
| 163 | - | } | |
| 164 | - | ||
| 165 | - | func (c *resolveFromImageTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) { | |
| 166 | - | panic("unexpected call") | |
| 167 | - | } | |
| 168 | - | ||
| 169 | - | func (c *resolveFromImageTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) { | |
| 170 | - | panic("unexpected call") | |
| 171 | - | } | |
| 172 | - | ||
| 173 | - | func (c *resolveFromImageTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) { | |
| 174 | - | panic("unexpected call") | |
| 175 | - | } | |
| 176 | - | ||
| 177 | - | func (c *resolveFromImageTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) { | |
| 178 | - | panic("unexpected call") | |
| 179 | - | } | |
| 180 | - | ||
| 181 | - | func (c *resolveFromImageTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) { | |
| 182 | - | panic("unexpected call") | |
| 183 | - | } | |
| 184 | - | ||
| 185 | - | func (c *resolveFromImageTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) { | |
| 186 | - | panic("unexpected call") | |
| 187 | - | } | |
| 188 | - | ||
| 189 | - | func (c *resolveFromImageTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) { | |
| 190 | - | panic("unexpected call") | |
| 191 | - | } | |
| 192 | - | ||
| 193 | - | func (c *resolveFromImageTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) { | |
| 194 | - | panic("unexpected call") | |
| 195 | - | } | |
| 196 | - | ||
| 197 | - | func (c *resolveFromImageTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |
| 198 | - | panic("unexpected call") | |
| 199 | - | } | |
| 200 | - | ||
| 201 | 21 | func TestValidateBuildArg(t *testing.T) { | |
| 202 | 22 | tests := []struct { | |
| 203 | 23 | name string | |
| @@ -631,13 +451,10 @@ func TestParseLabelPair(t *testing.T) { | |||
| 631 | 451 | } | |
| 632 | 452 | ||
| 633 | 453 | func TestResolveFromImage_NoRouteSuggestsBootstrap(t *testing.T) { | |
| 634 | - | cp := &resolveFromImageTestControlPlane{ | |
| 635 | - | findRoutesByImage: func(context.Context, string) ([]domain.Route, error) { | |
| 636 | - | return nil, nil | |
| 637 | - | }, | |
| 638 | - | } | |
| 454 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 455 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "myapp").Return(nil, nil).Once() | |
| 639 | 456 | ||
| 640 | - | _, _, _, err := resolveFromImage(context.Background(), cp, "myapp", "Dockerfile") | |
| 457 | + | _, _, _, err := resolveFromImage(context.Background(), cpMock, "myapp", "Dockerfile") | |
| 641 | 458 | ||
| 642 | 459 | assert.Error(t, err) | |
| 643 | 460 | assert.Contains(t, err.Error(), `no route configured for image "myapp"`) | |
| @@ -645,18 +462,10 @@ func TestResolveFromImage_NoRouteSuggestsBootstrap(t *testing.T) { | |||
| 645 | 462 | } | |
| 646 | 463 | ||
| 647 | 464 | func TestResolveRoute_DottedBareImageUsesImageLookup(t *testing.T) { | |
| 648 | - | cp := &resolveFromImageTestControlPlane{ | |
| 649 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | |
| 650 | - | assert.Equal(t, "my.app", imageName) | |
| 651 | - | return []domain.Route{{Domain: "app.example.com", Image: "registry.example.com/my.app:latest"}}, nil | |
| 652 | - | }, | |
| 653 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 654 | - | t.Fatalf("unexpected GetRoute call") | |
| 655 | - | return nil, nil | |
| 656 | - | }, | |
| 657 | - | } | |
| 465 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 466 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "my.app").Return([]domain.Route{{Domain: "app.example.com", Image: "registry.example.com/my.app:latest"}}, nil).Once() | |
| 658 | 467 | ||
| 659 | - | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cp, "my.app", "", "Dockerfile") | |
| 468 | + | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cpMock, "my.app", "", "Dockerfile") | |
| 660 | 469 | ||
| 661 | 470 | assert.NoError(t, err) | |
| 662 | 471 | assert.Equal(t, "registry.example.com", registry) | |
| @@ -665,21 +474,12 @@ func TestResolveRoute_DottedBareImageUsesImageLookup(t *testing.T) { | |||
| 665 | 474 | } | |
| 666 | 475 | ||
| 667 | 476 | func TestResolveRoute_DottedBareImageNoRoutesKeepsBootstrapError(t *testing.T) { | |
| 668 | - | sawImageLookup := false | |
| 669 | - | cp := &resolveFromImageTestControlPlane{ | |
| 670 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | |
| 671 | - | sawImageLookup = true | |
| 672 | - | assert.Equal(t, "my.app", imageName) | |
| 673 | - | return nil, nil | |
| 674 | - | }, | |
| 675 | - | getRoute: func(_ context.Context, domainName string) (*domain.Route, error) { | |
| 676 | - | assert.True(t, sawImageLookup, "expected image lookup before legacy domain lookup") | |
| 677 | - | assert.Equal(t, "my.app", domainName) | |
| 678 | - | return nil, domain.ErrRouteNotFound | |
| 679 | - | }, | |
| 680 | - | } | |
| 477 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 478 | + | imageLookup := cpMock.EXPECT().FindRoutesByImage(context.Background(), "my.app").Return(nil, nil).Once() | |
| 479 | + | routeLookup := cpMock.EXPECT().GetRoute(context.Background(), "my.app").Return(nil, domain.ErrRouteNotFound).Once() | |
| 480 | + | mock.InOrder(imageLookup, routeLookup) | |
| 681 | 481 | ||
| 682 | - | _, _, _, err := resolveRoute(context.Background(), cp, "my.app", "", "Dockerfile") | |
| 482 | + | _, _, _, err := resolveRoute(context.Background(), cpMock, "my.app", "", "Dockerfile") | |
| 683 | 483 | ||
| 684 | 484 | assert.Error(t, err) | |
| 685 | 485 | assert.True(t, errors.Is(err, domain.ErrNoRouteForImage)) | |
| @@ -696,21 +496,12 @@ func TestNoRouteForImageErrorWrapsSentinel(t *testing.T) { | |||
| 696 | 496 | } | |
| 697 | 497 | ||
| 698 | 498 | func TestResolveRoute_DottedBareDomainFallsBackToLegacyLookup(t *testing.T) { | |
| 699 | - | sawImageLookup := false | |
| 700 | - | cp := &resolveFromImageTestControlPlane{ | |
| 701 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | |
| 702 | - | sawImageLookup = true | |
| 703 | - | assert.Equal(t, "app.example.com", imageName) | |
| 704 | - | return nil, nil | |
| 705 | - | }, | |
| 706 | - | getRoute: func(_ context.Context, domainName string) (*domain.Route, error) { | |
| 707 | - | assert.True(t, sawImageLookup, "expected image lookup before legacy domain lookup") | |
| 708 | - | assert.Equal(t, "app.example.com", domainName) | |
| 709 | - | return &domain.Route{Domain: domainName, Image: "registry.example.com/myapp:latest"}, nil | |
| 710 | - | }, | |
| 711 | - | } | |
| 499 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 500 | + | imageLookup := cpMock.EXPECT().FindRoutesByImage(context.Background(), "app.example.com").Return(nil, nil).Once() | |
| 501 | + | routeLookup := cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}, nil).Once() | |
| 502 | + | mock.InOrder(imageLookup, routeLookup) | |
| 712 | 503 | ||
| 713 | - | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cp, "app.example.com", "", "Dockerfile") | |
| 504 | + | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cpMock, "app.example.com", "", "Dockerfile") | |
| 714 | 505 | ||
| 715 | 506 | assert.NoError(t, err) | |
| 716 | 507 | assert.Equal(t, "registry.example.com", registry) | |
| @@ -719,14 +510,10 @@ func TestResolveRoute_DottedBareDomainFallsBackToLegacyLookup(t *testing.T) { | |||
| 719 | 510 | } | |
| 720 | 511 | ||
| 721 | 512 | func TestResolveRoute_TaggedImageStripsTagBeforeLookup(t *testing.T) { | |
| 722 | - | cp := &resolveFromImageTestControlPlane{ | |
| 723 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | |
| 724 | - | assert.Equal(t, "myapp", imageName) | |
| 725 | - | return []domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil | |
| 726 | - | }, | |
| 727 | - | } | |
| 513 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 514 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "myapp").Return([]domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil).Once() | |
| 728 | 515 | ||
| 729 | - | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cp, "myapp:v1.2.3", "", "Dockerfile") | |
| 516 | + | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cpMock, "myapp:v1.2.3", "", "Dockerfile") | |
| 730 | 517 | ||
| 731 | 518 | assert.NoError(t, err) | |
| 732 | 519 | assert.Equal(t, "registry.example.com", registry) | |
| @@ -735,14 +522,10 @@ func TestResolveRoute_TaggedImageStripsTagBeforeLookup(t *testing.T) { | |||
| 735 | 522 | } | |
| 736 | 523 | ||
| 737 | 524 | func TestResolveRoute_RegistryQualifiedTaggedImageUsesImageLookup(t *testing.T) { | |
| 738 | - | cp := &resolveFromImageTestControlPlane{ | |
| 739 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | |
| 740 | - | assert.Equal(t, "registry.example.com/myapp", imageName) | |
| 741 | - | return []domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil | |
| 742 | - | }, | |
| 743 | - | } | |
| 525 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 526 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "registry.example.com/myapp").Return([]domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil).Once() | |
| 744 | 527 | ||
| 745 | - | _, _, _, err := resolveRoute(context.Background(), cp, "registry.example.com/myapp:v1.2.3", "", "Dockerfile") | |
| 528 | + | _, _, _, err := resolveRoute(context.Background(), cpMock, "registry.example.com/myapp:v1.2.3", "", "Dockerfile") | |
| 746 | 529 | ||
| 747 | 530 | assert.NoError(t, err) | |
| 748 | 531 | } | |
| @@ -11,193 +11,13 @@ import ( | |||||
| 11 | "strings" | 11 | "strings" | ||
| 12 | "testing" | 12 | "testing" | ||
| 13 | 13 | ||||
| 14 | - | "github.com/bnema/gordon/internal/adapters/dto" | 14 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" |
| 15 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | 15 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | ||
| 16 | "github.com/bnema/gordon/internal/domain" | 16 | "github.com/bnema/gordon/internal/domain" | ||
| 17 | "github.com/stretchr/testify/assert" | 17 | "github.com/stretchr/testify/assert" | ||
| 18 | + | "github.com/stretchr/testify/mock" | |||
| 18 | ) | 19 | ) | ||
| 19 | 20 | ||||
| 20 | - | type resolveFromImageTestControlPlane struct { | |||
| 21 | - | getRoute func(context.Context, string) (*domain.Route, error) | |||
| 22 | - | findRoutesByImage func(context.Context, string) ([]domain.Route, error) | |||
| 23 | - | } | |||
| 24 | - | ||||
| 25 | - | var _ ControlPlane = (*resolveFromImageTestControlPlane)(nil) | |||
| 26 | - | ||||
| 27 | - | func (c *resolveFromImageTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |||
| 28 | - | panic("unexpected call") | |||
| 29 | - | } | |||
| 30 | - | ||||
| 31 | - | func (c *resolveFromImageTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 32 | - | panic("unexpected call") | |||
| 33 | - | } | |||
| 34 | - | ||||
| 35 | - | func (c *resolveFromImageTestControlPlane) GetRoute(ctx context.Context, domainName string) (*domain.Route, error) { | |||
| 36 | - | if c.getRoute != nil { | |||
| 37 | - | return c.getRoute(ctx, domainName) | |||
| 38 | - | } | |||
| 39 | - | panic("unexpected call") | |||
| 40 | - | } | |||
| 41 | - | ||||
| 42 | - | func (c *resolveFromImageTestControlPlane) FindRoutesByImage(ctx context.Context, imageName string) ([]domain.Route, error) { | |||
| 43 | - | if c.findRoutesByImage != nil { | |||
| 44 | - | return c.findRoutesByImage(ctx, imageName) | |||
| 45 | - | } | |||
| 46 | - | return nil, nil | |||
| 47 | - | } | |||
| 48 | - | ||||
| 49 | - | func (c *resolveFromImageTestControlPlane) AddRoute(context.Context, domain.Route) error { | |||
| 50 | - | panic("unexpected call") | |||
| 51 | - | } | |||
| 52 | - | ||||
| 53 | - | func (c *resolveFromImageTestControlPlane) UpdateRoute(context.Context, domain.Route) error { | |||
| 54 | - | panic("unexpected call") | |||
| 55 | - | } | |||
| 56 | - | ||||
| 57 | - | func (c *resolveFromImageTestControlPlane) RemoveRoute(context.Context, string) error { | |||
| 58 | - | panic("unexpected call") | |||
| 59 | - | } | |||
| 60 | - | ||||
| 61 | - | func (c *resolveFromImageTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) { | |||
| 62 | - | panic("unexpected call") | |||
| 63 | - | } | |||
| 64 | - | ||||
| 65 | - | func (c *resolveFromImageTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) { | |||
| 66 | - | panic("unexpected call") | |||
| 67 | - | } | |||
| 68 | - | ||||
| 69 | - | func (c *resolveFromImageTestControlPlane) SetSecrets(context.Context, string, map[string]string) error { | |||
| 70 | - | panic("unexpected call") | |||
| 71 | - | } | |||
| 72 | - | ||||
| 73 | - | func (c *resolveFromImageTestControlPlane) DeleteSecret(context.Context, string, string) error { | |||
| 74 | - | panic("unexpected call") | |||
| 75 | - | } | |||
| 76 | - | ||||
| 77 | - | func (c *resolveFromImageTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error { | |||
| 78 | - | panic("unexpected call") | |||
| 79 | - | } | |||
| 80 | - | ||||
| 81 | - | func (c *resolveFromImageTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error { | |||
| 82 | - | panic("unexpected call") | |||
| 83 | - | } | |||
| 84 | - | ||||
| 85 | - | func (c *resolveFromImageTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) { | |||
| 86 | - | panic("unexpected call") | |||
| 87 | - | } | |||
| 88 | - | ||||
| 89 | - | func (c *resolveFromImageTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) { | |||
| 90 | - | panic("unexpected call") | |||
| 91 | - | } | |||
| 92 | - | ||||
| 93 | - | func (c *resolveFromImageTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |||
| 94 | - | panic("unexpected call") | |||
| 95 | - | } | |||
| 96 | - | ||||
| 97 | - | func (c *resolveFromImageTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |||
| 98 | - | panic("unexpected call") | |||
| 99 | - | } | |||
| 100 | - | ||||
| 101 | - | func (c *resolveFromImageTestControlPlane) FindAttachmentTargetsByImage(context.Context, string) ([]string, error) { | |||
| 102 | - | panic("unexpected call") | |||
| 103 | - | } | |||
| 104 | - | ||||
| 105 | - | func (c *resolveFromImageTestControlPlane) AddAttachment(context.Context, string, string) error { | |||
| 106 | - | panic("unexpected call") | |||
| 107 | - | } | |||
| 108 | - | ||||
| 109 | - | func (c *resolveFromImageTestControlPlane) RemoveAttachment(context.Context, string, string) error { | |||
| 110 | - | panic("unexpected call") | |||
| 111 | - | } | |||
| 112 | - | ||||
| 113 | - | func (c *resolveFromImageTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) { | |||
| 114 | - | panic("unexpected call") | |||
| 115 | - | } | |||
| 116 | - | ||||
| 117 | - | func (c *resolveFromImageTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error { | |||
| 118 | - | panic("unexpected call") | |||
| 119 | - | } | |||
| 120 | - | ||||
| 121 | - | func (c *resolveFromImageTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error { | |||
| 122 | - | panic("unexpected call") | |||
| 123 | - | } | |||
| 124 | - | ||||
| 125 | - | func (c *resolveFromImageTestControlPlane) GetTLSStatus(context.Context) (*dto.TLSStatusResponse, error) { | |||
| 126 | - | return &dto.TLSStatusResponse{}, nil | |||
| 127 | - | } | |||
| 128 | - | ||||
| 129 | - | func (c *resolveFromImageTestControlPlane) GetStatus(context.Context) (*remote.Status, error) { | |||
| 130 | - | panic("unexpected call") | |||
| 131 | - | } | |||
| 132 | - | ||||
| 133 | - | func (c *resolveFromImageTestControlPlane) Reload(context.Context) error { | |||
| 134 | - | panic("unexpected call") | |||
| 135 | - | } | |||
| 136 | - | ||||
| 137 | - | func (c *resolveFromImageTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) { | |||
| 138 | - | panic("unexpected call") | |||
| 139 | - | } | |||
| 140 | - | ||||
| 141 | - | func (c *resolveFromImageTestControlPlane) GetConfig(context.Context) (*remote.Config, error) { | |||
| 142 | - | panic("unexpected call") | |||
| 143 | - | } | |||
| 144 | - | ||||
| 145 | - | func (c *resolveFromImageTestControlPlane) DeployIntent(context.Context, string) error { | |||
| 146 | - | panic("unexpected call") | |||
| 147 | - | } | |||
| 148 | - | ||||
| 149 | - | func (c *resolveFromImageTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) { | |||
| 150 | - | panic("unexpected call") | |||
| 151 | - | } | |||
| 152 | - | ||||
| 153 | - | func (c *resolveFromImageTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) { | |||
| 154 | - | panic("unexpected call") | |||
| 155 | - | } | |||
| 156 | - | ||||
| 157 | - | func (c *resolveFromImageTestControlPlane) ListTags(context.Context, string) ([]string, error) { | |||
| 158 | - | panic("unexpected call") | |||
| 159 | - | } | |||
| 160 | - | ||||
| 161 | - | func (c *resolveFromImageTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) { | |||
| 162 | - | panic("unexpected call") | |||
| 163 | - | } | |||
| 164 | - | ||||
| 165 | - | func (c *resolveFromImageTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) { | |||
| 166 | - | panic("unexpected call") | |||
| 167 | - | } | |||
| 168 | - | ||||
| 169 | - | func (c *resolveFromImageTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) { | |||
| 170 | - | panic("unexpected call") | |||
| 171 | - | } | |||
| 172 | - | ||||
| 173 | - | func (c *resolveFromImageTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) { | |||
| 174 | - | panic("unexpected call") | |||
| 175 | - | } | |||
| 176 | - | ||||
| 177 | - | func (c *resolveFromImageTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) { | |||
| 178 | - | panic("unexpected call") | |||
| 179 | - | } | |||
| 180 | - | ||||
| 181 | - | func (c *resolveFromImageTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) { | |||
| 182 | - | panic("unexpected call") | |||
| 183 | - | } | |||
| 184 | - | ||||
| 185 | - | func (c *resolveFromImageTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) { | |||
| 186 | - | panic("unexpected call") | |||
| 187 | - | } | |||
| 188 | - | ||||
| 189 | - | func (c *resolveFromImageTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) { | |||
| 190 | - | panic("unexpected call") | |||
| 191 | - | } | |||
| 192 | - | ||||
| 193 | - | func (c *resolveFromImageTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) { | |||
| 194 | - | panic("unexpected call") | |||
| 195 | - | } | |||
| 196 | - | ||||
| 197 | - | func (c *resolveFromImageTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |||
| 198 | - | panic("unexpected call") | |||
| 199 | - | } | |||
| 200 | - | ||||
| 201 | func TestValidateBuildArg(t *testing.T) { | 21 | func TestValidateBuildArg(t *testing.T) { | ||
| 202 | tests := []struct { | 22 | tests := []struct { | ||
| 203 | name string | 23 | name string | ||
| @@ -631,13 +451,10 @@ func TestParseLabelPair(t *testing.T) { | |||||
| 631 | } | 451 | } | ||
| 632 | 452 | ||||
| 633 | func TestResolveFromImage_NoRouteSuggestsBootstrap(t *testing.T) { | 453 | func TestResolveFromImage_NoRouteSuggestsBootstrap(t *testing.T) { | ||
| 634 | - | cp := &resolveFromImageTestControlPlane{ | 454 | + | cpMock := climocks.NewMockControlPlane(t) |
| 635 | - | findRoutesByImage: func(context.Context, string) ([]domain.Route, error) { | 455 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "myapp").Return(nil, nil).Once() |
| 636 | - | return nil, nil | |||
| 637 | - | }, | |||
| 638 | - | } | |||
| 639 | 456 | ||||
| 640 | - | _, _, _, err := resolveFromImage(context.Background(), cp, "myapp", "Dockerfile") | 457 | + | _, _, _, err := resolveFromImage(context.Background(), cpMock, "myapp", "Dockerfile") |
| 641 | 458 | ||||
| 642 | assert.Error(t, err) | 459 | assert.Error(t, err) | ||
| 643 | assert.Contains(t, err.Error(), `no route configured for image "myapp"`) | 460 | assert.Contains(t, err.Error(), `no route configured for image "myapp"`) | ||
| @@ -645,18 +462,10 @@ func TestResolveFromImage_NoRouteSuggestsBootstrap(t *testing.T) { | |||||
| 645 | } | 462 | } | ||
| 646 | 463 | ||||
| 647 | func TestResolveRoute_DottedBareImageUsesImageLookup(t *testing.T) { | 464 | func TestResolveRoute_DottedBareImageUsesImageLookup(t *testing.T) { | ||
| 648 | - | cp := &resolveFromImageTestControlPlane{ | 465 | + | cpMock := climocks.NewMockControlPlane(t) |
| 649 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | 466 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "my.app").Return([]domain.Route{{Domain: "app.example.com", Image: "registry.example.com/my.app:latest"}}, nil).Once() |
| 650 | - | assert.Equal(t, "my.app", imageName) | |||
| 651 | - | return []domain.Route{{Domain: "app.example.com", Image: "registry.example.com/my.app:latest"}}, nil | |||
| 652 | - | }, | |||
| 653 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | |||
| 654 | - | t.Fatalf("unexpected GetRoute call") | |||
| 655 | - | return nil, nil | |||
| 656 | - | }, | |||
| 657 | - | } | |||
| 658 | 467 | ||||
| 659 | - | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cp, "my.app", "", "Dockerfile") | 468 | + | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cpMock, "my.app", "", "Dockerfile") |
| 660 | 469 | ||||
| 661 | assert.NoError(t, err) | 470 | assert.NoError(t, err) | ||
| 662 | assert.Equal(t, "registry.example.com", registry) | 471 | assert.Equal(t, "registry.example.com", registry) | ||
| @@ -665,21 +474,12 @@ func TestResolveRoute_DottedBareImageUsesImageLookup(t *testing.T) { | |||||
| 665 | } | 474 | } | ||
| 666 | 475 | ||||
| 667 | func TestResolveRoute_DottedBareImageNoRoutesKeepsBootstrapError(t *testing.T) { | 476 | func TestResolveRoute_DottedBareImageNoRoutesKeepsBootstrapError(t *testing.T) { | ||
| 668 | - | sawImageLookup := false | 477 | + | cpMock := climocks.NewMockControlPlane(t) |
| 669 | - | cp := &resolveFromImageTestControlPlane{ | 478 | + | imageLookup := cpMock.EXPECT().FindRoutesByImage(context.Background(), "my.app").Return(nil, nil).Once() |
| 670 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | 479 | + | routeLookup := cpMock.EXPECT().GetRoute(context.Background(), "my.app").Return(nil, domain.ErrRouteNotFound).Once() |
| 671 | - | sawImageLookup = true | 480 | + | mock.InOrder(imageLookup, routeLookup) |
| 672 | - | assert.Equal(t, "my.app", imageName) | |||
| 673 | - | return nil, nil | |||
| 674 | - | }, | |||
| 675 | - | getRoute: func(_ context.Context, domainName string) (*domain.Route, error) { | |||
| 676 | - | assert.True(t, sawImageLookup, "expected image lookup before legacy domain lookup") | |||
| 677 | - | assert.Equal(t, "my.app", domainName) | |||
| 678 | - | return nil, domain.ErrRouteNotFound | |||
| 679 | - | }, | |||
| 680 | - | } | |||
| 681 | 481 | ||||
| 682 | - | _, _, _, err := resolveRoute(context.Background(), cp, "my.app", "", "Dockerfile") | 482 | + | _, _, _, err := resolveRoute(context.Background(), cpMock, "my.app", "", "Dockerfile") |
| 683 | 483 | ||||
| 684 | assert.Error(t, err) | 484 | assert.Error(t, err) | ||
| 685 | assert.True(t, errors.Is(err, domain.ErrNoRouteForImage)) | 485 | assert.True(t, errors.Is(err, domain.ErrNoRouteForImage)) | ||
| @@ -696,21 +496,12 @@ func TestNoRouteForImageErrorWrapsSentinel(t *testing.T) { | |||||
| 696 | } | 496 | } | ||
| 697 | 497 | ||||
| 698 | func TestResolveRoute_DottedBareDomainFallsBackToLegacyLookup(t *testing.T) { | 498 | func TestResolveRoute_DottedBareDomainFallsBackToLegacyLookup(t *testing.T) { | ||
| 699 | - | sawImageLookup := false | 499 | + | cpMock := climocks.NewMockControlPlane(t) |
| 700 | - | cp := &resolveFromImageTestControlPlane{ | 500 | + | imageLookup := cpMock.EXPECT().FindRoutesByImage(context.Background(), "app.example.com").Return(nil, nil).Once() |
| 701 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | 501 | + | routeLookup := cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}, nil).Once() |
| 702 | - | sawImageLookup = true | 502 | + | mock.InOrder(imageLookup, routeLookup) |
| 703 | - | assert.Equal(t, "app.example.com", imageName) | |||
| 704 | - | return nil, nil | |||
| 705 | - | }, | |||
| 706 | - | getRoute: func(_ context.Context, domainName string) (*domain.Route, error) { | |||
| 707 | - | assert.True(t, sawImageLookup, "expected image lookup before legacy domain lookup") | |||
| 708 | - | assert.Equal(t, "app.example.com", domainName) | |||
| 709 | - | return &domain.Route{Domain: domainName, Image: "registry.example.com/myapp:latest"}, nil | |||
| 710 | - | }, | |||
| 711 | - | } | |||
| 712 | 503 | ||||
| 713 | - | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cp, "app.example.com", "", "Dockerfile") | 504 | + | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cpMock, "app.example.com", "", "Dockerfile") |
| 714 | 505 | ||||
| 715 | assert.NoError(t, err) | 506 | assert.NoError(t, err) | ||
| 716 | assert.Equal(t, "registry.example.com", registry) | 507 | assert.Equal(t, "registry.example.com", registry) | ||
| @@ -719,14 +510,10 @@ func TestResolveRoute_DottedBareDomainFallsBackToLegacyLookup(t *testing.T) { | |||||
| 719 | } | 510 | } | ||
| 720 | 511 | ||||
| 721 | func TestResolveRoute_TaggedImageStripsTagBeforeLookup(t *testing.T) { | 512 | func TestResolveRoute_TaggedImageStripsTagBeforeLookup(t *testing.T) { | ||
| 722 | - | cp := &resolveFromImageTestControlPlane{ | 513 | + | cpMock := climocks.NewMockControlPlane(t) |
| 723 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | 514 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "myapp").Return([]domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil).Once() |
| 724 | - | assert.Equal(t, "myapp", imageName) | |||
| 725 | - | return []domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil | |||
| 726 | - | }, | |||
| 727 | - | } | |||
| 728 | 515 | ||||
| 729 | - | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cp, "myapp:v1.2.3", "", "Dockerfile") | 516 | + | registry, imageName, pushDomain, err := resolveRoute(context.Background(), cpMock, "myapp:v1.2.3", "", "Dockerfile") |
| 730 | 517 | ||||
| 731 | assert.NoError(t, err) | 518 | assert.NoError(t, err) | ||
| 732 | assert.Equal(t, "registry.example.com", registry) | 519 | assert.Equal(t, "registry.example.com", registry) | ||
| @@ -735,14 +522,10 @@ func TestResolveRoute_TaggedImageStripsTagBeforeLookup(t *testing.T) { | |||||
| 735 | } | 522 | } | ||
| 736 | 523 | ||||
| 737 | func TestResolveRoute_RegistryQualifiedTaggedImageUsesImageLookup(t *testing.T) { | 524 | func TestResolveRoute_RegistryQualifiedTaggedImageUsesImageLookup(t *testing.T) { | ||
| 738 | - | cp := &resolveFromImageTestControlPlane{ | 525 | + | cpMock := climocks.NewMockControlPlane(t) |
| 739 | - | findRoutesByImage: func(_ context.Context, imageName string) ([]domain.Route, error) { | 526 | + | cpMock.EXPECT().FindRoutesByImage(context.Background(), "registry.example.com/myapp").Return([]domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil).Once() |
| 740 | - | assert.Equal(t, "registry.example.com/myapp", imageName) | |||
| 741 | - | return []domain.Route{{Domain: "app.example.com", Image: "registry.example.com/myapp:latest"}}, nil | |||
| 742 | - | }, | |||
| 743 | - | } | |||
| 744 | 527 | ||||
| 745 | - | _, _, _, err := resolveRoute(context.Background(), cp, "registry.example.com/myapp:v1.2.3", "", "Dockerfile") | 528 | + | _, _, _, err := resolveRoute(context.Background(), cpMock, "registry.example.com/myapp:v1.2.3", "", "Dockerfile") |
| 746 | 529 | ||||
| 747 | assert.NoError(t, err) | 530 | assert.NoError(t, err) | ||
| 748 | } | 531 | } | ||
M internal/adapters/in/cli/routes.go +9 -13
| @@ -33,9 +33,7 @@ func truncateImage(image string, maxLen int) string { | |||
| 33 | 33 | return "" | |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | - | if idx := strings.Index(image, "@sha256:"); idx != -1 { | |
| 37 | - | name := image[:idx] | |
| 38 | - | digest := image[idx+8:] // Skip "@sha256:" | |
| 36 | + | if name, digest, ok := strings.Cut(image, "@sha256:"); ok { | |
| 39 | 37 | if len(digest) > 12 { | |
| 40 | 38 | digest = digest[:12] | |
| 41 | 39 | } | |
| @@ -349,17 +347,16 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||
| 349 | 347 | remoteSections := make([]routeListSection, len(names)) | |
| 350 | 348 | var wg sync.WaitGroup | |
| 351 | 349 | for i, name := range names { | |
| 352 | - | i, name := i, name | |
| 353 | 350 | entry := remotes.entries[name] | |
| 354 | 351 | wg.Add(1) | |
| 355 | - | go func() { | |
| 352 | + | go func(idx int, remoteName string, remoteEntry remote.RemoteEntry) { | |
| 356 | 353 | defer wg.Done() | |
| 357 | - | section, err := deps.loadRemote(ctx, name, entry) | |
| 354 | + | section, err := deps.loadRemote(ctx, remoteName, remoteEntry) | |
| 358 | 355 | if err != nil && section.Error == "" { | |
| 359 | 356 | section.Error = err.Error() | |
| 360 | 357 | } | |
| 361 | - | remoteSections[i] = normalizeRouteListRemoteSection(section, name, entry) | |
| 362 | - | }() | |
| 358 | + | remoteSections[idx] = normalizeRouteListRemoteSection(section, remoteName, remoteEntry) | |
| 359 | + | }(i, name, entry) | |
| 363 | 360 | } | |
| 364 | 361 | wg.Wait() | |
| 365 | 362 | ||
| @@ -473,17 +470,16 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||
| 473 | 470 | remoteSections := make([]routeStatusSection, len(names)) | |
| 474 | 471 | var wg sync.WaitGroup | |
| 475 | 472 | for i, name := range names { | |
| 476 | - | i, name := i, name | |
| 477 | 473 | entry := remotes.entries[name] | |
| 478 | 474 | wg.Add(1) | |
| 479 | - | go func() { | |
| 475 | + | go func(idx int, remoteName string, remoteEntry remote.RemoteEntry) { | |
| 480 | 476 | defer wg.Done() | |
| 481 | - | section, err := deps.loadRemote(ctx, name, entry) | |
| 477 | + | section, err := deps.loadRemote(ctx, remoteName, remoteEntry) | |
| 482 | 478 | if err != nil && section.Error == "" { | |
| 483 | 479 | section.Error = err.Error() | |
| 484 | 480 | } | |
| 485 | - | remoteSections[i] = normalizeRouteStatusRemoteSection(section, name, entry) | |
| 486 | - | }() | |
| 481 | + | remoteSections[idx] = normalizeRouteStatusRemoteSection(section, remoteName, remoteEntry) | |
| 482 | + | }(i, name, entry) | |
| 487 | 483 | } | |
| 488 | 484 | wg.Wait() | |
| 489 | 485 | ||
| @@ -33,9 +33,7 @@ func truncateImage(image string, maxLen int) string { | |||||
| 33 | return "" | 33 | return "" | ||
| 34 | } | 34 | } | ||
| 35 | 35 | ||||
| 36 | - | if idx := strings.Index(image, "@sha256:"); idx != -1 { | 36 | + | if name, digest, ok := strings.Cut(image, "@sha256:"); ok { |
| 37 | - | name := image[:idx] | |||
| 38 | - | digest := image[idx+8:] // Skip "@sha256:" | |||
| 39 | if len(digest) > 12 { | 37 | if len(digest) > 12 { | ||
| 40 | digest = digest[:12] | 38 | digest = digest[:12] | ||
| 41 | } | 39 | } | ||
| @@ -349,17 +347,16 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||||
| 349 | remoteSections := make([]routeListSection, len(names)) | 347 | remoteSections := make([]routeListSection, len(names)) | ||
| 350 | var wg sync.WaitGroup | 348 | var wg sync.WaitGroup | ||
| 351 | for i, name := range names { | 349 | for i, name := range names { | ||
| 352 | - | i, name := i, name | |||
| 353 | entry := remotes.entries[name] | 350 | entry := remotes.entries[name] | ||
| 354 | wg.Add(1) | 351 | wg.Add(1) | ||
| 355 | - | go func() { | 352 | + | go func(idx int, remoteName string, remoteEntry remote.RemoteEntry) { |
| 356 | defer wg.Done() | 353 | defer wg.Done() | ||
| 357 | - | section, err := deps.loadRemote(ctx, name, entry) | 354 | + | section, err := deps.loadRemote(ctx, remoteName, remoteEntry) |
| 358 | if err != nil && section.Error == "" { | 355 | if err != nil && section.Error == "" { | ||
| 359 | section.Error = err.Error() | 356 | section.Error = err.Error() | ||
| 360 | } | 357 | } | ||
| 361 | - | remoteSections[i] = normalizeRouteListRemoteSection(section, name, entry) | 358 | + | remoteSections[idx] = normalizeRouteListRemoteSection(section, remoteName, remoteEntry) |
| 362 | - | }() | 359 | + | }(i, name, entry) |
| 363 | } | 360 | } | ||
| 364 | wg.Wait() | 361 | wg.Wait() | ||
| 365 | 362 | ||||
| @@ -473,17 +470,16 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||||
| 473 | remoteSections := make([]routeStatusSection, len(names)) | 470 | remoteSections := make([]routeStatusSection, len(names)) | ||
| 474 | var wg sync.WaitGroup | 471 | var wg sync.WaitGroup | ||
| 475 | for i, name := range names { | 472 | for i, name := range names { | ||
| 476 | - | i, name := i, name | |||
| 477 | entry := remotes.entries[name] | 473 | entry := remotes.entries[name] | ||
| 478 | wg.Add(1) | 474 | wg.Add(1) | ||
| 479 | - | go func() { | 475 | + | go func(idx int, remoteName string, remoteEntry remote.RemoteEntry) { |
| 480 | defer wg.Done() | 476 | defer wg.Done() | ||
| 481 | - | section, err := deps.loadRemote(ctx, name, entry) | 477 | + | section, err := deps.loadRemote(ctx, remoteName, remoteEntry) |
| 482 | if err != nil && section.Error == "" { | 478 | if err != nil && section.Error == "" { | ||
| 483 | section.Error = err.Error() | 479 | section.Error = err.Error() | ||
| 484 | } | 480 | } | ||
| 485 | - | remoteSections[i] = normalizeRouteStatusRemoteSection(section, name, entry) | 481 | + | remoteSections[idx] = normalizeRouteStatusRemoteSection(section, remoteName, remoteEntry) |
| 486 | - | }() | 482 | + | }(i, name, entry) |
| 487 | } | 483 | } | ||
| 488 | wg.Wait() | 484 | wg.Wait() | ||
| 489 | 485 | ||||
M internal/adapters/in/cli/routes_test.go +34 -88
| @@ -23,32 +23,6 @@ import ( | |||
| 23 | 23 | "github.com/stretchr/testify/require" | |
| 24 | 24 | ) | |
| 25 | 25 | ||
| 26 | - | type routesShowTestControlPlane struct { | |
| 27 | - | resolveFromImageTestControlPlane | |
| 28 | - | getHealth func(context.Context) (map[string]*remote.RouteHealth, error) | |
| 29 | - | } | |
| 30 | - | ||
| 31 | - | var _ ControlPlane = (*routesShowTestControlPlane)(nil) | |
| 32 | - | ||
| 33 | - | func (c *routesShowTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |
| 34 | - | panic("unexpected call") | |
| 35 | - | } | |
| 36 | - | ||
| 37 | - | func (c *routesShowTestControlPlane) GetHealth(ctx context.Context) (map[string]*remote.RouteHealth, error) { | |
| 38 | - | if c.getHealth != nil { | |
| 39 | - | return c.getHealth(ctx) | |
| 40 | - | } | |
| 41 | - | panic("unexpected call") | |
| 42 | - | } | |
| 43 | - | ||
| 44 | - | func (c *routesShowTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |
| 45 | - | panic("unexpected call") | |
| 46 | - | } | |
| 47 | - | ||
| 48 | - | func (c *routesShowTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |
| 49 | - | panic("unexpected call") | |
| 50 | - | } | |
| 51 | - | ||
| 52 | 26 | func TestTruncateImage(t *testing.T) { | |
| 53 | 27 | tests := []struct { | |
| 54 | 28 | name string | |
| @@ -315,19 +289,12 @@ func TestRouteStatusTitle_PreservesProbeFailureError(t *testing.T) { | |||
| 315 | 289 | } | |
| 316 | 290 | ||
| 317 | 291 | func TestRunRoutesShow_JSONIncludesHealthError(t *testing.T) { | |
| 318 | - | fake := &routesShowTestControlPlane{ | |
| 319 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |
| 320 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 321 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |
| 322 | - | }, | |
| 323 | - | }, | |
| 324 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 325 | - | return nil, errors.New("probe failed") | |
| 326 | - | }, | |
| 327 | - | } | |
| 292 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 293 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | |
| 294 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(nil, errors.New("probe failed")).Once() | |
| 328 | 295 | ||
| 329 | 296 | var out bytes.Buffer | |
| 330 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true) | |
| 297 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", true) | |
| 331 | 298 | ||
| 332 | 299 | require.NoError(t, err) | |
| 333 | 300 | ||
| @@ -341,19 +308,12 @@ func TestRunRoutesShow_JSONIncludesHealthError(t *testing.T) { | |||
| 341 | 308 | } | |
| 342 | 309 | ||
| 343 | 310 | func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |
| 344 | - | fake := &routesShowTestControlPlane{ | |
| 345 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |
| 346 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 347 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |
| 348 | - | }, | |
| 349 | - | }, | |
| 350 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 351 | - | return nil, errors.New("probe failed") | |
| 352 | - | }, | |
| 353 | - | } | |
| 311 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 312 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | |
| 313 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(nil, errors.New("probe failed")).Once() | |
| 354 | 314 | ||
| 355 | 315 | var out bytes.Buffer | |
| 356 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false) | |
| 316 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", false) | |
| 357 | 317 | ||
| 358 | 318 | require.NoError(t, err) | |
| 359 | 319 | ||
| @@ -366,25 +326,18 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |||
| 366 | 326 | } | |
| 367 | 327 | ||
| 368 | 328 | func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | |
| 369 | - | fake := &routesShowTestControlPlane{ | |
| 370 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |
| 371 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 372 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |
| 373 | - | }, | |
| 374 | - | }, | |
| 375 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 376 | - | return map[string]*remote.RouteHealth{ | |
| 377 | - | "app.example.com": { | |
| 378 | - | HTTPStatus: 503, | |
| 379 | - | ContainerStatus: "", | |
| 380 | - | Error: "probe failed", | |
| 381 | - | }, | |
| 382 | - | }, nil | |
| 329 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 330 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | |
| 331 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(map[string]*remote.RouteHealth{ | |
| 332 | + | "app.example.com": { | |
| 333 | + | HTTPStatus: 503, | |
| 334 | + | ContainerStatus: "", | |
| 335 | + | Error: "probe failed", | |
| 383 | 336 | }, | |
| 384 | - | } | |
| 337 | + | }, nil).Once() | |
| 385 | 338 | ||
| 386 | 339 | var out bytes.Buffer | |
| 387 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true) | |
| 340 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", true) | |
| 388 | 341 | ||
| 389 | 342 | require.NoError(t, err) | |
| 390 | 343 | ||
| @@ -398,25 +351,18 @@ func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t | |||
| 398 | 351 | } | |
| 399 | 352 | ||
| 400 | 353 | func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | |
| 401 | - | fake := &routesShowTestControlPlane{ | |
| 402 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |
| 403 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 404 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |
| 405 | - | }, | |
| 354 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 355 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | |
| 356 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(map[string]*remote.RouteHealth{ | |
| 357 | + | "app.example.com": { | |
| 358 | + | HTTPStatus: 503, | |
| 359 | + | ContainerStatus: "", | |
| 360 | + | Error: "probe failed", | |
| 406 | 361 | }, | |
| 407 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 408 | - | return map[string]*remote.RouteHealth{ | |
| 409 | - | "app.example.com": { | |
| 410 | - | HTTPStatus: 503, | |
| 411 | - | ContainerStatus: "", | |
| 412 | - | Error: "probe failed", | |
| 413 | - | }, | |
| 414 | - | }, nil | |
| 415 | - | }, | |
| 416 | - | } | |
| 362 | + | }, nil).Once() | |
| 417 | 363 | ||
| 418 | 364 | var out bytes.Buffer | |
| 419 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false) | |
| 365 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", false) | |
| 420 | 366 | ||
| 421 | 367 | require.NoError(t, err) | |
| 422 | 368 | ||
| @@ -954,21 +900,21 @@ func TestAttachmentsForRouteDiagnosisUsesOwner(t *testing.T) { | |||
| 954 | 900 | } | |
| 955 | 901 | ||
| 956 | 902 | func TestRunRoutePurge_AttachmentsForceIsScopedToRoute(t *testing.T) { | |
| 957 | - | cp := climocks.NewMockControlPlane(t) | |
| 903 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 958 | 904 | orphanedAttachments := []domain.CleanupAttachment{ | |
| 959 | 905 | {ContainerID: "app-attachment", Owner: "app.example.com", Name: "postgres"}, | |
| 960 | 906 | {ContainerID: "other-attachment", Owner: "other.example.com", Name: "redis"}, | |
| 961 | 907 | } | |
| 962 | - | cp.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | |
| 963 | - | cp.EXPECT().ListRoutesWithDetails(context.Background()).Return(nil, nil).Once() | |
| 964 | - | cp.EXPECT().GetHealth(context.Background()).Return(nil, nil).Once() | |
| 965 | - | cp.EXPECT().ListVolumes(context.Background()).Return(nil, nil).Once() | |
| 966 | - | cp.EXPECT().ListOrphanedAttachments(context.Background()).Return(orphanedAttachments, nil).Once() | |
| 967 | - | cp.EXPECT().CleanupOrphanedAttachments(context.Background(), "app.example.com", true).Return(&domain.CleanupReport{ | |
| 908 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | |
| 909 | + | cpMock.EXPECT().ListRoutesWithDetails(context.Background()).Return(nil, nil).Once() | |
| 910 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(nil, nil).Once() | |
| 911 | + | cpMock.EXPECT().ListVolumes(context.Background()).Return(nil, nil).Once() | |
| 912 | + | cpMock.EXPECT().ListOrphanedAttachments(context.Background()).Return(orphanedAttachments, nil).Once() | |
| 913 | + | cpMock.EXPECT().CleanupOrphanedAttachments(context.Background(), "app.example.com", true).Return(&domain.CleanupReport{ | |
| 968 | 914 | RemovedContainers: []domain.CleanupContainer{{ID: "app-attachment", Name: "postgres"}}, | |
| 969 | 915 | }, nil).Once() | |
| 970 | 916 | ||
| 971 | - | report, err := runRoutePurge(context.Background(), cp, "app.example.com", routePurgeOptions{Force: true, Attachments: true}) | |
| 917 | + | report, err := runRoutePurge(context.Background(), cpMock, "app.example.com", routePurgeOptions{Force: true, Attachments: true}) | |
| 972 | 918 | ||
| 973 | 919 | require.NoError(t, err) | |
| 974 | 920 | require.NotNil(t, report) | |
| @@ -23,32 +23,6 @@ import ( | |||||
| 23 | "github.com/stretchr/testify/require" | 23 | "github.com/stretchr/testify/require" | ||
| 24 | ) | 24 | ) | ||
| 25 | 25 | ||||
| 26 | - | type routesShowTestControlPlane struct { | |||
| 27 | - | resolveFromImageTestControlPlane | |||
| 28 | - | getHealth func(context.Context) (map[string]*remote.RouteHealth, error) | |||
| 29 | - | } | |||
| 30 | - | ||||
| 31 | - | var _ ControlPlane = (*routesShowTestControlPlane)(nil) | |||
| 32 | - | ||||
| 33 | - | func (c *routesShowTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |||
| 34 | - | panic("unexpected call") | |||
| 35 | - | } | |||
| 36 | - | ||||
| 37 | - | func (c *routesShowTestControlPlane) GetHealth(ctx context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 38 | - | if c.getHealth != nil { | |||
| 39 | - | return c.getHealth(ctx) | |||
| 40 | - | } | |||
| 41 | - | panic("unexpected call") | |||
| 42 | - | } | |||
| 43 | - | ||||
| 44 | - | func (c *routesShowTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |||
| 45 | - | panic("unexpected call") | |||
| 46 | - | } | |||
| 47 | - | ||||
| 48 | - | func (c *routesShowTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |||
| 49 | - | panic("unexpected call") | |||
| 50 | - | } | |||
| 51 | - | ||||
| 52 | func TestTruncateImage(t *testing.T) { | 26 | func TestTruncateImage(t *testing.T) { | ||
| 53 | tests := []struct { | 27 | tests := []struct { | ||
| 54 | name string | 28 | name string | ||
| @@ -315,19 +289,12 @@ func TestRouteStatusTitle_PreservesProbeFailureError(t *testing.T) { | |||||
| 315 | } | 289 | } | ||
| 316 | 290 | ||||
| 317 | func TestRunRoutesShow_JSONIncludesHealthError(t *testing.T) { | 291 | func TestRunRoutesShow_JSONIncludesHealthError(t *testing.T) { | ||
| 318 | - | fake := &routesShowTestControlPlane{ | 292 | + | cpMock := climocks.NewMockControlPlane(t) |
| 319 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | 293 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() |
| 320 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | 294 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(nil, errors.New("probe failed")).Once() |
| 321 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |||
| 322 | - | }, | |||
| 323 | - | }, | |||
| 324 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 325 | - | return nil, errors.New("probe failed") | |||
| 326 | - | }, | |||
| 327 | - | } | |||
| 328 | 295 | ||||
| 329 | var out bytes.Buffer | 296 | var out bytes.Buffer | ||
| 330 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true) | 297 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", true) |
| 331 | 298 | ||||
| 332 | require.NoError(t, err) | 299 | require.NoError(t, err) | ||
| 333 | 300 | ||||
| @@ -341,19 +308,12 @@ func TestRunRoutesShow_JSONIncludesHealthError(t *testing.T) { | |||||
| 341 | } | 308 | } | ||
| 342 | 309 | ||||
| 343 | func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | 310 | func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | ||
| 344 | - | fake := &routesShowTestControlPlane{ | 311 | + | cpMock := climocks.NewMockControlPlane(t) |
| 345 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | 312 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() |
| 346 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | 313 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(nil, errors.New("probe failed")).Once() |
| 347 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |||
| 348 | - | }, | |||
| 349 | - | }, | |||
| 350 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 351 | - | return nil, errors.New("probe failed") | |||
| 352 | - | }, | |||
| 353 | - | } | |||
| 354 | 314 | ||||
| 355 | var out bytes.Buffer | 315 | var out bytes.Buffer | ||
| 356 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false) | 316 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", false) |
| 357 | 317 | ||||
| 358 | require.NoError(t, err) | 318 | require.NoError(t, err) | ||
| 359 | 319 | ||||
| @@ -366,25 +326,18 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |||||
| 366 | } | 326 | } | ||
| 367 | 327 | ||||
| 368 | func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | 328 | func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | ||
| 369 | - | fake := &routesShowTestControlPlane{ | 329 | + | cpMock := climocks.NewMockControlPlane(t) |
| 370 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | 330 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() |
| 371 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | 331 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(map[string]*remote.RouteHealth{ |
| 372 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | 332 | + | "app.example.com": { |
| 373 | - | }, | 333 | + | HTTPStatus: 503, |
| 374 | - | }, | 334 | + | ContainerStatus: "", |
| 375 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | 335 | + | Error: "probe failed", |
| 376 | - | return map[string]*remote.RouteHealth{ | |||
| 377 | - | "app.example.com": { | |||
| 378 | - | HTTPStatus: 503, | |||
| 379 | - | ContainerStatus: "", | |||
| 380 | - | Error: "probe failed", | |||
| 381 | - | }, | |||
| 382 | - | }, nil | |||
| 383 | }, | 336 | }, | ||
| 384 | - | } | 337 | + | }, nil).Once() |
| 385 | 338 | ||||
| 386 | var out bytes.Buffer | 339 | var out bytes.Buffer | ||
| 387 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true) | 340 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", true) |
| 388 | 341 | ||||
| 389 | require.NoError(t, err) | 342 | require.NoError(t, err) | ||
| 390 | 343 | ||||
| @@ -398,25 +351,18 @@ func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t | |||||
| 398 | } | 351 | } | ||
| 399 | 352 | ||||
| 400 | func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | 353 | func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | ||
| 401 | - | fake := &routesShowTestControlPlane{ | 354 | + | cpMock := climocks.NewMockControlPlane(t) |
| 402 | - | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | 355 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() |
| 403 | - | getRoute: func(context.Context, string) (*domain.Route, error) { | 356 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(map[string]*remote.RouteHealth{ |
| 404 | - | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | 357 | + | "app.example.com": { |
| 405 | - | }, | 358 | + | HTTPStatus: 503, |
| 359 | + | ContainerStatus: "", | |||
| 360 | + | Error: "probe failed", | |||
| 406 | }, | 361 | }, | ||
| 407 | - | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | 362 | + | }, nil).Once() |
| 408 | - | return map[string]*remote.RouteHealth{ | |||
| 409 | - | "app.example.com": { | |||
| 410 | - | HTTPStatus: 503, | |||
| 411 | - | ContainerStatus: "", | |||
| 412 | - | Error: "probe failed", | |||
| 413 | - | }, | |||
| 414 | - | }, nil | |||
| 415 | - | }, | |||
| 416 | - | } | |||
| 417 | 363 | ||||
| 418 | var out bytes.Buffer | 364 | var out bytes.Buffer | ||
| 419 | - | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false) | 365 | + | err := runRoutesShow(context.Background(), cpMock, &out, "app.example.com", false) |
| 420 | 366 | ||||
| 421 | require.NoError(t, err) | 367 | require.NoError(t, err) | ||
| 422 | 368 | ||||
| @@ -954,21 +900,21 @@ func TestAttachmentsForRouteDiagnosisUsesOwner(t *testing.T) { | |||||
| 954 | } | 900 | } | ||
| 955 | 901 | ||||
| 956 | func TestRunRoutePurge_AttachmentsForceIsScopedToRoute(t *testing.T) { | 902 | func TestRunRoutePurge_AttachmentsForceIsScopedToRoute(t *testing.T) { | ||
| 957 | - | cp := climocks.NewMockControlPlane(t) | 903 | + | cpMock := climocks.NewMockControlPlane(t) |
| 958 | orphanedAttachments := []domain.CleanupAttachment{ | 904 | orphanedAttachments := []domain.CleanupAttachment{ | ||
| 959 | {ContainerID: "app-attachment", Owner: "app.example.com", Name: "postgres"}, | 905 | {ContainerID: "app-attachment", Owner: "app.example.com", Name: "postgres"}, | ||
| 960 | {ContainerID: "other-attachment", Owner: "other.example.com", Name: "redis"}, | 906 | {ContainerID: "other-attachment", Owner: "other.example.com", Name: "redis"}, | ||
| 961 | } | 907 | } | ||
| 962 | - | cp.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() | 908 | + | cpMock.EXPECT().GetRoute(context.Background(), "app.example.com").Return(&domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil).Once() |
| 963 | - | cp.EXPECT().ListRoutesWithDetails(context.Background()).Return(nil, nil).Once() | 909 | + | cpMock.EXPECT().ListRoutesWithDetails(context.Background()).Return(nil, nil).Once() |
| 964 | - | cp.EXPECT().GetHealth(context.Background()).Return(nil, nil).Once() | 910 | + | cpMock.EXPECT().GetHealth(context.Background()).Return(nil, nil).Once() |
| 965 | - | cp.EXPECT().ListVolumes(context.Background()).Return(nil, nil).Once() | 911 | + | cpMock.EXPECT().ListVolumes(context.Background()).Return(nil, nil).Once() |
| 966 | - | cp.EXPECT().ListOrphanedAttachments(context.Background()).Return(orphanedAttachments, nil).Once() | 912 | + | cpMock.EXPECT().ListOrphanedAttachments(context.Background()).Return(orphanedAttachments, nil).Once() |
| 967 | - | cp.EXPECT().CleanupOrphanedAttachments(context.Background(), "app.example.com", true).Return(&domain.CleanupReport{ | 913 | + | cpMock.EXPECT().CleanupOrphanedAttachments(context.Background(), "app.example.com", true).Return(&domain.CleanupReport{ |
| 968 | RemovedContainers: []domain.CleanupContainer{{ID: "app-attachment", Name: "postgres"}}, | 914 | RemovedContainers: []domain.CleanupContainer{{ID: "app-attachment", Name: "postgres"}}, | ||
| 969 | }, nil).Once() | 915 | }, nil).Once() | ||
| 970 | 916 | ||||
| 971 | - | report, err := runRoutePurge(context.Background(), cp, "app.example.com", routePurgeOptions{Force: true, Attachments: true}) | 917 | + | report, err := runRoutePurge(context.Background(), cpMock, "app.example.com", routePurgeOptions{Force: true, Attachments: true}) |
| 972 | 918 | ||||
| 973 | require.NoError(t, err) | 919 | require.NoError(t, err) | ||
| 974 | require.NotNil(t, report) | 920 | require.NotNil(t, report) | ||
M internal/adapters/in/cli/tls_test.go +49 -234
| @@ -11,229 +11,47 @@ import ( | |||
| 11 | 11 | "github.com/stretchr/testify/require" | |
| 12 | 12 | ||
| 13 | 13 | "github.com/bnema/gordon/internal/adapters/dto" | |
| 14 | - | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | |
| 15 | - | "github.com/bnema/gordon/internal/domain" | |
| 14 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 16 | 15 | ) | |
| 17 | 16 | ||
| 18 | - | // tlsTestControlPlane implements ControlPlane for TLS tests. | |
| 19 | - | type tlsTestControlPlane struct { | |
| 20 | - | getTLSStatus func(context.Context) (*dto.TLSStatusResponse, error) | |
| 21 | - | } | |
| 22 | - | ||
| 23 | - | var _ ControlPlane = (*tlsTestControlPlane)(nil) | |
| 24 | - | ||
| 25 | - | func (c *tlsTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |
| 26 | - | panic("unexpected call") | |
| 27 | - | } | |
| 28 | - | ||
| 29 | - | func (c *tlsTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 30 | - | panic("unexpected call") | |
| 31 | - | } | |
| 32 | - | ||
| 33 | - | func (c *tlsTestControlPlane) GetRoute(context.Context, string) (*domain.Route, error) { | |
| 34 | - | panic("unexpected call") | |
| 35 | - | } | |
| 36 | - | ||
| 37 | - | func (c *tlsTestControlPlane) FindRoutesByImage(context.Context, string) ([]domain.Route, error) { | |
| 38 | - | panic("unexpected call") | |
| 39 | - | } | |
| 40 | - | ||
| 41 | - | func (c *tlsTestControlPlane) AddRoute(context.Context, domain.Route) error { | |
| 42 | - | panic("unexpected call") | |
| 43 | - | } | |
| 44 | - | ||
| 45 | - | func (c *tlsTestControlPlane) UpdateRoute(context.Context, domain.Route) error { | |
| 46 | - | panic("unexpected call") | |
| 47 | - | } | |
| 48 | - | ||
| 49 | - | func (c *tlsTestControlPlane) RemoveRoute(context.Context, string) error { | |
| 50 | - | panic("unexpected call") | |
| 51 | - | } | |
| 52 | - | ||
| 53 | - | func (c *tlsTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) { | |
| 54 | - | panic("unexpected call") | |
| 55 | - | } | |
| 56 | - | ||
| 57 | - | func (c *tlsTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) { | |
| 58 | - | panic("unexpected call") | |
| 59 | - | } | |
| 60 | - | ||
| 61 | - | func (c *tlsTestControlPlane) SetSecrets(context.Context, string, map[string]string) error { | |
| 62 | - | panic("unexpected call") | |
| 63 | - | } | |
| 64 | - | ||
| 65 | - | func (c *tlsTestControlPlane) DeleteSecret(context.Context, string, string) error { | |
| 66 | - | panic("unexpected call") | |
| 67 | - | } | |
| 68 | - | ||
| 69 | - | func (c *tlsTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error { | |
| 70 | - | panic("unexpected call") | |
| 71 | - | } | |
| 72 | - | ||
| 73 | - | func (c *tlsTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error { | |
| 74 | - | panic("unexpected call") | |
| 75 | - | } | |
| 76 | - | ||
| 77 | - | func (c *tlsTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) { | |
| 78 | - | panic("unexpected call") | |
| 79 | - | } | |
| 80 | - | ||
| 81 | - | func (c *tlsTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) { | |
| 82 | - | panic("unexpected call") | |
| 83 | - | } | |
| 84 | - | ||
| 85 | - | func (c *tlsTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |
| 86 | - | panic("unexpected call") | |
| 87 | - | } | |
| 88 | - | ||
| 89 | - | func (c *tlsTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |
| 90 | - | panic("unexpected call") | |
| 91 | - | } | |
| 92 | - | ||
| 93 | - | func (c *tlsTestControlPlane) FindAttachmentTargetsByImage(context.Context, string) ([]string, error) { | |
| 94 | - | panic("unexpected call") | |
| 95 | - | } | |
| 96 | - | ||
| 97 | - | func (c *tlsTestControlPlane) AddAttachment(context.Context, string, string) error { | |
| 98 | - | panic("unexpected call") | |
| 99 | - | } | |
| 100 | - | ||
| 101 | - | func (c *tlsTestControlPlane) RemoveAttachment(context.Context, string, string) error { | |
| 102 | - | panic("unexpected call") | |
| 103 | - | } | |
| 104 | - | ||
| 105 | - | func (c *tlsTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) { | |
| 106 | - | panic("unexpected call") | |
| 107 | - | } | |
| 108 | - | ||
| 109 | - | func (c *tlsTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error { | |
| 110 | - | panic("unexpected call") | |
| 111 | - | } | |
| 112 | - | ||
| 113 | - | func (c *tlsTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error { | |
| 114 | - | panic("unexpected call") | |
| 115 | - | } | |
| 116 | - | ||
| 117 | - | func (c *tlsTestControlPlane) GetTLSStatus(ctx context.Context) (*dto.TLSStatusResponse, error) { | |
| 118 | - | if c.getTLSStatus != nil { | |
| 119 | - | return c.getTLSStatus(ctx) | |
| 120 | - | } | |
| 121 | - | return nil, nil | |
| 122 | - | } | |
| 123 | - | ||
| 124 | - | func (c *tlsTestControlPlane) GetStatus(context.Context) (*remote.Status, error) { | |
| 125 | - | panic("unexpected call") | |
| 126 | - | } | |
| 127 | - | ||
| 128 | - | func (c *tlsTestControlPlane) Reload(context.Context) error { | |
| 129 | - | panic("unexpected call") | |
| 130 | - | } | |
| 131 | - | ||
| 132 | - | func (c *tlsTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) { | |
| 133 | - | panic("unexpected call") | |
| 134 | - | } | |
| 135 | - | ||
| 136 | - | func (c *tlsTestControlPlane) GetConfig(context.Context) (*remote.Config, error) { | |
| 137 | - | panic("unexpected call") | |
| 138 | - | } | |
| 139 | - | ||
| 140 | - | func (c *tlsTestControlPlane) DeployIntent(context.Context, string) error { | |
| 141 | - | panic("unexpected call") | |
| 142 | - | } | |
| 143 | - | ||
| 144 | - | func (c *tlsTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) { | |
| 145 | - | panic("unexpected call") | |
| 146 | - | } | |
| 147 | - | ||
| 148 | - | func (c *tlsTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) { | |
| 149 | - | panic("unexpected call") | |
| 150 | - | } | |
| 151 | - | ||
| 152 | - | func (c *tlsTestControlPlane) ListTags(context.Context, string) ([]string, error) { | |
| 153 | - | panic("unexpected call") | |
| 154 | - | } | |
| 155 | - | ||
| 156 | - | func (c *tlsTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) { | |
| 157 | - | panic("unexpected call") | |
| 158 | - | } | |
| 159 | - | ||
| 160 | - | func (c *tlsTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) { | |
| 161 | - | panic("unexpected call") | |
| 162 | - | } | |
| 163 | - | ||
| 164 | - | func (c *tlsTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) { | |
| 165 | - | panic("unexpected call") | |
| 166 | - | } | |
| 167 | - | ||
| 168 | - | func (c *tlsTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) { | |
| 169 | - | panic("unexpected call") | |
| 170 | - | } | |
| 171 | - | ||
| 172 | - | func (c *tlsTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) { | |
| 173 | - | panic("unexpected call") | |
| 174 | - | } | |
| 175 | - | ||
| 176 | - | func (c *tlsTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) { | |
| 177 | - | panic("unexpected call") | |
| 178 | - | } | |
| 179 | - | ||
| 180 | - | func (c *tlsTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) { | |
| 181 | - | panic("unexpected call") | |
| 182 | - | } | |
| 183 | - | ||
| 184 | - | func (c *tlsTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) { | |
| 185 | - | panic("unexpected call") | |
| 186 | - | } | |
| 187 | - | ||
| 188 | - | func (c *tlsTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) { | |
| 189 | - | panic("unexpected call") | |
| 190 | - | } | |
| 191 | - | ||
| 192 | - | func (c *tlsTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |
| 193 | - | panic("unexpected call") | |
| 194 | - | } | |
| 195 | - | ||
| 196 | 17 | func TestTLSStatus_HumanOutput(t *testing.T) { | |
| 197 | 18 | notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) | |
| 198 | 19 | ||
| 199 | - | cp := &tlsTestControlPlane{ | |
| 200 | - | getTLSStatus: func(_ context.Context) (*dto.TLSStatusResponse, error) { | |
| 201 | - | return &dto.TLSStatusResponse{ | |
| 202 | - | ACMEEnabled: true, | |
| 203 | - | ConfiguredMode: "acme", | |
| 204 | - | EffectiveMode: "acme-staging", | |
| 205 | - | SelectionReason: "configured", | |
| 206 | - | TokenSource: "env", | |
| 207 | - | Certificates: []dto.TLSCertificateEntry{ | |
| 208 | - | { | |
| 209 | - | ID: "cert-abc123", | |
| 210 | - | Names: []string{"example.com", "www.example.com"}, | |
| 211 | - | Status: "valid", | |
| 212 | - | NotAfter: notAfter, | |
| 213 | - | LastError: "", | |
| 214 | - | }, | |
| 215 | - | }, | |
| 216 | - | Routes: []dto.TLSRouteCoverage{ | |
| 217 | - | { | |
| 218 | - | Domain: "example.com", | |
| 219 | - | Covered: true, | |
| 220 | - | CoveredBy: "cert-abc123", | |
| 221 | - | RequiredACME: true, | |
| 222 | - | }, | |
| 223 | - | { | |
| 224 | - | Domain: "internal.local", | |
| 225 | - | Covered: false, | |
| 226 | - | RequiredACME: false, | |
| 227 | - | Error: "self-signed cert", | |
| 228 | - | }, | |
| 229 | - | }, | |
| 230 | - | Errors: []string{"route internal.local has no ACME cert"}, | |
| 231 | - | }, nil | |
| 20 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 21 | + | cpMock.EXPECT().GetTLSStatus(context.Background()).Return(&dto.TLSStatusResponse{ | |
| 22 | + | ACMEEnabled: true, | |
| 23 | + | ConfiguredMode: "acme", | |
| 24 | + | EffectiveMode: "acme-staging", | |
| 25 | + | SelectionReason: "configured", | |
| 26 | + | TokenSource: "env", | |
| 27 | + | Certificates: []dto.TLSCertificateEntry{ | |
| 28 | + | { | |
| 29 | + | ID: "cert-abc123", | |
| 30 | + | Names: []string{"example.com", "www.example.com"}, | |
| 31 | + | Status: "valid", | |
| 32 | + | NotAfter: notAfter, | |
| 33 | + | LastError: "", | |
| 34 | + | }, | |
| 35 | + | }, | |
| 36 | + | Routes: []dto.TLSRouteCoverage{ | |
| 37 | + | { | |
| 38 | + | Domain: "example.com", | |
| 39 | + | Covered: true, | |
| 40 | + | CoveredBy: "cert-abc123", | |
| 41 | + | RequiredACME: true, | |
| 42 | + | }, | |
| 43 | + | { | |
| 44 | + | Domain: "internal.local", | |
| 45 | + | Covered: false, | |
| 46 | + | RequiredACME: false, | |
| 47 | + | Error: "self-signed cert", | |
| 48 | + | }, | |
| 232 | 49 | }, | |
| 233 | - | } | |
| 50 | + | Errors: []string{"route internal.local has no ACME cert"}, | |
| 51 | + | }, nil).Once() | |
| 234 | 52 | ||
| 235 | 53 | var buf bytes.Buffer | |
| 236 | - | err := runTLSStatusCmd(context.Background(), cp, &buf, false) | |
| 54 | + | err := runTLSStatusCmd(context.Background(), cpMock, &buf, false) | |
| 237 | 55 | require.NoError(t, err) | |
| 238 | 56 | ||
| 239 | 57 | output := buf.String() | |
| @@ -273,28 +91,25 @@ func TestTLSStatus_HumanOutput(t *testing.T) { | |||
| 273 | 91 | func TestTLSStatus_JSONOutput(t *testing.T) { | |
| 274 | 92 | notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) | |
| 275 | 93 | ||
| 276 | - | cp := &tlsTestControlPlane{ | |
| 277 | - | getTLSStatus: func(_ context.Context) (*dto.TLSStatusResponse, error) { | |
| 278 | - | return &dto.TLSStatusResponse{ | |
| 279 | - | ACMEEnabled: true, | |
| 280 | - | ConfiguredMode: "acme", | |
| 281 | - | EffectiveMode: "acme-staging", | |
| 282 | - | TokenSource: "env", | |
| 283 | - | Certificates: []dto.TLSCertificateEntry{ | |
| 284 | - | { | |
| 285 | - | ID: "cert-abc123", | |
| 286 | - | Names: []string{"example.com", "www.example.com"}, | |
| 287 | - | Status: "valid", | |
| 288 | - | NotAfter: notAfter, | |
| 289 | - | LastError: "", | |
| 290 | - | }, | |
| 291 | - | }, | |
| 292 | - | }, nil | |
| 94 | + | cpMock := climocks.NewMockControlPlane(t) | |
| 95 | + | cpMock.EXPECT().GetTLSStatus(context.Background()).Return(&dto.TLSStatusResponse{ | |
| 96 | + | ACMEEnabled: true, | |
| 97 | + | ConfiguredMode: "acme", | |
| 98 | + | EffectiveMode: "acme-staging", | |
| 99 | + | TokenSource: "env", | |
| 100 | + | Certificates: []dto.TLSCertificateEntry{ | |
| 101 | + | { | |
| 102 | + | ID: "cert-abc123", | |
| 103 | + | Names: []string{"example.com", "www.example.com"}, | |
| 104 | + | Status: "valid", | |
| 105 | + | NotAfter: notAfter, | |
| 106 | + | LastError: "", | |
| 107 | + | }, | |
| 293 | 108 | }, | |
| 294 | - | } | |
| 109 | + | }, nil).Once() | |
| 295 | 110 | ||
| 296 | 111 | var buf bytes.Buffer | |
| 297 | - | err := runTLSStatusCmd(context.Background(), cp, &buf, true) | |
| 112 | + | err := runTLSStatusCmd(context.Background(), cpMock, &buf, true) | |
| 298 | 113 | require.NoError(t, err) | |
| 299 | 114 | ||
| 300 | 115 | var result dto.TLSStatusResponse | |
| @@ -11,229 +11,47 @@ import ( | |||||
| 11 | "github.com/stretchr/testify/require" | 11 | "github.com/stretchr/testify/require" | ||
| 12 | 12 | ||||
| 13 | "github.com/bnema/gordon/internal/adapters/dto" | 13 | "github.com/bnema/gordon/internal/adapters/dto" | ||
| 14 | - | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | 14 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" |
| 15 | - | "github.com/bnema/gordon/internal/domain" | |||
| 16 | ) | 15 | ) | ||
| 17 | 16 | ||||
| 18 | - | // tlsTestControlPlane implements ControlPlane for TLS tests. | |||
| 19 | - | type tlsTestControlPlane struct { | |||
| 20 | - | getTLSStatus func(context.Context) (*dto.TLSStatusResponse, error) | |||
| 21 | - | } | |||
| 22 | - | ||||
| 23 | - | var _ ControlPlane = (*tlsTestControlPlane)(nil) | |||
| 24 | - | ||||
| 25 | - | func (c *tlsTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) { | |||
| 26 | - | panic("unexpected call") | |||
| 27 | - | } | |||
| 28 | - | ||||
| 29 | - | func (c *tlsTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 30 | - | panic("unexpected call") | |||
| 31 | - | } | |||
| 32 | - | ||||
| 33 | - | func (c *tlsTestControlPlane) GetRoute(context.Context, string) (*domain.Route, error) { | |||
| 34 | - | panic("unexpected call") | |||
| 35 | - | } | |||
| 36 | - | ||||
| 37 | - | func (c *tlsTestControlPlane) FindRoutesByImage(context.Context, string) ([]domain.Route, error) { | |||
| 38 | - | panic("unexpected call") | |||
| 39 | - | } | |||
| 40 | - | ||||
| 41 | - | func (c *tlsTestControlPlane) AddRoute(context.Context, domain.Route) error { | |||
| 42 | - | panic("unexpected call") | |||
| 43 | - | } | |||
| 44 | - | ||||
| 45 | - | func (c *tlsTestControlPlane) UpdateRoute(context.Context, domain.Route) error { | |||
| 46 | - | panic("unexpected call") | |||
| 47 | - | } | |||
| 48 | - | ||||
| 49 | - | func (c *tlsTestControlPlane) RemoveRoute(context.Context, string) error { | |||
| 50 | - | panic("unexpected call") | |||
| 51 | - | } | |||
| 52 | - | ||||
| 53 | - | func (c *tlsTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) { | |||
| 54 | - | panic("unexpected call") | |||
| 55 | - | } | |||
| 56 | - | ||||
| 57 | - | func (c *tlsTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) { | |||
| 58 | - | panic("unexpected call") | |||
| 59 | - | } | |||
| 60 | - | ||||
| 61 | - | func (c *tlsTestControlPlane) SetSecrets(context.Context, string, map[string]string) error { | |||
| 62 | - | panic("unexpected call") | |||
| 63 | - | } | |||
| 64 | - | ||||
| 65 | - | func (c *tlsTestControlPlane) DeleteSecret(context.Context, string, string) error { | |||
| 66 | - | panic("unexpected call") | |||
| 67 | - | } | |||
| 68 | - | ||||
| 69 | - | func (c *tlsTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error { | |||
| 70 | - | panic("unexpected call") | |||
| 71 | - | } | |||
| 72 | - | ||||
| 73 | - | func (c *tlsTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error { | |||
| 74 | - | panic("unexpected call") | |||
| 75 | - | } | |||
| 76 | - | ||||
| 77 | - | func (c *tlsTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) { | |||
| 78 | - | panic("unexpected call") | |||
| 79 | - | } | |||
| 80 | - | ||||
| 81 | - | func (c *tlsTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) { | |||
| 82 | - | panic("unexpected call") | |||
| 83 | - | } | |||
| 84 | - | ||||
| 85 | - | func (c *tlsTestControlPlane) ListOrphanedAttachments(context.Context) ([]domain.CleanupAttachment, error) { | |||
| 86 | - | panic("unexpected call") | |||
| 87 | - | } | |||
| 88 | - | ||||
| 89 | - | func (c *tlsTestControlPlane) CleanupOrphanedAttachments(context.Context, string, bool) (*domain.CleanupReport, error) { | |||
| 90 | - | panic("unexpected call") | |||
| 91 | - | } | |||
| 92 | - | ||||
| 93 | - | func (c *tlsTestControlPlane) FindAttachmentTargetsByImage(context.Context, string) ([]string, error) { | |||
| 94 | - | panic("unexpected call") | |||
| 95 | - | } | |||
| 96 | - | ||||
| 97 | - | func (c *tlsTestControlPlane) AddAttachment(context.Context, string, string) error { | |||
| 98 | - | panic("unexpected call") | |||
| 99 | - | } | |||
| 100 | - | ||||
| 101 | - | func (c *tlsTestControlPlane) RemoveAttachment(context.Context, string, string) error { | |||
| 102 | - | panic("unexpected call") | |||
| 103 | - | } | |||
| 104 | - | ||||
| 105 | - | func (c *tlsTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) { | |||
| 106 | - | panic("unexpected call") | |||
| 107 | - | } | |||
| 108 | - | ||||
| 109 | - | func (c *tlsTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error { | |||
| 110 | - | panic("unexpected call") | |||
| 111 | - | } | |||
| 112 | - | ||||
| 113 | - | func (c *tlsTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error { | |||
| 114 | - | panic("unexpected call") | |||
| 115 | - | } | |||
| 116 | - | ||||
| 117 | - | func (c *tlsTestControlPlane) GetTLSStatus(ctx context.Context) (*dto.TLSStatusResponse, error) { | |||
| 118 | - | if c.getTLSStatus != nil { | |||
| 119 | - | return c.getTLSStatus(ctx) | |||
| 120 | - | } | |||
| 121 | - | return nil, nil | |||
| 122 | - | } | |||
| 123 | - | ||||
| 124 | - | func (c *tlsTestControlPlane) GetStatus(context.Context) (*remote.Status, error) { | |||
| 125 | - | panic("unexpected call") | |||
| 126 | - | } | |||
| 127 | - | ||||
| 128 | - | func (c *tlsTestControlPlane) Reload(context.Context) error { | |||
| 129 | - | panic("unexpected call") | |||
| 130 | - | } | |||
| 131 | - | ||||
| 132 | - | func (c *tlsTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) { | |||
| 133 | - | panic("unexpected call") | |||
| 134 | - | } | |||
| 135 | - | ||||
| 136 | - | func (c *tlsTestControlPlane) GetConfig(context.Context) (*remote.Config, error) { | |||
| 137 | - | panic("unexpected call") | |||
| 138 | - | } | |||
| 139 | - | ||||
| 140 | - | func (c *tlsTestControlPlane) DeployIntent(context.Context, string) error { | |||
| 141 | - | panic("unexpected call") | |||
| 142 | - | } | |||
| 143 | - | ||||
| 144 | - | func (c *tlsTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) { | |||
| 145 | - | panic("unexpected call") | |||
| 146 | - | } | |||
| 147 | - | ||||
| 148 | - | func (c *tlsTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) { | |||
| 149 | - | panic("unexpected call") | |||
| 150 | - | } | |||
| 151 | - | ||||
| 152 | - | func (c *tlsTestControlPlane) ListTags(context.Context, string) ([]string, error) { | |||
| 153 | - | panic("unexpected call") | |||
| 154 | - | } | |||
| 155 | - | ||||
| 156 | - | func (c *tlsTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) { | |||
| 157 | - | panic("unexpected call") | |||
| 158 | - | } | |||
| 159 | - | ||||
| 160 | - | func (c *tlsTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) { | |||
| 161 | - | panic("unexpected call") | |||
| 162 | - | } | |||
| 163 | - | ||||
| 164 | - | func (c *tlsTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) { | |||
| 165 | - | panic("unexpected call") | |||
| 166 | - | } | |||
| 167 | - | ||||
| 168 | - | func (c *tlsTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) { | |||
| 169 | - | panic("unexpected call") | |||
| 170 | - | } | |||
| 171 | - | ||||
| 172 | - | func (c *tlsTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) { | |||
| 173 | - | panic("unexpected call") | |||
| 174 | - | } | |||
| 175 | - | ||||
| 176 | - | func (c *tlsTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) { | |||
| 177 | - | panic("unexpected call") | |||
| 178 | - | } | |||
| 179 | - | ||||
| 180 | - | func (c *tlsTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) { | |||
| 181 | - | panic("unexpected call") | |||
| 182 | - | } | |||
| 183 | - | ||||
| 184 | - | func (c *tlsTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) { | |||
| 185 | - | panic("unexpected call") | |||
| 186 | - | } | |||
| 187 | - | ||||
| 188 | - | func (c *tlsTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) { | |||
| 189 | - | panic("unexpected call") | |||
| 190 | - | } | |||
| 191 | - | ||||
| 192 | - | func (c *tlsTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |||
| 193 | - | panic("unexpected call") | |||
| 194 | - | } | |||
| 195 | - | ||||
| 196 | func TestTLSStatus_HumanOutput(t *testing.T) { | 17 | func TestTLSStatus_HumanOutput(t *testing.T) { | ||
| 197 | notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) | 18 | notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) | ||
| 198 | 19 | ||||
| 199 | - | cp := &tlsTestControlPlane{ | 20 | + | cpMock := climocks.NewMockControlPlane(t) |
| 200 | - | getTLSStatus: func(_ context.Context) (*dto.TLSStatusResponse, error) { | 21 | + | cpMock.EXPECT().GetTLSStatus(context.Background()).Return(&dto.TLSStatusResponse{ |
| 201 | - | return &dto.TLSStatusResponse{ | 22 | + | ACMEEnabled: true, |
| 202 | - | ACMEEnabled: true, | 23 | + | ConfiguredMode: "acme", |
| 203 | - | ConfiguredMode: "acme", | 24 | + | EffectiveMode: "acme-staging", |
| 204 | - | EffectiveMode: "acme-staging", | 25 | + | SelectionReason: "configured", |
| 205 | - | SelectionReason: "configured", | 26 | + | TokenSource: "env", |
| 206 | - | TokenSource: "env", | 27 | + | Certificates: []dto.TLSCertificateEntry{ |
| 207 | - | Certificates: []dto.TLSCertificateEntry{ | 28 | + | { |
| 208 | - | { | 29 | + | ID: "cert-abc123", |
| 209 | - | ID: "cert-abc123", | 30 | + | Names: []string{"example.com", "www.example.com"}, |
| 210 | - | Names: []string{"example.com", "www.example.com"}, | 31 | + | Status: "valid", |
| 211 | - | Status: "valid", | 32 | + | NotAfter: notAfter, |
| 212 | - | NotAfter: notAfter, | 33 | + | LastError: "", |
| 213 | - | LastError: "", | 34 | + | }, |
| 214 | - | }, | 35 | + | }, |
| 215 | - | }, | 36 | + | Routes: []dto.TLSRouteCoverage{ |
| 216 | - | Routes: []dto.TLSRouteCoverage{ | 37 | + | { |
| 217 | - | { | 38 | + | Domain: "example.com", |
| 218 | - | Domain: "example.com", | 39 | + | Covered: true, |
| 219 | - | Covered: true, | 40 | + | CoveredBy: "cert-abc123", |
| 220 | - | CoveredBy: "cert-abc123", | 41 | + | RequiredACME: true, |
| 221 | - | RequiredACME: true, | 42 | + | }, |
| 222 | - | }, | 43 | + | { |
| 223 | - | { | 44 | + | Domain: "internal.local", |
| 224 | - | Domain: "internal.local", | 45 | + | Covered: false, |
| 225 | - | Covered: false, | 46 | + | RequiredACME: false, |
| 226 | - | RequiredACME: false, | 47 | + | Error: "self-signed cert", |
| 227 | - | Error: "self-signed cert", | 48 | + | }, |
| 228 | - | }, | |||
| 229 | - | }, | |||
| 230 | - | Errors: []string{"route internal.local has no ACME cert"}, | |||
| 231 | - | }, nil | |||
| 232 | }, | 49 | }, | ||
| 233 | - | } | 50 | + | Errors: []string{"route internal.local has no ACME cert"}, |
| 51 | + | }, nil).Once() | |||
| 234 | 52 | ||||
| 235 | var buf bytes.Buffer | 53 | var buf bytes.Buffer | ||
| 236 | - | err := runTLSStatusCmd(context.Background(), cp, &buf, false) | 54 | + | err := runTLSStatusCmd(context.Background(), cpMock, &buf, false) |
| 237 | require.NoError(t, err) | 55 | require.NoError(t, err) | ||
| 238 | 56 | ||||
| 239 | output := buf.String() | 57 | output := buf.String() | ||
| @@ -273,28 +91,25 @@ func TestTLSStatus_HumanOutput(t *testing.T) { | |||||
| 273 | func TestTLSStatus_JSONOutput(t *testing.T) { | 91 | func TestTLSStatus_JSONOutput(t *testing.T) { | ||
| 274 | notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) | 92 | notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) | ||
| 275 | 93 | ||||
| 276 | - | cp := &tlsTestControlPlane{ | 94 | + | cpMock := climocks.NewMockControlPlane(t) |
| 277 | - | getTLSStatus: func(_ context.Context) (*dto.TLSStatusResponse, error) { | 95 | + | cpMock.EXPECT().GetTLSStatus(context.Background()).Return(&dto.TLSStatusResponse{ |
| 278 | - | return &dto.TLSStatusResponse{ | 96 | + | ACMEEnabled: true, |
| 279 | - | ACMEEnabled: true, | 97 | + | ConfiguredMode: "acme", |
| 280 | - | ConfiguredMode: "acme", | 98 | + | EffectiveMode: "acme-staging", |
| 281 | - | EffectiveMode: "acme-staging", | 99 | + | TokenSource: "env", |
| 282 | - | TokenSource: "env", | 100 | + | Certificates: []dto.TLSCertificateEntry{ |
| 283 | - | Certificates: []dto.TLSCertificateEntry{ | 101 | + | { |
| 284 | - | { | 102 | + | ID: "cert-abc123", |
| 285 | - | ID: "cert-abc123", | 103 | + | Names: []string{"example.com", "www.example.com"}, |
| 286 | - | Names: []string{"example.com", "www.example.com"}, | 104 | + | Status: "valid", |
| 287 | - | Status: "valid", | 105 | + | NotAfter: notAfter, |
| 288 | - | NotAfter: notAfter, | 106 | + | LastError: "", |
| 289 | - | LastError: "", | 107 | + | }, |
| 290 | - | }, | |||
| 291 | - | }, | |||
| 292 | - | }, nil | |||
| 293 | }, | 108 | }, | ||
| 294 | - | } | 109 | + | }, nil).Once() |
| 295 | 110 | ||||
| 296 | var buf bytes.Buffer | 111 | var buf bytes.Buffer | ||
| 297 | - | err := runTLSStatusCmd(context.Background(), cp, &buf, true) | 112 | + | err := runTLSStatusCmd(context.Background(), cpMock, &buf, true) |
| 298 | require.NoError(t, err) | 113 | require.NoError(t, err) | ||
| 299 | 114 | ||||
| 300 | var result dto.TLSStatusResponse | 115 | var result dto.TLSStatusResponse | ||
M internal/adapters/in/cli/ui_adoption_test.go +11 -25
| @@ -12,6 +12,7 @@ import ( | |||
| 12 | 12 | "testing" | |
| 13 | 13 | ||
| 14 | 14 | "github.com/bnema/gordon/internal/adapters/dto" | |
| 15 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 17 | 18 | var uiAdoptionHelperCalls = map[string]struct{}{ | |
| @@ -54,8 +55,8 @@ func TestPresentationHelpers(t *testing.T) { | |||
| 54 | 55 | } | |
| 55 | 56 | ||
| 56 | 57 | func TestUIAdoption(t *testing.T) { | |
| 57 | - | for _, expect := range uiAdoptionExpectations { | |
| 58 | - | expect := expect | |
| 58 | + | for i := range uiAdoptionExpectations { | |
| 59 | + | expect := uiAdoptionExpectations[i] | |
| 59 | 60 | t.Run(expect.family, func(t *testing.T) { | |
| 60 | 61 | fset := token.NewFileSet() | |
| 61 | 62 | fileNode, err := parser.ParseFile(fset, expect.file, nil, parser.AllErrors) | |
| @@ -64,7 +65,6 @@ func TestUIAdoption(t *testing.T) { | |||
| 64 | 65 | } | |
| 65 | 66 | ||
| 66 | 67 | for _, fnName := range expect.functions { | |
| 67 | - | fnName := fnName | |
| 68 | 68 | t.Run(fnName, func(t *testing.T) { | |
| 69 | 69 | fn := findFuncDecl(fileNode, fnName) | |
| 70 | 70 | if fn == nil { | |
| @@ -150,26 +150,6 @@ func isForbiddenRawPrintCall(call *ast.CallExpr, sel *ast.SelectorExpr) bool { | |||
| 150 | 150 | return false | |
| 151 | 151 | } | |
| 152 | 152 | ||
| 153 | - | type imagesClientStub struct { | |
| 154 | - | listResp []dto.Image | |
| 155 | - | pruneResp *dto.ImagePruneResponse | |
| 156 | - | err error | |
| 157 | - | } | |
| 158 | - | ||
| 159 | - | func (s imagesClientStub) ListImages(context.Context) ([]dto.Image, error) { | |
| 160 | - | if s.err != nil { | |
| 161 | - | return nil, s.err | |
| 162 | - | } | |
| 163 | - | return s.listResp, nil | |
| 164 | - | } | |
| 165 | - | ||
| 166 | - | func (s imagesClientStub) PruneImages(context.Context, dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | |
| 167 | - | if s.err != nil { | |
| 168 | - | return nil, s.err | |
| 169 | - | } | |
| 170 | - | return s.pruneResp, nil | |
| 171 | - | } | |
| 172 | - | ||
| 173 | 153 | func TestUIAdoptionRuntimeSeams(t *testing.T) { | |
| 174 | 154 | uiAdoptionSeamMu.Lock() | |
| 175 | 155 | defer uiAdoptionSeamMu.Unlock() | |
| @@ -201,13 +181,19 @@ func TestUIAdoptionRuntimeSeams(t *testing.T) { | |||
| 201 | 181 | t.Fatalf("version command execution failed: %v", err) | |
| 202 | 182 | } | |
| 203 | 183 | ||
| 184 | + | imagesMock := climocks.NewMockimagesClient(t) | |
| 185 | + | imagesMock.EXPECT().ListImages(context.Background()).Return([]dto.Image{{Repository: "repo/app", Tag: "latest", ID: "sha256:abc", Size: 1}}, nil).Once() | |
| 186 | + | ||
| 204 | 187 | imgBuf := new(bytes.Buffer) | |
| 205 | - | if err := runImagesList(context.Background(), imagesClientStub{listResp: []dto.Image{{Repository: "repo/app", Tag: "latest", ID: "sha256:abc", Size: 1}}}, imgBuf, false); err != nil { | |
| 188 | + | if err := runImagesList(context.Background(), imagesMock, imgBuf, false); err != nil { | |
| 206 | 189 | t.Fatalf("runImagesList failed: %v", err) | |
| 207 | 190 | } | |
| 208 | 191 | ||
| 192 | + | pruneMock := climocks.NewMockimagesClient(t) | |
| 193 | + | pruneMock.EXPECT().ListImages(context.Background()).Return([]dto.Image{}, nil).Once() | |
| 194 | + | ||
| 209 | 195 | pruneBuf := new(bytes.Buffer) | |
| 210 | - | if err := runImagesPrune(context.Background(), imagesClientStub{listResp: []dto.Image{}, pruneResp: &dto.ImagePruneResponse{}}, imagesPruneOptions{DryRun: true}, pruneBuf); err != nil { | |
| 196 | + | if err := runImagesPrune(context.Background(), pruneMock, imagesPruneOptions{DryRun: true}, pruneBuf); err != nil { | |
| 211 | 197 | t.Fatalf("runImagesPrune failed: %v", err) | |
| 212 | 198 | } | |
| 213 | 199 | ||
| @@ -12,6 +12,7 @@ import ( | |||||
| 12 | "testing" | 12 | "testing" | ||
| 13 | 13 | ||||
| 14 | "github.com/bnema/gordon/internal/adapters/dto" | 14 | "github.com/bnema/gordon/internal/adapters/dto" | ||
| 15 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |||
| 15 | ) | 16 | ) | ||
| 16 | 17 | ||||
| 17 | var uiAdoptionHelperCalls = map[string]struct{}{ | 18 | var uiAdoptionHelperCalls = map[string]struct{}{ | ||
| @@ -54,8 +55,8 @@ func TestPresentationHelpers(t *testing.T) { | |||||
| 54 | } | 55 | } | ||
| 55 | 56 | ||||
| 56 | func TestUIAdoption(t *testing.T) { | 57 | func TestUIAdoption(t *testing.T) { | ||
| 57 | - | for _, expect := range uiAdoptionExpectations { | 58 | + | for i := range uiAdoptionExpectations { |
| 58 | - | expect := expect | 59 | + | expect := uiAdoptionExpectations[i] |
| 59 | t.Run(expect.family, func(t *testing.T) { | 60 | t.Run(expect.family, func(t *testing.T) { | ||
| 60 | fset := token.NewFileSet() | 61 | fset := token.NewFileSet() | ||
| 61 | fileNode, err := parser.ParseFile(fset, expect.file, nil, parser.AllErrors) | 62 | fileNode, err := parser.ParseFile(fset, expect.file, nil, parser.AllErrors) | ||
| @@ -64,7 +65,6 @@ func TestUIAdoption(t *testing.T) { | |||||
| 64 | } | 65 | } | ||
| 65 | 66 | ||||
| 66 | for _, fnName := range expect.functions { | 67 | for _, fnName := range expect.functions { | ||
| 67 | - | fnName := fnName | |||
| 68 | t.Run(fnName, func(t *testing.T) { | 68 | t.Run(fnName, func(t *testing.T) { | ||
| 69 | fn := findFuncDecl(fileNode, fnName) | 69 | fn := findFuncDecl(fileNode, fnName) | ||
| 70 | if fn == nil { | 70 | if fn == nil { | ||
| @@ -150,26 +150,6 @@ func isForbiddenRawPrintCall(call *ast.CallExpr, sel *ast.SelectorExpr) bool { | |||||
| 150 | return false | 150 | return false | ||
| 151 | } | 151 | } | ||
| 152 | 152 | ||||
| 153 | - | type imagesClientStub struct { | |||
| 154 | - | listResp []dto.Image | |||
| 155 | - | pruneResp *dto.ImagePruneResponse | |||
| 156 | - | err error | |||
| 157 | - | } | |||
| 158 | - | ||||
| 159 | - | func (s imagesClientStub) ListImages(context.Context) ([]dto.Image, error) { | |||
| 160 | - | if s.err != nil { | |||
| 161 | - | return nil, s.err | |||
| 162 | - | } | |||
| 163 | - | return s.listResp, nil | |||
| 164 | - | } | |||
| 165 | - | ||||
| 166 | - | func (s imagesClientStub) PruneImages(context.Context, dto.ImagePruneRequest) (*dto.ImagePruneResponse, error) { | |||
| 167 | - | if s.err != nil { | |||
| 168 | - | return nil, s.err | |||
| 169 | - | } | |||
| 170 | - | return s.pruneResp, nil | |||
| 171 | - | } | |||
| 172 | - | ||||
| 173 | func TestUIAdoptionRuntimeSeams(t *testing.T) { | 153 | func TestUIAdoptionRuntimeSeams(t *testing.T) { | ||
| 174 | uiAdoptionSeamMu.Lock() | 154 | uiAdoptionSeamMu.Lock() | ||
| 175 | defer uiAdoptionSeamMu.Unlock() | 155 | defer uiAdoptionSeamMu.Unlock() | ||
| @@ -201,13 +181,19 @@ func TestUIAdoptionRuntimeSeams(t *testing.T) { | |||||
| 201 | t.Fatalf("version command execution failed: %v", err) | 181 | t.Fatalf("version command execution failed: %v", err) | ||
| 202 | } | 182 | } | ||
| 203 | 183 | ||||
| 184 | + | imagesMock := climocks.NewMockimagesClient(t) | |||
| 185 | + | imagesMock.EXPECT().ListImages(context.Background()).Return([]dto.Image{{Repository: "repo/app", Tag: "latest", ID: "sha256:abc", Size: 1}}, nil).Once() | |||
| 186 | + | ||||
| 204 | imgBuf := new(bytes.Buffer) | 187 | imgBuf := new(bytes.Buffer) | ||
| 205 | - | if err := runImagesList(context.Background(), imagesClientStub{listResp: []dto.Image{{Repository: "repo/app", Tag: "latest", ID: "sha256:abc", Size: 1}}}, imgBuf, false); err != nil { | 188 | + | if err := runImagesList(context.Background(), imagesMock, imgBuf, false); err != nil { |
| 206 | t.Fatalf("runImagesList failed: %v", err) | 189 | t.Fatalf("runImagesList failed: %v", err) | ||
| 207 | } | 190 | } | ||
| 208 | 191 | ||||
| 192 | + | pruneMock := climocks.NewMockimagesClient(t) | |||
| 193 | + | pruneMock.EXPECT().ListImages(context.Background()).Return([]dto.Image{}, nil).Once() | |||
| 194 | + | ||||
| 209 | pruneBuf := new(bytes.Buffer) | 195 | pruneBuf := new(bytes.Buffer) | ||
| 210 | - | if err := runImagesPrune(context.Background(), imagesClientStub{listResp: []dto.Image{}, pruneResp: &dto.ImagePruneResponse{}}, imagesPruneOptions{DryRun: true}, pruneBuf); err != nil { | 196 | + | if err := runImagesPrune(context.Background(), pruneMock, imagesPruneOptions{DryRun: true}, pruneBuf); err != nil { |
| 211 | t.Fatalf("runImagesPrune failed: %v", err) | 197 | t.Fatalf("runImagesPrune failed: %v", err) | ||
| 212 | } | 198 | } | ||
| 213 | 199 | ||||
M internal/adapters/in/cli/volumes_test.go +31 -53
| @@ -6,91 +6,69 @@ import ( | |||
| 6 | 6 | "testing" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/stretchr/testify/assert" | |
| 9 | + | "github.com/stretchr/testify/mock" | |
| 9 | 10 | "github.com/stretchr/testify/require" | |
| 10 | 11 | ||
| 11 | 12 | "github.com/bnema/gordon/internal/adapters/dto" | |
| 13 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 12 | 14 | ) | |
| 13 | 15 | ||
| 14 | - | type fakeVolumesClient struct { | |
| 15 | - | listResp []dto.Volume | |
| 16 | - | pruneResp *dto.VolumePruneResponse | |
| 17 | - | listErr error | |
| 18 | - | pruneErr error | |
| 19 | - | ||
| 20 | - | pruneCalls []dto.VolumePruneRequest | |
| 21 | - | } | |
| 22 | - | ||
| 23 | - | func (f *fakeVolumesClient) ListVolumes(_ context.Context) ([]dto.Volume, error) { | |
| 24 | - | return f.listResp, f.listErr | |
| 25 | - | } | |
| 26 | - | ||
| 27 | - | func (f *fakeVolumesClient) PruneVolumes(_ context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |
| 28 | - | f.pruneCalls = append(f.pruneCalls, req) | |
| 29 | - | return f.pruneResp, f.pruneErr | |
| 30 | - | } | |
| 31 | - | ||
| 32 | 16 | func TestRunVolumesList_ShowsVolumes(t *testing.T) { | |
| 33 | - | client := &fakeVolumesClient{ | |
| 34 | - | listResp: []dto.Volume{ | |
| 35 | - | {Name: "db-data", InUse: true, Containers: []string{"postgres"}, Size: 1024 * 1024}, | |
| 36 | - | {Name: "orphaned-vol", InUse: false, Size: 512}, | |
| 37 | - | }, | |
| 38 | - | } | |
| 17 | + | clientMock := climocks.NewMockvolumesClient(t) | |
| 18 | + | clientMock.EXPECT().ListVolumes(context.Background()).Return([]dto.Volume{ | |
| 19 | + | {Name: "db-data", InUse: true, Containers: []string{"postgres"}, Size: 1024 * 1024}, | |
| 20 | + | {Name: "orphaned-vol", InUse: false, Size: 512}, | |
| 21 | + | }, nil).Once() | |
| 39 | 22 | ||
| 40 | 23 | var out bytes.Buffer | |
| 41 | - | err := runVolumesList(context.Background(), client, &out, false) | |
| 24 | + | err := runVolumesList(context.Background(), clientMock, &out, false) | |
| 42 | 25 | require.NoError(t, err) | |
| 43 | 26 | assert.Contains(t, out.String(), "db-data") | |
| 44 | 27 | assert.Contains(t, out.String(), "orphaned-vol") | |
| 45 | 28 | } | |
| 46 | 29 | ||
| 47 | 30 | func TestRunVolumesList_JSON(t *testing.T) { | |
| 48 | - | client := &fakeVolumesClient{ | |
| 49 | - | listResp: []dto.Volume{ | |
| 50 | - | {Name: "vol1", InUse: true}, | |
| 51 | - | }, | |
| 52 | - | } | |
| 31 | + | clientMock := climocks.NewMockvolumesClient(t) | |
| 32 | + | clientMock.EXPECT().ListVolumes(context.Background()).Return([]dto.Volume{ | |
| 33 | + | {Name: "vol1", InUse: true}, | |
| 34 | + | }, nil).Once() | |
| 53 | 35 | ||
| 54 | 36 | var out bytes.Buffer | |
| 55 | - | err := runVolumesList(context.Background(), client, &out, true) | |
| 37 | + | err := runVolumesList(context.Background(), clientMock, &out, true) | |
| 56 | 38 | require.NoError(t, err) | |
| 57 | 39 | assert.Contains(t, out.String(), `"name"`) | |
| 58 | 40 | } | |
| 59 | 41 | ||
| 60 | 42 | func TestRunVolumesPrune_JSONExecutesActualPrune(t *testing.T) { | |
| 61 | - | client := &fakeVolumesClient{ | |
| 62 | - | pruneResp: &dto.VolumePruneResponse{ | |
| 63 | - | VolumesRemoved: 1, | |
| 64 | - | SpaceReclaimed: 1024, | |
| 65 | - | Volumes: []dto.Volume{{Name: "orphan1", Size: 1024}}, | |
| 66 | - | }, | |
| 43 | + | clientMock := climocks.NewMockvolumesClient(t) | |
| 44 | + | resp := &dto.VolumePruneResponse{ | |
| 45 | + | VolumesRemoved: 1, | |
| 46 | + | SpaceReclaimed: 1024, | |
| 47 | + | Volumes: []dto.Volume{{Name: "orphan1", Size: 1024}}, | |
| 67 | 48 | } | |
| 49 | + | previewCall := clientMock.EXPECT().PruneVolumes(context.Background(), dto.VolumePruneRequest{DryRun: true}).Return(resp, nil).Once() | |
| 50 | + | pruneCall := clientMock.EXPECT().PruneVolumes(context.Background(), dto.VolumePruneRequest{DryRun: false}).Return(resp, nil).Once() | |
| 51 | + | mock.InOrder(previewCall, pruneCall) | |
| 68 | 52 | ||
| 69 | 53 | var out bytes.Buffer | |
| 70 | - | err := runVolumesPrune(context.Background(), client, volumesPruneOptions{Json: true}, &out) | |
| 54 | + | err := runVolumesPrune(context.Background(), clientMock, volumesPruneOptions{Json: true}, &out) | |
| 71 | 55 | require.NoError(t, err) | |
| 72 | - | ||
| 73 | - | // Should have called PruneVolumes twice: once dry-run (preview), once actual | |
| 74 | - | require.Len(t, client.pruneCalls, 2) | |
| 75 | - | assert.True(t, client.pruneCalls[0].DryRun, "first call should be dry-run preview") | |
| 76 | - | assert.False(t, client.pruneCalls[1].DryRun, "second call should be actual prune") | |
| 77 | 56 | assert.Contains(t, out.String(), `"volumes_removed"`) | |
| 78 | 57 | } | |
| 79 | 58 | ||
| 80 | 59 | func TestRunVolumesPrune_DryRun(t *testing.T) { | |
| 81 | - | client := &fakeVolumesClient{ | |
| 82 | - | pruneResp: &dto.VolumePruneResponse{ | |
| 83 | - | VolumesRemoved: 2, | |
| 84 | - | SpaceReclaimed: 4096, | |
| 85 | - | Volumes: []dto.Volume{ | |
| 86 | - | {Name: "orphan1", Size: 2048}, | |
| 87 | - | {Name: "orphan2", Size: 2048}, | |
| 88 | - | }, | |
| 60 | + | clientMock := climocks.NewMockvolumesClient(t) | |
| 61 | + | clientMock.EXPECT().PruneVolumes(context.Background(), dto.VolumePruneRequest{DryRun: true}).Return(&dto.VolumePruneResponse{ | |
| 62 | + | VolumesRemoved: 2, | |
| 63 | + | SpaceReclaimed: 4096, | |
| 64 | + | Volumes: []dto.Volume{ | |
| 65 | + | {Name: "orphan1", Size: 2048}, | |
| 66 | + | {Name: "orphan2", Size: 2048}, | |
| 89 | 67 | }, | |
| 90 | - | } | |
| 68 | + | }, nil).Once() | |
| 91 | 69 | ||
| 92 | 70 | var out bytes.Buffer | |
| 93 | - | err := runVolumesPrune(context.Background(), client, volumesPruneOptions{DryRun: true}, &out) | |
| 71 | + | err := runVolumesPrune(context.Background(), clientMock, volumesPruneOptions{DryRun: true}, &out) | |
| 94 | 72 | require.NoError(t, err) | |
| 95 | 73 | assert.Contains(t, out.String(), "orphan1") | |
| 96 | 74 | } | |
| @@ -6,91 +6,69 @@ import ( | |||||
| 6 | "testing" | 6 | "testing" | ||
| 7 | 7 | ||||
| 8 | "github.com/stretchr/testify/assert" | 8 | "github.com/stretchr/testify/assert" | ||
| 9 | + | "github.com/stretchr/testify/mock" | |||
| 9 | "github.com/stretchr/testify/require" | 10 | "github.com/stretchr/testify/require" | ||
| 10 | 11 | ||||
| 11 | "github.com/bnema/gordon/internal/adapters/dto" | 12 | "github.com/bnema/gordon/internal/adapters/dto" | ||
| 13 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |||
| 12 | ) | 14 | ) | ||
| 13 | 15 | ||||
| 14 | - | type fakeVolumesClient struct { | |||
| 15 | - | listResp []dto.Volume | |||
| 16 | - | pruneResp *dto.VolumePruneResponse | |||
| 17 | - | listErr error | |||
| 18 | - | pruneErr error | |||
| 19 | - | ||||
| 20 | - | pruneCalls []dto.VolumePruneRequest | |||
| 21 | - | } | |||
| 22 | - | ||||
| 23 | - | func (f *fakeVolumesClient) ListVolumes(_ context.Context) ([]dto.Volume, error) { | |||
| 24 | - | return f.listResp, f.listErr | |||
| 25 | - | } | |||
| 26 | - | ||||
| 27 | - | func (f *fakeVolumesClient) PruneVolumes(_ context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) { | |||
| 28 | - | f.pruneCalls = append(f.pruneCalls, req) | |||
| 29 | - | return f.pruneResp, f.pruneErr | |||
| 30 | - | } | |||
| 31 | - | ||||
| 32 | func TestRunVolumesList_ShowsVolumes(t *testing.T) { | 16 | func TestRunVolumesList_ShowsVolumes(t *testing.T) { | ||
| 33 | - | client := &fakeVolumesClient{ | 17 | + | clientMock := climocks.NewMockvolumesClient(t) |
| 34 | - | listResp: []dto.Volume{ | 18 | + | clientMock.EXPECT().ListVolumes(context.Background()).Return([]dto.Volume{ |
| 35 | - | {Name: "db-data", InUse: true, Containers: []string{"postgres"}, Size: 1024 * 1024}, | 19 | + | {Name: "db-data", InUse: true, Containers: []string{"postgres"}, Size: 1024 * 1024}, |
| 36 | - | {Name: "orphaned-vol", InUse: false, Size: 512}, | 20 | + | {Name: "orphaned-vol", InUse: false, Size: 512}, |
| 37 | - | }, | 21 | + | }, nil).Once() |
| 38 | - | } | |||
| 39 | 22 | ||||
| 40 | var out bytes.Buffer | 23 | var out bytes.Buffer | ||
| 41 | - | err := runVolumesList(context.Background(), client, &out, false) | 24 | + | err := runVolumesList(context.Background(), clientMock, &out, false) |
| 42 | require.NoError(t, err) | 25 | require.NoError(t, err) | ||
| 43 | assert.Contains(t, out.String(), "db-data") | 26 | assert.Contains(t, out.String(), "db-data") | ||
| 44 | assert.Contains(t, out.String(), "orphaned-vol") | 27 | assert.Contains(t, out.String(), "orphaned-vol") | ||
| 45 | } | 28 | } | ||
| 46 | 29 | ||||
| 47 | func TestRunVolumesList_JSON(t *testing.T) { | 30 | func TestRunVolumesList_JSON(t *testing.T) { | ||
| 48 | - | client := &fakeVolumesClient{ | 31 | + | clientMock := climocks.NewMockvolumesClient(t) |
| 49 | - | listResp: []dto.Volume{ | 32 | + | clientMock.EXPECT().ListVolumes(context.Background()).Return([]dto.Volume{ |
| 50 | - | {Name: "vol1", InUse: true}, | 33 | + | {Name: "vol1", InUse: true}, |
| 51 | - | }, | 34 | + | }, nil).Once() |
| 52 | - | } | |||
| 53 | 35 | ||||
| 54 | var out bytes.Buffer | 36 | var out bytes.Buffer | ||
| 55 | - | err := runVolumesList(context.Background(), client, &out, true) | 37 | + | err := runVolumesList(context.Background(), clientMock, &out, true) |
| 56 | require.NoError(t, err) | 38 | require.NoError(t, err) | ||
| 57 | assert.Contains(t, out.String(), `"name"`) | 39 | assert.Contains(t, out.String(), `"name"`) | ||
| 58 | } | 40 | } | ||
| 59 | 41 | ||||
| 60 | func TestRunVolumesPrune_JSONExecutesActualPrune(t *testing.T) { | 42 | func TestRunVolumesPrune_JSONExecutesActualPrune(t *testing.T) { | ||
| 61 | - | client := &fakeVolumesClient{ | 43 | + | clientMock := climocks.NewMockvolumesClient(t) |
| 62 | - | pruneResp: &dto.VolumePruneResponse{ | 44 | + | resp := &dto.VolumePruneResponse{ |
| 63 | - | VolumesRemoved: 1, | 45 | + | VolumesRemoved: 1, |
| 64 | - | SpaceReclaimed: 1024, | 46 | + | SpaceReclaimed: 1024, |
| 65 | - | Volumes: []dto.Volume{{Name: "orphan1", Size: 1024}}, | 47 | + | Volumes: []dto.Volume{{Name: "orphan1", Size: 1024}}, |
| 66 | - | }, | |||
| 67 | } | 48 | } | ||
| 49 | + | previewCall := clientMock.EXPECT().PruneVolumes(context.Background(), dto.VolumePruneRequest{DryRun: true}).Return(resp, nil).Once() | |||
| 50 | + | pruneCall := clientMock.EXPECT().PruneVolumes(context.Background(), dto.VolumePruneRequest{DryRun: false}).Return(resp, nil).Once() | |||
| 51 | + | mock.InOrder(previewCall, pruneCall) | |||
| 68 | 52 | ||||
| 69 | var out bytes.Buffer | 53 | var out bytes.Buffer | ||
| 70 | - | err := runVolumesPrune(context.Background(), client, volumesPruneOptions{Json: true}, &out) | 54 | + | err := runVolumesPrune(context.Background(), clientMock, volumesPruneOptions{Json: true}, &out) |
| 71 | require.NoError(t, err) | 55 | require.NoError(t, err) | ||
| 72 | - | ||||
| 73 | - | // Should have called PruneVolumes twice: once dry-run (preview), once actual | |||
| 74 | - | require.Len(t, client.pruneCalls, 2) | |||
| 75 | - | assert.True(t, client.pruneCalls[0].DryRun, "first call should be dry-run preview") | |||
| 76 | - | assert.False(t, client.pruneCalls[1].DryRun, "second call should be actual prune") | |||
| 77 | assert.Contains(t, out.String(), `"volumes_removed"`) | 56 | assert.Contains(t, out.String(), `"volumes_removed"`) | ||
| 78 | } | 57 | } | ||
| 79 | 58 | ||||
| 80 | func TestRunVolumesPrune_DryRun(t *testing.T) { | 59 | func TestRunVolumesPrune_DryRun(t *testing.T) { | ||
| 81 | - | client := &fakeVolumesClient{ | 60 | + | clientMock := climocks.NewMockvolumesClient(t) |
| 82 | - | pruneResp: &dto.VolumePruneResponse{ | 61 | + | clientMock.EXPECT().PruneVolumes(context.Background(), dto.VolumePruneRequest{DryRun: true}).Return(&dto.VolumePruneResponse{ |
| 83 | - | VolumesRemoved: 2, | 62 | + | VolumesRemoved: 2, |
| 84 | - | SpaceReclaimed: 4096, | 63 | + | SpaceReclaimed: 4096, |
| 85 | - | Volumes: []dto.Volume{ | 64 | + | Volumes: []dto.Volume{ |
| 86 | - | {Name: "orphan1", Size: 2048}, | 65 | + | {Name: "orphan1", Size: 2048}, |
| 87 | - | {Name: "orphan2", Size: 2048}, | 66 | + | {Name: "orphan2", Size: 2048}, |
| 88 | - | }, | |||
| 89 | }, | 67 | }, | ||
| 90 | - | } | 68 | + | }, nil).Once() |
| 91 | 69 | ||||
| 92 | var out bytes.Buffer | 70 | var out bytes.Buffer | ||
| 93 | - | err := runVolumesPrune(context.Background(), client, volumesPruneOptions{DryRun: true}, &out) | 71 | + | err := runVolumesPrune(context.Background(), clientMock, volumesPruneOptions{DryRun: true}, &out) |
| 94 | require.NoError(t, err) | 72 | require.NoError(t, err) | ||
| 95 | assert.Contains(t, out.String(), "orphan1") | 73 | assert.Contains(t, out.String(), "orphan1") | ||
| 96 | } | 74 | } | ||