1package cli
2
3import (
4 "bytes"
5 "encoding/json"
6 "testing"
7 "time"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11
12 "github.com/bnema/gordon/internal/adapters/dto"
13 "github.com/bnema/gordon/internal/adapters/in/cli/remote"
14 "github.com/bnema/gordon/internal/domain"
15)
16
17func TestRoutesList_JSONFlag_Accepted(t *testing.T) {
18 cmd := newRoutesListCmd()
19 f := cmd.Flags().Lookup("json")
20 assert.NotNil(t, f)
21 if f != nil {
22 assert.Equal(t, "false", f.DefValue)
23 }
24}
25
26func TestAttachmentsList_JSONFlag_Accepted(t *testing.T) {
27 cmd := newAttachmentsListCmd()
28 f := cmd.Flags().Lookup("json")
29 assert.NotNil(t, f)
30 if f != nil {
31 assert.Equal(t, "false", f.DefValue)
32 }
33}
34
35func TestSecretsList_JSONFlag_Accepted(t *testing.T) {
36 cmd := newSecretsListCmd()
37 f := cmd.Flags().Lookup("json")
38 assert.NotNil(t, f)
39 if f != nil {
40 assert.Equal(t, "false", f.DefValue)
41 }
42}
43
44func TestImagesList_JSONFlag_Accepted(t *testing.T) {
45 cmd := newImagesListCmd()
46 f := cmd.Flags().Lookup("json")
47 assert.NotNil(t, f)
48 if f != nil {
49 assert.Equal(t, "false", f.DefValue)
50 }
51}
52
53func TestBackupList_JSONFlag_Accepted(t *testing.T) {
54 cmd := newBackupListCmd()
55 f := cmd.Flags().Lookup("json")
56 assert.NotNil(t, f)
57 if f != nil {
58 assert.Equal(t, "false", f.DefValue)
59 }
60}
61
62func TestTokenList_JSONFlag_Accepted(t *testing.T) {
63 cmd := newTokenListCmd()
64 f := cmd.Flags().Lookup("json")
65 assert.NotNil(t, f)
66 if f != nil {
67 assert.Equal(t, "false", f.DefValue)
68 }
69}
70
71func TestRemotesList_JSONFlag_Accepted(t *testing.T) {
72 cmd := newRemotesListCmd()
73 f := cmd.Flags().Lookup("json")
74 assert.NotNil(t, f)
75 if f != nil {
76 assert.Equal(t, "false", f.DefValue)
77 }
78}
79
80func TestRollbackList_JSONFlag_Accepted(t *testing.T) {
81 cmd := newRollbackListCmd()
82 f := cmd.Flags().Lookup("json")
83 assert.NotNil(t, f)
84 if f != nil {
85 assert.Equal(t, "false", f.DefValue)
86 }
87}
88
89func TestImagesList_JSONShape_RoundTripsDTO(t *testing.T) {
90 createdAt := time.Date(2026, 2, 8, 12, 0, 0, 0, time.UTC)
91 images := []dto.Image{{Repository: "registry.example.com/app", Tag: "latest", Size: 12_000_000, Created: createdAt, ID: "sha256:1111", Dangling: false}}
92
93 payload, err := json.Marshal(images)
94 require.NoError(t, err)
95
96 var got []dto.Image
97 require.NoError(t, json.Unmarshal(payload, &got))
98 require.Len(t, got, 1)
99 assert.Equal(t, images[0].Repository, got[0].Repository)
100 assert.Equal(t, images[0].Tag, got[0].Tag)
101 assert.Equal(t, images[0].ID, got[0].ID)
102}
103
104func TestRollbackList_JSONShape_RoundTripsTags(t *testing.T) {
105 tags := []string{"v1.2.0", "v1.1.0"}
106 payload, err := json.Marshal(tags)
107 require.NoError(t, err)
108
109 var got []string
110 require.NoError(t, json.Unmarshal(payload, &got))
111 assert.Equal(t, tags, got)
112}
113
114func TestBackupList_JSONShape_RoundTripsJobs(t *testing.T) {
115 startedAt := time.Date(2026, 3, 1, 10, 0, 0, 0, time.UTC)
116 jobs := []domain.BackupJob{{ID: "b1", Domain: "app.example.com", DBName: "postgres", Status: domain.BackupStatusCompleted, StartedAt: startedAt}}
117
118 payload, err := json.Marshal(jobs)
119 require.NoError(t, err)
120
121 var got []domain.BackupJob
122 require.NoError(t, json.Unmarshal(payload, &got))
123 require.Len(t, got, 1)
124 assert.Equal(t, "b1", got[0].ID)
125 assert.Equal(t, "app.example.com", got[0].Domain)
126}
127
128func TestTokenList_JSONShape_RoundTripsTokens(t *testing.T) {
129 tokens := []domain.Token{{
130 ID: "tok-1",
131 Subject: "deploy",
132 Scopes: []string{"admin:*:*"},
133 IssuedAt: time.Date(2026, 3, 1, 10, 0, 0, 0, time.UTC),
134 ExpiresAt: time.Date(2026, 4, 1, 10, 0, 0, 0, time.UTC),
135 }}
136
137 payload, err := json.Marshal(tokens)
138 require.NoError(t, err)
139
140 var got []domain.Token
141 require.NoError(t, json.Unmarshal(payload, &got))
142 require.Len(t, got, 1)
143 assert.Equal(t, tokens[0].ID, got[0].ID)
144 assert.Equal(t, tokens[0].Subject, got[0].Subject)
145 assert.Equal(t, tokens[0].Scopes, got[0].Scopes)
146}
147
148func TestRemotesList_JSONShape_RoundTripsRemoteObjects(t *testing.T) {
149 items := []struct {
150 Name string `json:"name"`
151 URL string `json:"url"`
152 Active bool `json:"active"`
153 InsecureTLS bool `json:"insecure_tls"`
154 }{
155 {Name: "prod", URL: "https://prod.example.com", Active: true, InsecureTLS: true},
156 }
157
158 payload, err := json.Marshal(items)
159 require.NoError(t, err)
160
161 var got []struct {
162 Name string `json:"name"`
163 URL string `json:"url"`
164 Active bool `json:"active"`
165 InsecureTLS bool `json:"insecure_tls"`
166 }
167 require.NoError(t, json.Unmarshal(payload, &got))
168 require.Len(t, got, 1)
169 assert.Equal(t, "prod", got[0].Name)
170 assert.Equal(t, "https://prod.example.com", got[0].URL)
171 assert.True(t, got[0].Active)
172 assert.True(t, got[0].InsecureTLS)
173}
174
175func TestAttachmentsList_JSONShape_RoundTripsTargetPayload(t *testing.T) {
176 payload := struct {
177 Target string `json:"target"`
178 Images []string `json:"images"`
179 }{
180 Target: "app.example.com",
181 Images: []string{"postgres:16"},
182 }
183
184 encoded, err := json.Marshal(payload)
185 require.NoError(t, err)
186
187 var got struct {
188 Target string `json:"target"`
189 Images []string `json:"images"`
190 }
191 require.NoError(t, json.Unmarshal(encoded, &got))
192 assert.Equal(t, payload.Target, got.Target)
193 assert.Equal(t, payload.Images, got.Images)
194}
195
196func TestSecretsList_JSONShape_RoundTripsPayload(t *testing.T) {
197 payload := struct {
198 Domain string `json:"domain"`
199 Keys []string `json:"keys"`
200 Attachments []remote.AttachmentSecrets `json:"attachments"`
201 }{
202 Domain: "app.example.com",
203 Keys: []string{"API_KEY"},
204 Attachments: []remote.AttachmentSecrets{{
205 Service: "postgres",
206 Keys: []string{"POSTGRES_PASSWORD"},
207 }},
208 }
209
210 encoded, err := json.Marshal(payload)
211 require.NoError(t, err)
212
213 var got struct {
214 Domain string `json:"domain"`
215 Keys []string `json:"keys"`
216 Attachments []remote.AttachmentSecrets `json:"attachments"`
217 }
218 require.NoError(t, json.Unmarshal(encoded, &got))
219 assert.Equal(t, payload.Domain, got.Domain)
220 assert.Equal(t, payload.Keys, got.Keys)
221 assert.Equal(t, payload.Attachments, got.Attachments)
222}
223
224func TestSecretsList_JSONShape_NormalizesNilAttachmentsToEmptySlice(t *testing.T) {
225 payload := struct {
226 Domain string `json:"domain"`
227 Keys []string `json:"keys"`
228 Attachments []remote.AttachmentSecrets `json:"attachments"`
229 }{
230 Domain: "app.example.com",
231 Keys: []string{"API_KEY"},
232 Attachments: []remote.AttachmentSecrets{},
233 }
234
235 encoded, err := json.Marshal(payload)
236 require.NoError(t, err)
237 assert.Contains(t, string(encoded), `"attachments":[]`)
238
239 var got struct {
240 Domain string `json:"domain"`
241 Keys []string `json:"keys"`
242 Attachments []remote.AttachmentSecrets `json:"attachments"`
243 }
244 require.NoError(t, json.Unmarshal(encoded, &got))
245 assert.NotNil(t, got.Attachments)
246 assert.Empty(t, got.Attachments)
247}
248
249func TestRoutesList_JSONShape_RoundTripsLocalPayload(t *testing.T) {
250 payload := []struct {
251 Domain string `json:"domain"`
252 Image string `json:"image"`
253 }{
254 {Domain: "app.example.com", Image: "app:latest"},
255 }
256
257 encoded, err := json.Marshal(payload)
258 require.NoError(t, err)
259
260 var got []struct {
261 Domain string `json:"domain"`
262 Image string `json:"image"`
263 }
264 require.NoError(t, json.Unmarshal(encoded, &got))
265 require.Len(t, got, 1)
266 assert.Equal(t, payload[0], got[0])
267}
268
269func TestRoutesStatus_JSONFlag_Accepted(t *testing.T) {
270 cmd := newRoutesStatusCmd()
271 f := cmd.Flags().Lookup("json")
272 assert.NotNil(t, f)
273 if f != nil {
274 assert.Equal(t, "false", f.DefValue)
275 }
276}
277
278func TestRoutesList_JSONShape_RoundTripsSections(t *testing.T) {
279 payload := []routeListSection{{
280 Kind: "local",
281 Name: "local",
282 Routes: []routeListItem{{
283 Domain: "app.local",
284 Image: "myapp:latest",
285 }},
286 }}
287
288 encoded, err := json.Marshal(payload)
289 require.NoError(t, err)
290
291 var got []routeListSection
292 require.NoError(t, json.Unmarshal(encoded, &got))
293 require.Len(t, got, 1)
294 assert.Equal(t, "local", got[0].Kind)
295 assert.Equal(t, "app.local", got[0].Routes[0].Domain)
296}
297
298func TestRoutesStatus_JSONShape_RoundTripsSections(t *testing.T) {
299 payload := []routeStatusSection{{
300 Kind: "remote",
301 Name: "igor",
302 URL: "https://gordon.supri.xyz",
303 Routes: []routeStatusItem{{
304 Domain: "grafana.supri.xyz",
305 Image: "grafana",
306 ContainerStatus: "running",
307 HTTPStatus: 200,
308 Network: "gordon-shared",
309 Attachments: []routeStatusAttachment{{
310 Name: "prometheus",
311 Image: "prometheus:v5",
312 Status: "running",
313 }},
314 }},
315 }}
316
317 encoded, err := json.Marshal(payload)
318 require.NoError(t, err)
319
320 var got []routeStatusSection
321 require.NoError(t, json.Unmarshal(encoded, &got))
322 require.Len(t, got, 1)
323 assert.Equal(t, "igor", got[0].Name)
324 assert.Equal(t, 200, got[0].Routes[0].HTTPStatus)
325 assert.Equal(t, "prometheus", got[0].Routes[0].Attachments[0].Name)
326}
327
328func TestWriteJSON_ProducesValidIndentedJSON(t *testing.T) {
329 var out bytes.Buffer
330 err := writeJSON(&out, map[string]any{"ok": true})
331 require.NoError(t, err)
332
333 var got map[string]any
334 require.NoError(t, json.Unmarshal(out.Bytes(), &got))
335 assert.Equal(t, true, got["ok"])
336 assert.Contains(t, out.String(), "\n")
337}