v2.2.0 64d39bf

manifest_test.go RawHistoryFolder

1package filesystem
2
3import (
4	"os"
5	"path/filepath"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/require"
10)
11
12func TestNewManifestStorage(t *testing.T) {
13	tmpDir := t.TempDir()
14	log := testLogger()
15
16	storage, err := NewManifestStorage(tmpDir, log)
17
18	require.NoError(t, err)
19	assert.NotNil(t, storage)
20
21	// Verify repositories directory was created
22	assert.DirExists(t, filepath.Join(tmpDir, "repositories"))
23}
24
25func TestNewManifestStorage_InvalidPath(t *testing.T) {
26	log := testLogger()
27
28	_, err := NewManifestStorage("/nonexistent/deeply/nested/path/that/should/fail", log)
29
30	assert.Error(t, err)
31}
32
33func TestManifestStorage_PutAndGetManifest(t *testing.T) {
34	tmpDir := t.TempDir()
35	log := testLogger()
36
37	storage, err := NewManifestStorage(tmpDir, log)
38	require.NoError(t, err)
39
40	manifestData := []byte(`{"schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json"}`)
41	contentType := "application/vnd.docker.distribution.manifest.v2+json"
42
43	// Put manifest
44	err = storage.PutManifest("myapp", "latest", contentType, manifestData)
45	require.NoError(t, err)
46
47	// Get manifest
48	data, ct, err := storage.GetManifest("myapp", "latest")
49	require.NoError(t, err)
50
51	assert.Equal(t, manifestData, data)
52	assert.Equal(t, contentType, ct)
53}
54
55func TestManifestStorage_GetManifest_NotFound(t *testing.T) {
56	tmpDir := t.TempDir()
57	log := testLogger()
58
59	storage, err := NewManifestStorage(tmpDir, log)
60	require.NoError(t, err)
61
62	data, ct, err := storage.GetManifest("notexists", "latest")
63
64	assert.Error(t, err)
65	assert.Nil(t, data)
66	assert.Empty(t, ct)
67	assert.Contains(t, err.Error(), "manifest not found")
68}
69
70func TestManifestStorage_GetManifest_FallbackContentType(t *testing.T) {
71	tmpDir := t.TempDir()
72	log := testLogger()
73
74	storage, err := NewManifestStorage(tmpDir, log)
75	require.NoError(t, err)
76
77	// Create manifest file directly without content type file
78	manifestDir := filepath.Join(tmpDir, "repositories", "myapp", "manifests")
79	err = os.MkdirAll(manifestDir, 0750)
80	require.NoError(t, err)
81
82	manifestData := []byte(`{"schemaVersion": 2}`)
83	err = os.WriteFile(filepath.Join(manifestDir, "latest"), manifestData, 0600)
84	require.NoError(t, err)
85
86	// Get manifest should use fallback content type
87	data, ct, err := storage.GetManifest("myapp", "latest")
88	require.NoError(t, err)
89
90	assert.Equal(t, manifestData, data)
91	assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", ct)
92}
93
94func TestManifestStorage_DeleteManifest(t *testing.T) {
95	tmpDir := t.TempDir()
96	log := testLogger()
97
98	storage, err := NewManifestStorage(tmpDir, log)
99	require.NoError(t, err)
100
101	manifestData := []byte(`{"schemaVersion": 2}`)
102	contentType := "application/vnd.docker.distribution.manifest.v2+json"
103
104	// Put manifest
105	err = storage.PutManifest("myapp", "latest", contentType, manifestData)
106	require.NoError(t, err)
107
108	// Verify it exists
109	_, _, err = storage.GetManifest("myapp", "latest")
110	require.NoError(t, err)
111
112	// Delete manifest
113	err = storage.DeleteManifest("myapp", "latest")
114	require.NoError(t, err)
115
116	// Verify it's gone
117	_, _, err = storage.GetManifest("myapp", "latest")
118	assert.Error(t, err)
119}
120
121func TestManifestStorage_DeleteManifest_NotFound(t *testing.T) {
122	tmpDir := t.TempDir()
123	log := testLogger()
124
125	storage, err := NewManifestStorage(tmpDir, log)
126	require.NoError(t, err)
127
128	err = storage.DeleteManifest("notexists", "latest")
129
130	assert.Error(t, err)
131	assert.Contains(t, err.Error(), "manifest not found")
132}
133
134func TestManifestStorage_ListTags(t *testing.T) {
135	tmpDir := t.TempDir()
136	log := testLogger()
137
138	storage, err := NewManifestStorage(tmpDir, log)
139	require.NoError(t, err)
140
141	manifestData := []byte(`{"schemaVersion": 2}`)
142	contentType := "application/vnd.docker.distribution.manifest.v2+json"
143
144	// Put multiple manifests
145	err = storage.PutManifest("myapp", "latest", contentType, manifestData)
146	require.NoError(t, err)
147	err = storage.PutManifest("myapp", "v1.0", contentType, manifestData)
148	require.NoError(t, err)
149	err = storage.PutManifest("myapp", "v2.0", contentType, manifestData)
150	require.NoError(t, err)
151
152	// List tags
153	tags, err := storage.ListTags("myapp")
154	require.NoError(t, err)
155
156	assert.Len(t, tags, 3)
157	assert.Contains(t, tags, "latest")
158	assert.Contains(t, tags, "v1.0")
159	assert.Contains(t, tags, "v2.0")
160}
161
162func TestManifestStorage_ListTags_Empty(t *testing.T) {
163	tmpDir := t.TempDir()
164	log := testLogger()
165
166	storage, err := NewManifestStorage(tmpDir, log)
167	require.NoError(t, err)
168
169	// List tags for non-existent repo
170	tags, err := storage.ListTags("notexists")
171	require.NoError(t, err)
172
173	assert.Empty(t, tags)
174}
175
176func TestManifestStorage_ListTags_AfterDelete(t *testing.T) {
177	tmpDir := t.TempDir()
178	log := testLogger()
179
180	storage, err := NewManifestStorage(tmpDir, log)
181	require.NoError(t, err)
182
183	manifestData := []byte(`{"schemaVersion": 2}`)
184	contentType := "application/vnd.docker.distribution.manifest.v2+json"
185
186	// Put manifests
187	err = storage.PutManifest("myapp", "latest", contentType, manifestData)
188	require.NoError(t, err)
189	err = storage.PutManifest("myapp", "v1.0", contentType, manifestData)
190	require.NoError(t, err)
191
192	// Delete one
193	err = storage.DeleteManifest("myapp", "v1.0")
194	require.NoError(t, err)
195
196	// List tags
197	tags, err := storage.ListTags("myapp")
198	require.NoError(t, err)
199
200	assert.Len(t, tags, 1)
201	assert.Contains(t, tags, "latest")
202	assert.NotContains(t, tags, "v1.0")
203}
204
205func TestManifestStorage_ListRepositories(t *testing.T) {
206	tmpDir := t.TempDir()
207	log := testLogger()
208
209	storage, err := NewManifestStorage(tmpDir, log)
210	require.NoError(t, err)
211
212	manifestData := []byte(`{"schemaVersion": 2}`)
213	contentType := "application/vnd.docker.distribution.manifest.v2+json"
214
215	// Put manifests for different repos
216	err = storage.PutManifest("app1", "latest", contentType, manifestData)
217	require.NoError(t, err)
218	err = storage.PutManifest("app2", "latest", contentType, manifestData)
219	require.NoError(t, err)
220	err = storage.PutManifest("org/app3", "latest", contentType, manifestData)
221	require.NoError(t, err)
222
223	// List repositories
224	repos, err := storage.ListRepositories()
225	require.NoError(t, err)
226
227	assert.Len(t, repos, 3)
228	assert.Contains(t, repos, "app1")
229	assert.Contains(t, repos, "app2")
230	assert.Contains(t, repos, "org/app3")
231}
232
233func TestManifestStorage_ListRepositories_Empty(t *testing.T) {
234	tmpDir := t.TempDir()
235	log := testLogger()
236
237	storage, err := NewManifestStorage(tmpDir, log)
238	require.NoError(t, err)
239
240	repos, err := storage.ListRepositories()
241	require.NoError(t, err)
242
243	assert.Empty(t, repos)
244}
245
246func TestManifestStorage_NestedRepositoryNames(t *testing.T) {
247	tmpDir := t.TempDir()
248	log := testLogger()
249
250	storage, err := NewManifestStorage(tmpDir, log)
251	require.NoError(t, err)
252
253	manifestData := []byte(`{"schemaVersion": 2}`)
254	contentType := "application/vnd.docker.distribution.manifest.v2+json"
255
256	tests := []struct {
257		name      string
258		reference string
259	}{
260		{"simple", "latest"},
261		{"org/project", "v1.0"},
262		{"org/team/project", "v2.0"},
263		{"deeply/nested/repo/name", "sha256:abc123"},
264	}
265
266	for _, tt := range tests {
267		t.Run(tt.name, func(t *testing.T) {
268			err := storage.PutManifest(tt.name, tt.reference, contentType, manifestData)
269			require.NoError(t, err)
270
271			data, ct, err := storage.GetManifest(tt.name, tt.reference)
272			require.NoError(t, err)
273			assert.Equal(t, manifestData, data)
274			assert.Equal(t, contentType, ct)
275		})
276	}
277}
278
279func TestManifestStorage_ContentTypes(t *testing.T) {
280	tmpDir := t.TempDir()
281	log := testLogger()
282
283	storage, err := NewManifestStorage(tmpDir, log)
284	require.NoError(t, err)
285
286	tests := []struct {
287		name        string
288		contentType string
289	}{
290		{"v2 manifest", "application/vnd.docker.distribution.manifest.v2+json"},
291		{"v1 manifest", "application/vnd.docker.distribution.manifest.v1+json"},
292		{"OCI manifest", "application/vnd.oci.image.manifest.v1+json"},
293		{"OCI index", "application/vnd.oci.image.index.v1+json"},
294		{"manifest list", "application/vnd.docker.distribution.manifest.list.v2+json"},
295	}
296
297	for i, tt := range tests {
298		t.Run(tt.name, func(t *testing.T) {
299			repoName := "test-repo"
300			reference := "tag-" + string(rune('a'+i))
301			manifestData := []byte(`{"schemaVersion": 2}`)
302
303			err := storage.PutManifest(repoName, reference, tt.contentType, manifestData)
304			require.NoError(t, err)
305
306			_, ct, err := storage.GetManifest(repoName, reference)
307			require.NoError(t, err)
308			assert.Equal(t, tt.contentType, ct)
309		})
310	}
311}
312
313func TestManifestStorage_DuplicateTag(t *testing.T) {
314	tmpDir := t.TempDir()
315	log := testLogger()
316
317	storage, err := NewManifestStorage(tmpDir, log)
318	require.NoError(t, err)
319
320	contentType := "application/vnd.docker.distribution.manifest.v2+json"
321
322	// Put first manifest
323	manifestData1 := []byte(`{"schemaVersion": 2, "version": 1}`)
324	err = storage.PutManifest("myapp", "latest", contentType, manifestData1)
325	require.NoError(t, err)
326
327	// Put second manifest with same tag (overwrite)
328	manifestData2 := []byte(`{"schemaVersion": 2, "version": 2}`)
329	err = storage.PutManifest("myapp", "latest", contentType, manifestData2)
330	require.NoError(t, err)
331
332	// Get manifest should return the latest one
333	data, _, err := storage.GetManifest("myapp", "latest")
334	require.NoError(t, err)
335	assert.Equal(t, manifestData2, data)
336
337	// Tags list should only have one entry
338	tags, err := storage.ListTags("myapp")
339	require.NoError(t, err)
340	assert.Len(t, tags, 1)
341}
342
343func TestManifestStorage_LargeManifest(t *testing.T) {
344	tmpDir := t.TempDir()
345	log := testLogger()
346
347	storage, err := NewManifestStorage(tmpDir, log)
348	require.NoError(t, err)
349
350	// Create a large manifest (simulating one with many layers)
351	largeManifest := make([]byte, 1024*1024) // 1MB
352	for i := range largeManifest {
353		largeManifest[i] = byte('a' + (i % 26))
354	}
355
356	contentType := "application/vnd.docker.distribution.manifest.v2+json"
357
358	err = storage.PutManifest("large-app", "latest", contentType, largeManifest)
359	require.NoError(t, err)
360
361	data, _, err := storage.GetManifest("large-app", "latest")
362	require.NoError(t, err)
363	assert.Equal(t, largeManifest, data)
364}
365
366func TestManifestStorage_SpecialCharactersInReference(t *testing.T) {
367	tmpDir := t.TempDir()
368	log := testLogger()
369
370	storage, err := NewManifestStorage(tmpDir, log)
371	require.NoError(t, err)
372
373	manifestData := []byte(`{"schemaVersion": 2}`)
374	contentType := "application/vnd.docker.distribution.manifest.v2+json"
375
376	// SHA256 digests are valid references
377	reference := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
378
379	err = storage.PutManifest("myapp", reference, contentType, manifestData)
380	require.NoError(t, err)
381
382	data, _, err := storage.GetManifest("myapp", reference)
383	require.NoError(t, err)
384	assert.Equal(t, manifestData, data)
385}
386
387func TestManifestStorage_CompleteWorkflow(t *testing.T) {
388	tmpDir := t.TempDir()
389	log := testLogger()
390
391	storage, err := NewManifestStorage(tmpDir, log)
392	require.NoError(t, err)
393
394	contentType := "application/vnd.docker.distribution.manifest.v2+json"
395
396	// Simulate a complete registry workflow
397	// 1. Push first version
398	v1Data := []byte(`{"schemaVersion": 2, "version": "1.0"}`)
399	err = storage.PutManifest("myorg/myapp", "v1.0", contentType, v1Data)
400	require.NoError(t, err)
401	err = storage.PutManifest("myorg/myapp", "latest", contentType, v1Data)
402	require.NoError(t, err)
403
404	// 2. Push second version
405	v2Data := []byte(`{"schemaVersion": 2, "version": "2.0"}`)
406	err = storage.PutManifest("myorg/myapp", "v2.0", contentType, v2Data)
407	require.NoError(t, err)
408	err = storage.PutManifest("myorg/myapp", "latest", contentType, v2Data)
409	require.NoError(t, err)
410
411	// 3. Verify tags
412	tags, err := storage.ListTags("myorg/myapp")
413	require.NoError(t, err)
414	assert.Len(t, tags, 3)
415
416	// 4. Verify latest points to v2
417	data, _, err := storage.GetManifest("myorg/myapp", "latest")
418	require.NoError(t, err)
419	assert.Equal(t, v2Data, data)
420
421	// 5. Verify v1 is still accessible
422	data, _, err = storage.GetManifest("myorg/myapp", "v1.0")
423	require.NoError(t, err)
424	assert.Equal(t, v1Data, data)
425
426	// 6. Delete old version
427	err = storage.DeleteManifest("myorg/myapp", "v1.0")
428	require.NoError(t, err)
429
430	// 7. Verify tags updated
431	tags, err = storage.ListTags("myorg/myapp")
432	require.NoError(t, err)
433	assert.Len(t, tags, 2)
434	assert.NotContains(t, tags, "v1.0")
435
436	// 8. List repositories
437	repos, err := storage.ListRepositories()
438	require.NoError(t, err)
439	assert.Contains(t, repos, "myorg/myapp")
440}

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