1package auto
2
3import (
4 "context"
5 "encoding/json"
6 "io"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12
13 "github.com/bnema/gordon/internal/boundaries/out/mocks"
14 "github.com/bnema/gordon/internal/domain"
15)
16
17func TestParseConfigDigest(t *testing.T) {
18 tests := []struct {
19 name string
20 manifest map[string]any
21 expected string
22 wantErr bool
23 }{
24 {
25 name: "valid manifest v2",
26 manifest: map[string]any{
27 "schemaVersion": 2,
28 "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
29 "config": map[string]any{
30 "mediaType": "application/vnd.docker.container.image.v1+json",
31 "digest": "sha256:abc123",
32 "size": 1234,
33 },
34 },
35 expected: "sha256:abc123",
36 },
37 {
38 name: "OCI manifest",
39 manifest: map[string]any{
40 "schemaVersion": 2,
41 "mediaType": "application/vnd.oci.image.manifest.v1+json",
42 "config": map[string]any{
43 "mediaType": "application/vnd.oci.image.config.v1+json",
44 "digest": "sha256:def456",
45 "size": 5678,
46 },
47 },
48 expected: "sha256:def456",
49 },
50 {
51 name: "empty config digest",
52 manifest: map[string]any{
53 "schemaVersion": 2,
54 "config": map[string]any{
55 "digest": "",
56 },
57 },
58 expected: "",
59 },
60 }
61
62 for _, tt := range tests {
63 t.Run(tt.name, func(t *testing.T) {
64 data, err := json.Marshal(tt.manifest)
65 require.NoError(t, err)
66
67 result, err := ParseConfigDigest(data)
68
69 if tt.wantErr {
70 assert.Error(t, err)
71 return
72 }
73
74 require.NoError(t, err)
75 assert.Equal(t, tt.expected, result)
76 })
77 }
78}
79
80func TestParseConfigDigest_InvalidJSON(t *testing.T) {
81 _, err := ParseConfigDigest([]byte("invalid json"))
82 assert.Error(t, err)
83}
84
85func TestParseImageLabels(t *testing.T) {
86 tests := []struct {
87 name string
88 config map[string]any
89 expected *domain.ImageLabels
90 }{
91 {
92 name: "single domain label",
93 config: map[string]any{
94 "config": map[string]any{
95 "Labels": map[string]any{
96 "gordon.domain": "app.example.com",
97 },
98 },
99 },
100 expected: &domain.ImageLabels{
101 Domain: "app.example.com",
102 },
103 },
104 {
105 name: "multiple domains label",
106 config: map[string]any{
107 "config": map[string]any{
108 "Labels": map[string]any{
109 "gordon.domains": "app1.example.com, app2.example.com",
110 },
111 },
112 },
113 expected: &domain.ImageLabels{
114 Domains: []string{"app1.example.com", "app2.example.com"},
115 },
116 },
117 {
118 name: "all gordon labels",
119 config: map[string]any{
120 "config": map[string]any{
121 "Labels": map[string]any{
122 "gordon.domain": "app.example.com",
123 "gordon.health": "/healthz",
124 "gordon.proxy.port": "8080",
125 "gordon.env-file": ".env.prod",
126 },
127 },
128 },
129 expected: &domain.ImageLabels{
130 Domain: "app.example.com",
131 Health: "/healthz",
132 Port: "8080",
133 EnvFile: ".env.prod",
134 },
135 },
136 {
137 name: "no labels",
138 config: map[string]any{
139 "config": map[string]any{
140 "Labels": nil,
141 },
142 },
143 expected: &domain.ImageLabels{},
144 },
145 {
146 name: "no gordon labels",
147 config: map[string]any{
148 "config": map[string]any{
149 "Labels": map[string]any{
150 "other.label": "value",
151 },
152 },
153 },
154 expected: &domain.ImageLabels{},
155 },
156 {
157 name: "whitespace in labels",
158 config: map[string]any{
159 "config": map[string]any{
160 "Labels": map[string]any{
161 "gordon.domain": " app.example.com ",
162 },
163 },
164 },
165 expected: &domain.ImageLabels{
166 Domain: "app.example.com",
167 },
168 },
169 {
170 name: "deprecated port label fallback",
171 config: map[string]any{
172 "config": map[string]any{
173 "Labels": map[string]any{
174 "gordon.port": "9090",
175 },
176 },
177 },
178 expected: &domain.ImageLabels{
179 Port: "9090",
180 },
181 },
182 {
183 name: "proxy.port takes precedence over port",
184 config: map[string]any{
185 "config": map[string]any{
186 "Labels": map[string]any{
187 "gordon.proxy.port": "8080",
188 "gordon.port": "9090",
189 },
190 },
191 },
192 expected: &domain.ImageLabels{
193 Port: "8080",
194 },
195 },
196 }
197
198 for _, tt := range tests {
199 t.Run(tt.name, func(t *testing.T) {
200 data, err := json.Marshal(tt.config)
201 require.NoError(t, err)
202
203 result, err := ParseImageLabels(data)
204 require.NoError(t, err)
205
206 assert.Equal(t, tt.expected.Domain, result.Domain)
207 assert.Equal(t, tt.expected.Domains, result.Domains)
208 assert.Equal(t, tt.expected.Health, result.Health)
209 assert.Equal(t, tt.expected.Port, result.Port)
210 assert.Equal(t, tt.expected.EnvFile, result.EnvFile)
211 })
212 }
213}
214
215func TestParseImageLabels_InvalidJSON(t *testing.T) {
216 _, err := ParseImageLabels([]byte("invalid json"))
217 assert.Error(t, err)
218}
219
220func TestExtractLabels(t *testing.T) {
221 ctx := context.Background()
222
223 manifest := map[string]any{
224 "schemaVersion": 2,
225 "config": map[string]any{
226 "digest": "sha256:configdigest123",
227 },
228 }
229 manifestData, err := json.Marshal(manifest)
230 require.NoError(t, err)
231
232 imageConf := map[string]any{
233 "config": map[string]any{
234 "Labels": map[string]any{
235 "gordon.domain": "app.example.com",
236 "gordon.health": "/healthz",
237 },
238 },
239 }
240 configData, err := json.Marshal(imageConf)
241 require.NoError(t, err)
242
243 blobStorage := mocks.NewMockBlobStorage(t)
244 blobStorage.EXPECT().GetBlob("sha256:configdigest123").Return(io.NopCloser(strings.NewReader(string(configData))), nil)
245
246 labels, err := ExtractLabels(ctx, manifestData, blobStorage)
247 require.NoError(t, err)
248 assert.Equal(t, "app.example.com", labels.Domain)
249 assert.Equal(t, "/healthz", labels.Health)
250}
251
252func TestExtractLabels_InvalidManifest(t *testing.T) {
253 ctx := context.Background()
254 blobStorage := mocks.NewMockBlobStorage(t)
255
256 _, err := ExtractLabels(ctx, []byte("bad json"), blobStorage)
257 assert.Error(t, err)
258}
259
260func TestExtractLabels_EmptyConfigDigest(t *testing.T) {
261 ctx := context.Background()
262
263 manifest := map[string]any{
264 "schemaVersion": 2,
265 "config": map[string]any{
266 "digest": "",
267 },
268 }
269 manifestData, err := json.Marshal(manifest)
270 require.NoError(t, err)
271
272 blobStorage := mocks.NewMockBlobStorage(t)
273
274 _, err = ExtractLabels(ctx, manifestData, blobStorage)
275 assert.Error(t, err)
276 assert.Contains(t, err.Error(), "no config digest")
277}