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) GetStatus(ctx context.Context) (*remote.Status, error) {
112 if c.getStatus != nil {
113 return c.getStatus(ctx)
114 }
115 panic("unexpected call")
116}
117
118func (c *attachmentPushTestControlPlane) Reload(context.Context) error {
119 panic("unexpected call")
120}
121
122func (c *attachmentPushTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) {
123 panic("unexpected call")
124}
125
126func (c *attachmentPushTestControlPlane) GetConfig(context.Context) (*remote.Config, error) {
127 panic("unexpected call")
128}
129
130func (c *attachmentPushTestControlPlane) DeployIntent(context.Context, string) error {
131 panic("unexpected call")
132}
133
134func (c *attachmentPushTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) {
135 panic("unexpected call")
136}
137
138func (c *attachmentPushTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) {
139 panic("unexpected call")
140}
141
142func (c *attachmentPushTestControlPlane) ListTags(context.Context, string) ([]string, error) {
143 panic("unexpected call")
144}
145
146func (c *attachmentPushTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) {
147 panic("unexpected call")
148}
149
150func (c *attachmentPushTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) {
151 panic("unexpected call")
152}
153
154func (c *attachmentPushTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) {
155 panic("unexpected call")
156}
157
158func (c *attachmentPushTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) {
159 panic("unexpected call")
160}
161
162func (c *attachmentPushTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) {
163 panic("unexpected call")
164}
165
166func (c *attachmentPushTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) {
167 panic("unexpected call")
168}
169
170func (c *attachmentPushTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) {
171 panic("unexpected call")
172}
173
174func (c *attachmentPushTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) {
175 panic("unexpected call")
176}
177
178func (c *attachmentPushTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) {
179 panic("unexpected call")
180}
181
182func (c *attachmentPushTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) {
183 panic("unexpected call")
184}
185
186func TestResolveAttachmentImage_OneTarget(t *testing.T) {
187 cp := &attachmentPushTestControlPlane{
188 findAttachmentTargets: func(context.Context, string) ([]string, error) {
189 return []string{"app.example.com"}, nil
190 },
191 getStatus: func(context.Context) (*remote.Status, error) {
192 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
193 },
194 }
195
196 registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "postgres")
197
198 require.NoError(t, err)
199 assert.Equal(t, "registry.example.com", registry)
200 assert.Equal(t, "postgres", imageName)
201 assert.Equal(t, []string{"app.example.com"}, targets)
202}
203
204func TestResolveAttachmentImage_MultipleTargets(t *testing.T) {
205 cp := &attachmentPushTestControlPlane{
206 findAttachmentTargets: func(context.Context, string) ([]string, error) {
207 return []string{"app.example.com", "backend"}, nil
208 },
209 getStatus: func(context.Context) (*remote.Status, error) {
210 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
211 },
212 }
213
214 registry, imageName, targets, err := resolveAttachmentImage(context.Background(), cp, "redis")
215
216 require.NoError(t, err)
217 assert.Equal(t, "registry.example.com", registry)
218 assert.Equal(t, "redis", imageName)
219 assert.Equal(t, []string{"app.example.com", "backend"}, targets)
220}
221
222func TestResolveAttachmentImage_NotConfigured(t *testing.T) {
223 cp := &attachmentPushTestControlPlane{
224 findAttachmentTargets: func(context.Context, string) ([]string, error) {
225 return nil, nil
226 },
227 }
228
229 _, _, _, err := resolveAttachmentImage(context.Background(), cp, "postgres")
230
231 require.Error(t, err)
232 assert.Contains(t, err.Error(), "not configured as an attachment")
233}
234
235func TestAttachmentPushCmd_NoDeploy(t *testing.T) {
236 origResolveControlPlane := resolveControlPlaneFn
237 origDetermineVersion := determineVersionFn
238 origNewImageOps := newImageOpsFn
239 t.Cleanup(func() {
240 resolveControlPlaneFn = origResolveControlPlane
241 determineVersionFn = origDetermineVersion
242 newImageOpsFn = origNewImageOps
243 })
244
245 cp := &attachmentPushTestControlPlane{
246 findAttachmentTargets: func(context.Context, string) ([]string, error) {
247 return []string{"app.example.com"}, nil
248 },
249 getStatus: func(context.Context) (*remote.Status, error) {
250 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
251 },
252 }
253
254 resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) {
255 return &controlPlaneHandle{plane: cp}, nil
256 }
257 determineVersionFn = func(context.Context, string) string { return "v1.2.3" }
258 pushCalled := false
259 newImageOpsFn = func() (pushImageOps, error) {
260 m := climocks.NewMockpushImageOps(t)
261 m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil)
262 m.On("Exists", mock.Anything, mock.Anything).Return(true, nil)
263 m.On("Push", mock.Anything, mock.Anything).Run(func(mock.Arguments) {
264 pushCalled = true
265 }).Return(nil)
266 return m, nil
267 }
268
269 cmd := newAttachmentsPushCmd()
270 out := new(bytes.Buffer)
271 cmd.SetOut(out)
272 cmd.SetErr(out)
273 cmd.SetArgs([]string{"postgres"})
274
275 err := cmd.Execute()
276
277 require.NoError(t, err)
278 assert.True(t, pushCalled)
279 assert.Contains(t, out.String(), "Push complete")
280}
281
282func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) {
283 origResolveControlPlane := resolveControlPlaneFn
284 origDetermineVersion := determineVersionFn
285 origNewImageOps := newImageOpsFn
286 t.Cleanup(func() {
287 resolveControlPlaneFn = origResolveControlPlane
288 determineVersionFn = origDetermineVersion
289 newImageOpsFn = origNewImageOps
290 })
291
292 cp := &attachmentPushTestControlPlane{
293 findAttachmentTargets: func(context.Context, string) ([]string, error) {
294 return []string{"app.example.com"}, nil
295 },
296 getStatus: func(context.Context) (*remote.Status, error) {
297 return &remote.Status{RegistryDomain: "registry.example.com"}, nil
298 },
299 }
300
301 resolveControlPlaneFn = func(string) (*controlPlaneHandle, error) {
302 return &controlPlaneHandle{plane: cp}, nil
303 }
304 determineVersionFn = func(context.Context, string) string { return "v1.2.3" }
305
306 var gotVersionRef string
307 var gotLatestRef string
308 newImageOpsFn = func() (pushImageOps, error) {
309 m := climocks.NewMockpushImageOps(t)
310 m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil)
311 m.On("Exists", mock.Anything, mock.Anything).Return(true, nil)
312 m.On("Push", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
313 ref := args.Get(1).(string)
314 if gotVersionRef == "" {
315 gotVersionRef = ref
316 } else if gotLatestRef == "" {
317 gotLatestRef = ref
318 }
319 }).Return(nil)
320 return m, nil
321 }
322
323 cmd := newAttachmentsPushCmd()
324 cmd.SetOut(new(bytes.Buffer))
325 cmd.SetErr(new(bytes.Buffer))
326 cmd.SetArgs([]string{"postgres:18"})
327
328 err := cmd.Execute()
329
330 require.NoError(t, err)
331 assert.Equal(t, "registry.example.com/postgres:v1.2.3", gotVersionRef)
332 assert.Equal(t, "registry.example.com/postgres:latest", gotLatestRef)
333}
334
335func TestNormalizeAttachmentImageName_PreservesNamespaceWithoutRegistryHost(t *testing.T) {
336 got := normalizeAttachmentImageName("myorg/postgres:18")
337
338 assert.Equal(t, "myorg/postgres", got)
339}