1package cli
2
3import (
4 "bytes"
5 "context"
6 "testing"
7
8 "github.com/bnema/gordon/internal/adapters/dto"
9 climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks"
10 "github.com/bnema/gordon/internal/adapters/in/cli/remote"
11 "github.com/bnema/gordon/internal/domain"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/mock"
14 "github.com/stretchr/testify/require"
15)
16
17type attachmentPushTestControlPlane struct {
18 findAttachmentTargets func(context.Context, string) ([]string, error)
19 getStatus func(context.Context) (*remote.Status, error)
20}
21
22var _ ControlPlane = (*attachmentPushTestControlPlane)(nil)
23
24func (c *attachmentPushTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) {
25 panic("unexpected call")
26}
27
28func (c *attachmentPushTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) {
29 panic("unexpected call")
30}
31
32func (c *attachmentPushTestControlPlane) GetRoute(context.Context, string) (*domain.Route, error) {
33 panic("unexpected call")
34}
35
36func (c *attachmentPushTestControlPlane) FindRoutesByImage(context.Context, string) ([]domain.Route, error) {
37 panic("unexpected call")
38}
39
40func (c *attachmentPushTestControlPlane) AddRoute(context.Context, domain.Route) error {
41 panic("unexpected call")
42}
43
44func (c *attachmentPushTestControlPlane) UpdateRoute(context.Context, domain.Route) error {
45 panic("unexpected call")
46}
47
48func (c *attachmentPushTestControlPlane) RemoveRoute(context.Context, string) error {
49 panic("unexpected call")
50}
51
52func (c *attachmentPushTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) {
53 panic("unexpected call")
54}
55
56func (c *attachmentPushTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) {
57 panic("unexpected call")
58}
59
60func (c *attachmentPushTestControlPlane) SetSecrets(context.Context, string, map[string]string) error {
61 panic("unexpected call")
62}
63
64func (c *attachmentPushTestControlPlane) DeleteSecret(context.Context, string, string) error {
65 panic("unexpected call")
66}
67
68func (c *attachmentPushTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error {
69 panic("unexpected call")
70}
71
72func (c *attachmentPushTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error {
73 panic("unexpected call")
74}
75
76func (c *attachmentPushTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) {
77 panic("unexpected call")
78}
79
80func (c *attachmentPushTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) {
81 panic("unexpected call")
82}
83
84func (c *attachmentPushTestControlPlane) FindAttachmentTargetsByImage(ctx context.Context, imageName string) ([]string, error) {
85 if c.findAttachmentTargets != nil {
86 return c.findAttachmentTargets(ctx, imageName)
87 }
88 return nil, nil
89}
90
91func (c *attachmentPushTestControlPlane) AddAttachment(context.Context, string, string) error {
92 panic("unexpected call")
93}
94
95func (c *attachmentPushTestControlPlane) RemoveAttachment(context.Context, string, string) error {
96 panic("unexpected call")
97}
98
99func (c *attachmentPushTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) {
100 panic("unexpected call")
101}
102
103func (c *attachmentPushTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error {
104 panic("unexpected call")
105}
106
107func (c *attachmentPushTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error {
108 panic("unexpected call")
109}
110
111func (c *attachmentPushTestControlPlane) GetTLSStatus(context.Context) (*dto.TLSStatusResponse, error) {
112 return &dto.TLSStatusResponse{}, nil
113}
114
115func (c *attachmentPushTestControlPlane) GetStatus(ctx context.Context) (*remote.Status, error) {
116 if c.getStatus != nil {
117 return c.getStatus(ctx)
118 }
119 panic("unexpected call")
120}
121
122func (c *attachmentPushTestControlPlane) Reload(context.Context) error {
123 panic("unexpected call")
124}
125
126func (c *attachmentPushTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) {
127 panic("unexpected call")
128}
129
130func (c *attachmentPushTestControlPlane) GetConfig(context.Context) (*remote.Config, error) {
131 panic("unexpected call")
132}
133
134func (c *attachmentPushTestControlPlane) DeployIntent(context.Context, string) error {
135 panic("unexpected call")
136}
137
138func (c *attachmentPushTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) {
139 panic("unexpected call")
140}
141
142func (c *attachmentPushTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) {
143 panic("unexpected call")
144}
145
146func (c *attachmentPushTestControlPlane) ListTags(context.Context, string) ([]string, error) {
147 panic("unexpected call")
148}
149
150func (c *attachmentPushTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) {
151 panic("unexpected call")
152}
153
154func (c *attachmentPushTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) {
155 panic("unexpected call")
156}
157
158func (c *attachmentPushTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) {
159 panic("unexpected call")
160}
161
162func (c *attachmentPushTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) {
163 panic("unexpected call")
164}
165
166func (c *attachmentPushTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) {
167 panic("unexpected call")
168}
169
170func (c *attachmentPushTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) {
171 panic("unexpected call")
172}
173
174func (c *attachmentPushTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) {
175 panic("unexpected call")
176}
177
178func (c *attachmentPushTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) {
179 panic("unexpected call")
180}
181
182func (c *attachmentPushTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) {
183 panic("unexpected call")
184}
185
186func (c *attachmentPushTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) {
187 panic("unexpected call")
188}
189
190func TestResolveAttachmentImage_OneTarget(t *testing.T) {
191 cp := &attachmentPushTestControlPlane{
192 findAttachmentTargets: func(context.Context, string) ([]string, error) {
193 return []string{"app.example.com"}, nil
194 },
195 getStatus: func(context.Context) (*remote.Status, error) {
196 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
197 },
198 }
199
200 registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "postgres")
201
202 require.NoError(t, err)
203 assert.Equal(t, "registry.example.com", registry)
204 assert.Equal(t, "postgres", imageName)
205 assert.Equal(t, []string{"app.example.com"}, targets)
206}
207
208func TestResolveAttachmentImage_MultipleTargets(t *testing.T) {
209 cp := &attachmentPushTestControlPlane{
210 findAttachmentTargets: func(context.Context, string) ([]string, error) {
211 return []string{"app.example.com", "backend"}, nil
212 },
213 getStatus: func(context.Context) (*remote.Status, error) {
214 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
215 },
216 }
217
218 registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "redis")
219
220 require.NoError(t, err)
221 assert.Equal(t, "registry.example.com", registry)
222 assert.Equal(t, "redis", imageName)
223 assert.Equal(t, []string{"app.example.com", "backend"}, targets)
224}
225
226func TestResolveAttachmentImage_NotConfigured(t *testing.T) {
227 cp := &attachmentPushTestControlPlane{
228 findAttachmentTargets: func(context.Context, string) ([]string, error) {
229 return nil, nil
230 },
231 }
232
233 _, _, _, err := resolveAttachmentImage(context.Background(), cp, "postgres")
234
235 require.Error(t, err)
236 assert.Contains(t, err.Error(), "not configured as an attachment")
237}
238
239func TestAttachmentPushCmd_NoDeploy(t *testing.T) {
240 origResolveControlPlane := resolveControlPlaneFn
241 origDetermineVersion := determineVersionFn
242 origNewImageOps := newImageOpsFn
243 t.Cleanup(func() {
244 resolveControlPlaneFn = origResolveControlPlane
245 determineVersionFn = origDetermineVersion
246 newImageOpsFn = origNewImageOps
247 })
248
249 cp := &attachmentPushTestControlPlane{
250 findAttachmentTargets: func(context.Context, string) ([]string, error) {
251 return []string{"app.example.com"}, nil
252 },
253 getStatus: func(context.Context) (*remote.Status, error) {
254 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
255 },
256 }
257
258 resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) {
259 return &controlPlaneHandle{plane: cp}, nil
260 }
261 determineVersionFn = func(context.Context, string) string { return "v1.2.3" }
262 pushCalled := false
263 newImageOpsFn = func() (pushImageOps, error) {
264 m := climocks.NewMockpushImageOps(t)
265 m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil)
266 m.On("Exists", mock.Anything, mock.Anything).Return(true, nil)
267 m.On("Push", mock.Anything, mock.Anything).Run(func(mock.Arguments) {
268 pushCalled = true
269 }).Return(nil)
270 return m, nil
271 }
272
273 cmd := newAttachmentsPushCmd()
274 out := new(bytes.Buffer)
275 cmd.SetOut(out)
276 cmd.SetErr(out)
277 cmd.SetArgs([]string{"postgres"})
278
279 err := cmd.Execute()
280
281 require.NoError(t, err)
282 assert.True(t, pushCalled)
283 assert.Contains(t, out.String(), "Push complete")
284}
285
286func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) {
287 origResolveControlPlane := resolveControlPlaneFn
288 origDetermineVersion := determineVersionFn
289 origNewImageOps := newImageOpsFn
290 t.Cleanup(func() {
291 resolveControlPlaneFn = origResolveControlPlane
292 determineVersionFn = origDetermineVersion
293 newImageOpsFn = origNewImageOps
294 })
295
296 cp := &attachmentPushTestControlPlane{
297 findAttachmentTargets: func(context.Context, string) ([]string, error) {
298 return []string{"app.example.com"}, nil
299 },
300 getStatus: func(context.Context) (*remote.Status, error) {
301 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
302 },
303 }
304
305 resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) {
306 return &controlPlaneHandle{plane: cp}, nil
307 }
308 determineVersionFn = func(context.Context, string) string { return "v1.2.3" }
309
310 var gotVersionRef string
311 var gotLatestRef string
312 newImageOpsFn = func() (pushImageOps, error) {
313 m := climocks.NewMockpushImageOps(t)
314 m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil)
315 m.On("Exists", mock.Anything, mock.Anything).Return(true, nil)
316 m.On("Push", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
317 ref := args.Get(1).(string)
318 if gotVersionRef == "" {
319 gotVersionRef = ref
320 } else if gotLatestRef == "" {
321 gotLatestRef = ref
322 }
323 }).Return(nil)
324 return m, nil
325 }
326
327 cmd := newAttachmentsPushCmd()
328 cmd.SetOut(new(bytes.Buffer))
329 cmd.SetErr(new(bytes.Buffer))
330 cmd.SetArgs([]string{"postgres:18"})
331
332 err := cmd.Execute()
333
334 require.NoError(t, err)
335 assert.Equal(t, "registry.example.com/postgres:v1.2.3", gotVersionRef)
336 assert.Equal(t, "registry.example.com/postgres:latest", gotLatestRef)
337}
338
339func TestNormalizeAttachmentImageName_PreservesNamespaceWithoutRegistryHost(t *testing.T) {
340 got := normalizeAttachmentImageName("myorg/postgres:18")
341
342 assert.Equal(t, "myorg/postgres", got)
343}