v2.3.0 fc65046

types_test.go RawHistoryFolder

1package manifest
2
3import (
4	"encoding/json"
5	"testing"
6
7	"github.com/stretchr/testify/assert"
8	"github.com/stretchr/testify/require"
9)
10
11func TestOCIManifest_JSON(t *testing.T) {
12	tests := []struct {
13		name     string
14		manifest OCIManifest
15		wantJSON string
16	}{
17		{
18			name: "basic OCI manifest",
19			manifest: OCIManifest{
20				SchemaVersion: 2,
21				MediaType:     "application/vnd.oci.image.manifest.v1+json",
22				Config: OCIDescriptor{
23					MediaType: "application/vnd.oci.image.config.v1+json",
24					Digest:    "sha256:abc123",
25					Size:      1234,
26				},
27				Layers: []OCIDescriptor{
28					{
29						MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
30						Digest:    "sha256:layer1",
31						Size:      5678,
32					},
33				},
34			},
35			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:abc123","size":1234},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:layer1","size":5678}]}`,
36		},
37		{
38			name: "OCI manifest with annotations",
39			manifest: OCIManifest{
40				SchemaVersion: 2,
41				MediaType:     "application/vnd.oci.image.manifest.v1+json",
42				Config: OCIDescriptor{
43					MediaType: "application/vnd.oci.image.config.v1+json",
44					Digest:    "sha256:config123",
45					Size:      1024,
46				},
47				Layers: []OCIDescriptor{
48					{
49						MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
50						Digest:    "sha256:layer1",
51						Size:      2048,
52					},
53				},
54				Annotations: map[string]string{
55					"version":    "1.0.0",
56					"created-by": "gordon",
57				},
58			},
59			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:config123","size":1024},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:layer1","size":2048}],"annotations":{"created-by":"gordon","version":"1.0.0"}}`,
60		},
61		{
62			name: "OCI manifest with multiple layers",
63			manifest: OCIManifest{
64				SchemaVersion: 2,
65				MediaType:     "application/vnd.oci.image.manifest.v1+json",
66				Config: OCIDescriptor{
67					MediaType: "application/vnd.oci.image.config.v1+json",
68					Digest:    "sha256:config456",
69					Size:      512,
70				},
71				Layers: []OCIDescriptor{
72					{
73						MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
74						Digest:    "sha256:layer1",
75						Size:      1024,
76					},
77					{
78						MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
79						Digest:    "sha256:layer2",
80						Size:      2048,
81					},
82					{
83						MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
84						Digest:    "sha256:layer3",
85						Size:      4096,
86					},
87				},
88			},
89			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:config456","size":512},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:layer1","size":1024},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:layer2","size":2048},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:layer3","size":4096}]}`,
90		},
91	}
92
93	for _, tt := range tests {
94		t.Run(tt.name, func(t *testing.T) {
95			// Test marshaling
96			gotJSON, err := json.Marshal(tt.manifest)
97			require.NoError(t, err)
98			assert.JSONEq(t, tt.wantJSON, string(gotJSON))
99
100			// Test unmarshaling
101			var gotManifest OCIManifest
102			err = json.Unmarshal([]byte(tt.wantJSON), &gotManifest)
103			require.NoError(t, err)
104			assert.Equal(t, tt.manifest, gotManifest)
105		})
106	}
107}
108
109func TestDockerManifest_JSON(t *testing.T) {
110	tests := []struct {
111		name     string
112		manifest DockerManifest
113		wantJSON string
114	}{
115		{
116			name: "basic Docker manifest",
117			manifest: DockerManifest{
118				SchemaVersion: 2,
119				MediaType:     "application/vnd.docker.distribution.manifest.v2+json",
120				Config: OCIDescriptor{
121					MediaType: "application/vnd.docker.container.image.v1+json",
122					Digest:    "sha256:docker123",
123					Size:      1500,
124				},
125				Layers: []OCIDescriptor{
126					{
127						MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
128						Digest:    "sha256:dockerlayer1",
129						Size:      7890,
130					},
131				},
132			},
133			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:docker123","size":1500},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:dockerlayer1","size":7890}]}`,
134		},
135		{
136			name: "Docker manifest with empty layers",
137			manifest: DockerManifest{
138				SchemaVersion: 2,
139				MediaType:     "application/vnd.docker.distribution.manifest.v2+json",
140				Config: OCIDescriptor{
141					MediaType: "application/vnd.docker.container.image.v1+json",
142					Digest:    "sha256:empty123",
143					Size:      256,
144				},
145				Layers: []OCIDescriptor{},
146			},
147			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:empty123","size":256},"layers":[]}`,
148		},
149	}
150
151	for _, tt := range tests {
152		t.Run(tt.name, func(t *testing.T) {
153			// Test marshaling
154			gotJSON, err := json.Marshal(tt.manifest)
155			require.NoError(t, err)
156			assert.JSONEq(t, tt.wantJSON, string(gotJSON))
157
158			// Test unmarshaling
159			var gotManifest DockerManifest
160			err = json.Unmarshal([]byte(tt.wantJSON), &gotManifest)
161			require.NoError(t, err)
162			assert.Equal(t, tt.manifest, gotManifest)
163		})
164	}
165}
166
167func TestOCIDescriptor_JSON(t *testing.T) {
168	tests := []struct {
169		name       string
170		descriptor OCIDescriptor
171		wantJSON   string
172	}{
173		{
174			name: "basic descriptor",
175			descriptor: OCIDescriptor{
176				MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
177				Digest:    "sha256:descriptor123",
178				Size:      9999,
179			},
180			wantJSON: `{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:descriptor123","size":9999}`,
181		},
182		{
183			name: "descriptor with annotations",
184			descriptor: OCIDescriptor{
185				MediaType: "application/vnd.oci.image.config.v1+json",
186				Digest:    "sha256:annotated456",
187				Size:      1111,
188				Annotations: map[string]string{
189					"title":       "Test Layer",
190					"description": "A test layer for unit testing",
191				},
192			},
193			wantJSON: `{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:annotated456","size":1111,"annotations":{"description":"A test layer for unit testing","title":"Test Layer"}}`,
194		},
195		{
196			name: "descriptor with zero size",
197			descriptor: OCIDescriptor{
198				MediaType: "application/vnd.oci.empty.v1+json",
199				Digest:    "sha256:empty",
200				Size:      0,
201			},
202			wantJSON: `{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:empty","size":0}`,
203		},
204	}
205
206	for _, tt := range tests {
207		t.Run(tt.name, func(t *testing.T) {
208			// Test marshaling
209			gotJSON, err := json.Marshal(tt.descriptor)
210			require.NoError(t, err)
211			assert.JSONEq(t, tt.wantJSON, string(gotJSON))
212
213			// Test unmarshaling
214			var gotDescriptor OCIDescriptor
215			err = json.Unmarshal([]byte(tt.wantJSON), &gotDescriptor)
216			require.NoError(t, err)
217			assert.Equal(t, tt.descriptor, gotDescriptor)
218		})
219	}
220}
221
222func TestManifestList_JSON(t *testing.T) {
223	tests := []struct {
224		name         string
225		manifestList ManifestList
226		wantJSON     string
227	}{
228		{
229			name: "basic manifest list",
230			manifestList: ManifestList{
231				SchemaVersion: 2,
232				MediaType:     "application/vnd.oci.image.index.v1+json",
233				Manifests: []ManifestDescriptor{
234					{
235						MediaType: "application/vnd.oci.image.manifest.v1+json",
236						Digest:    "sha256:manifest1",
237						Size:      2222,
238						Platform: &Platform{
239							Architecture: "amd64",
240							OS:           "linux",
241						},
242					},
243				},
244			},
245			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:manifest1","size":2222,"platform":{"architecture":"amd64","os":"linux"}}]}`,
246		},
247		{
248			name: "manifest list with annotations",
249			manifestList: ManifestList{
250				SchemaVersion: 2,
251				MediaType:     "application/vnd.oci.image.index.v1+json",
252				Manifests: []ManifestDescriptor{
253					{
254						MediaType: "application/vnd.oci.image.manifest.v1+json",
255						Digest:    "sha256:manifest2",
256						Size:      3333,
257						Platform: &Platform{
258							Architecture: "arm64",
259							OS:           "linux",
260							Variant:      "v8",
261						},
262						Annotations: map[string]string{
263							"io.github.gordon.platform": "arm64-v8",
264						},
265					},
266				},
267				Annotations: map[string]string{
268					"version": "2.0.0",
269					"source":  "github.com/user/repo",
270				},
271			},
272			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:manifest2","size":3333,"platform":{"architecture":"arm64","os":"linux","variant":"v8"},"annotations":{"io.github.gordon.platform":"arm64-v8"}}],"annotations":{"source":"github.com/user/repo","version":"2.0.0"}}`,
273		},
274		{
275			name: "multi-platform manifest list",
276			manifestList: ManifestList{
277				SchemaVersion: 2,
278				MediaType:     "application/vnd.docker.distribution.manifest.list.v2+json",
279				Manifests: []ManifestDescriptor{
280					{
281						MediaType: "application/vnd.docker.distribution.manifest.v2+json",
282						Digest:    "sha256:linux-amd64",
283						Size:      4444,
284						Platform: &Platform{
285							Architecture: "amd64",
286							OS:           "linux",
287						},
288					},
289					{
290						MediaType: "application/vnd.docker.distribution.manifest.v2+json",
291						Digest:    "sha256:linux-arm64",
292						Size:      5555,
293						Platform: &Platform{
294							Architecture: "arm64",
295							OS:           "linux",
296						},
297					},
298					{
299						MediaType: "application/vnd.docker.distribution.manifest.v2+json",
300						Digest:    "sha256:windows-amd64",
301						Size:      6666,
302						Platform: &Platform{
303							Architecture: "amd64",
304							OS:           "windows",
305							OSVersion:    "10.0.17763.1234",
306						},
307					},
308				},
309			},
310			wantJSON: `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.list.v2+json","manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:linux-amd64","size":4444,"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:linux-arm64","size":5555,"platform":{"architecture":"arm64","os":"linux"}},{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:windows-amd64","size":6666,"platform":{"architecture":"amd64","os":"windows","os.version":"10.0.17763.1234"}}]}`,
311		},
312	}
313
314	for _, tt := range tests {
315		t.Run(tt.name, func(t *testing.T) {
316			// Test marshaling
317			gotJSON, err := json.Marshal(tt.manifestList)
318			require.NoError(t, err)
319			assert.JSONEq(t, tt.wantJSON, string(gotJSON))
320
321			// Test unmarshaling
322			var gotManifestList ManifestList
323			err = json.Unmarshal([]byte(tt.wantJSON), &gotManifestList)
324			require.NoError(t, err)
325			assert.Equal(t, tt.manifestList, gotManifestList)
326		})
327	}
328}
329
330func TestPlatform_JSON(t *testing.T) {
331	tests := []struct {
332		name     string
333		platform Platform
334		wantJSON string
335	}{
336		{
337			name: "basic platform",
338			platform: Platform{
339				Architecture: "amd64",
340				OS:           "linux",
341			},
342			wantJSON: `{"architecture":"amd64","os":"linux"}`,
343		},
344		{
345			name: "platform with variant",
346			platform: Platform{
347				Architecture: "arm",
348				OS:           "linux",
349				Variant:      "v7",
350			},
351			wantJSON: `{"architecture":"arm","os":"linux","variant":"v7"}`,
352		},
353		{
354			name: "windows platform with version",
355			platform: Platform{
356				Architecture: "amd64",
357				OS:           "windows",
358				OSVersion:    "10.0.14393.1066",
359			},
360			wantJSON: `{"architecture":"amd64","os":"windows","os.version":"10.0.14393.1066"}`,
361		},
362		{
363			name: "platform with features",
364			platform: Platform{
365				Architecture: "amd64",
366				OS:           "linux",
367				OSFeatures:   []string{"feature1", "feature2"},
368			},
369			wantJSON: `{"architecture":"amd64","os":"linux","os.features":["feature1","feature2"]}`,
370		},
371		{
372			name: "complete platform",
373			platform: Platform{
374				Architecture: "s390x",
375				OS:           "linux",
376				OSVersion:    "4.15.0",
377				OSFeatures:   []string{"capability1", "capability2", "capability3"},
378				Variant:      "custom",
379			},
380			wantJSON: `{"architecture":"s390x","os":"linux","os.version":"4.15.0","os.features":["capability1","capability2","capability3"],"variant":"custom"}`,
381		},
382		{
383			name: "empty platform",
384			platform: Platform{
385				Architecture: "",
386				OS:           "",
387			},
388			wantJSON: `{"architecture":"","os":""}`,
389		},
390	}
391
392	for _, tt := range tests {
393		t.Run(tt.name, func(t *testing.T) {
394			// Test marshaling
395			gotJSON, err := json.Marshal(tt.platform)
396			require.NoError(t, err)
397			assert.JSONEq(t, tt.wantJSON, string(gotJSON))
398
399			// Test unmarshaling
400			var gotPlatform Platform
401			err = json.Unmarshal([]byte(tt.wantJSON), &gotPlatform)
402			require.NoError(t, err)
403			assert.Equal(t, tt.platform, gotPlatform)
404		})
405	}
406}
407
408func TestManifestDescriptor_JSON(t *testing.T) {
409	tests := []struct {
410		name       string
411		descriptor ManifestDescriptor
412		wantJSON   string
413	}{
414		{
415			name: "basic manifest descriptor",
416			descriptor: ManifestDescriptor{
417				MediaType: "application/vnd.oci.image.manifest.v1+json",
418				Digest:    "sha256:manifestdesc123",
419				Size:      7777,
420			},
421			wantJSON: `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:manifestdesc123","size":7777}`,
422		},
423		{
424			name: "manifest descriptor with platform",
425			descriptor: ManifestDescriptor{
426				MediaType: "application/vnd.oci.image.manifest.v1+json",
427				Digest:    "sha256:platform123",
428				Size:      8888,
429				Platform: &Platform{
430					Architecture: "riscv64",
431					OS:           "linux",
432				},
433			},
434			wantJSON: `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:platform123","size":8888,"platform":{"architecture":"riscv64","os":"linux"}}`,
435		},
436		{
437			name: "manifest descriptor with annotations",
438			descriptor: ManifestDescriptor{
439				MediaType: "application/vnd.oci.image.manifest.v1+json",
440				Digest:    "sha256:annotations123",
441				Size:      9999,
442				Annotations: map[string]string{
443					"io.github.gordon.build": "automated",
444					"version":                "3.1.4",
445				},
446			},
447			wantJSON: `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:annotations123","size":9999,"annotations":{"io.github.gordon.build":"automated","version":"3.1.4"}}`,
448		},
449		{
450			name: "complete manifest descriptor",
451			descriptor: ManifestDescriptor{
452				MediaType: "application/vnd.oci.image.manifest.v1+json",
453				Digest:    "sha256:complete123",
454				Size:      12345,
455				Platform: &Platform{
456					Architecture: "ppc64le",
457					OS:           "linux",
458					Variant:      "power9",
459				},
460				Annotations: map[string]string{
461					"build.date":         "2023-01-01T00:00:00Z",
462					"maintainer":         "gordon-team",
463					"io.buildah.version": "1.23.0",
464				},
465			},
466			wantJSON: `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:complete123","size":12345,"platform":{"architecture":"ppc64le","os":"linux","variant":"power9"},"annotations":{"build.date":"2023-01-01T00:00:00Z","io.buildah.version":"1.23.0","maintainer":"gordon-team"}}`,
467		},
468	}
469
470	for _, tt := range tests {
471		t.Run(tt.name, func(t *testing.T) {
472			// Test marshaling
473			gotJSON, err := json.Marshal(tt.descriptor)
474			require.NoError(t, err)
475			assert.JSONEq(t, tt.wantJSON, string(gotJSON))
476
477			// Test unmarshaling
478			var gotDescriptor ManifestDescriptor
479			err = json.Unmarshal([]byte(tt.wantJSON), &gotDescriptor)
480			require.NoError(t, err)
481			assert.Equal(t, tt.descriptor, gotDescriptor)
482		})
483	}
484}
485
486func TestManifestTypes_EdgeCases(t *testing.T) {
487	t.Run("empty annotations should be omitted", func(t *testing.T) {
488		manifest := OCIManifest{
489			SchemaVersion: 2,
490			MediaType:     "application/vnd.oci.image.manifest.v1+json",
491			Config: OCIDescriptor{
492				MediaType: "application/vnd.oci.image.config.v1+json",
493				Digest:    "sha256:config",
494				Size:      100,
495			},
496			Layers:      []OCIDescriptor{},
497			Annotations: map[string]string{}, // Empty map should be omitted
498		}
499
500		jsonData, err := json.Marshal(manifest)
501		require.NoError(t, err)
502
503		// Should not contain "annotations" field
504		assert.NotContains(t, string(jsonData), "annotations")
505	})
506
507	t.Run("nil annotations should be omitted", func(t *testing.T) {
508		manifest := OCIManifest{
509			SchemaVersion: 2,
510			MediaType:     "application/vnd.oci.image.manifest.v1+json",
511			Config: OCIDescriptor{
512				MediaType: "application/vnd.oci.image.config.v1+json",
513				Digest:    "sha256:config",
514				Size:      100,
515			},
516			Layers: []OCIDescriptor{},
517			// Annotations is nil by default
518		}
519
520		jsonData, err := json.Marshal(manifest)
521		require.NoError(t, err)
522
523		// Should not contain "annotations" field
524		assert.NotContains(t, string(jsonData), "annotations")
525	})
526
527	t.Run("nil platform should be omitted", func(t *testing.T) {
528		descriptor := ManifestDescriptor{
529			MediaType: "application/vnd.oci.image.manifest.v1+json",
530			Digest:    "sha256:test",
531			Size:      123,
532			// Platform is nil by default
533		}
534
535		jsonData, err := json.Marshal(descriptor)
536		require.NoError(t, err)
537
538		// Should not contain "platform" field
539		assert.NotContains(t, string(jsonData), "platform")
540	})
541
542	t.Run("empty string fields are preserved", func(t *testing.T) {
543		platform := Platform{
544			Architecture: "",
545			OS:           "",
546			OSVersion:    "",
547			Variant:      "",
548		}
549
550		jsonData, err := json.Marshal(platform)
551		require.NoError(t, err)
552
553		// Empty strings should be preserved
554		assert.Contains(t, string(jsonData), `"architecture":""`)
555		assert.Contains(t, string(jsonData), `"os":""`)
556	})
557}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close