v2.0.0 ae3e752

storage_test.go RawHistoryFolder

1package registry
2
3import (
4	"io"
5	"os"
6	"path/filepath"
7	"strings"
8	"testing"
9
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestNewFilesystemStorage(t *testing.T) {
15	tmpDir := t.TempDir()
16
17	storage, err := NewFilesystemStorage(tmpDir)
18
19	require.NoError(t, err)
20	assert.NotNil(t, storage)
21	assert.Equal(t, tmpDir, storage.rootDir)
22
23	// Check that directories were created
24	assert.DirExists(t, filepath.Join(tmpDir, "repositories"))
25	assert.DirExists(t, filepath.Join(tmpDir, "blobs"))
26	assert.DirExists(t, filepath.Join(tmpDir, "uploads"))
27}
28
29func TestNewFilesystemStorage_InvalidDir(t *testing.T) {
30	// Try to create storage in a file instead of directory
31	tmpFile := filepath.Join(t.TempDir(), "file")
32	err := os.WriteFile(tmpFile, []byte("test"), 0644)
33	require.NoError(t, err)
34
35	_, err = NewFilesystemStorage(filepath.Join(tmpFile, "subdir"))
36	assert.Error(t, err)
37}
38
39func TestFilesystemStorage_ManifestOperations(t *testing.T) {
40	storage := createTestStorage(t)
41
42	name := "myapp"
43	reference := "latest"
44	contentType := "application/vnd.docker.distribution.manifest.v2+json"
45	manifestData := []byte(`{"test": "manifest"}`)
46
47	// Test PutManifest
48	err := storage.PutManifest(name, reference, contentType, manifestData)
49	assert.NoError(t, err)
50
51	// Test GetManifest
52	data, ct, err := storage.GetManifest(name, reference)
53	assert.NoError(t, err)
54	assert.Equal(t, manifestData, data)
55	assert.Equal(t, contentType, ct)
56
57	// Test manifest file exists
58	manifestPath := storage.getManifestPath(name, reference)
59	assert.FileExists(t, manifestPath)
60
61	// Test DeleteManifest
62	err = storage.DeleteManifest(name, reference)
63	assert.NoError(t, err)
64
65	// Test GetManifest after deletion
66	_, _, err = storage.GetManifest(name, reference)
67	assert.Error(t, err)
68	assert.Contains(t, err.Error(), "manifest not found")
69}
70
71func TestFilesystemStorage_GetManifest_NotFound(t *testing.T) {
72	storage := createTestStorage(t)
73
74	_, _, err := storage.GetManifest("nonexistent", "latest")
75	assert.Error(t, err)
76	assert.Contains(t, err.Error(), "manifest not found")
77}
78
79func TestFilesystemStorage_DeleteManifest_NotFound(t *testing.T) {
80	storage := createTestStorage(t)
81
82	err := storage.DeleteManifest("nonexistent", "latest")
83	assert.Error(t, err)
84	assert.Contains(t, err.Error(), "manifest not found")
85}
86
87func TestFilesystemStorage_ManifestContentType_Fallback(t *testing.T) {
88	storage := createTestStorage(t)
89
90	name := "myapp"
91	reference := "latest"
92	manifestData := []byte(`{"test": "manifest"}`)
93
94	// Manually create manifest without content type file
95	manifestPath := storage.getManifestPath(name, reference)
96	err := os.MkdirAll(filepath.Dir(manifestPath), 0755)
97	require.NoError(t, err)
98	err = os.WriteFile(manifestPath, manifestData, 0644)
99	require.NoError(t, err)
100
101	// Should use fallback content type
102	data, ct, err := storage.GetManifest(name, reference)
103	assert.NoError(t, err)
104	assert.Equal(t, manifestData, data)
105	assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", ct)
106}
107
108func TestFilesystemStorage_BlobOperations(t *testing.T) {
109	storage := createTestStorage(t)
110
111	digest := "sha256:abc123"
112	blobData := "test blob data"
113
114	// Test PutBlob
115	err := storage.PutBlob(digest, strings.NewReader(blobData), int64(len(blobData)))
116	assert.NoError(t, err)
117
118	// Test BlobExists
119	assert.True(t, storage.BlobExists(digest))
120	assert.False(t, storage.BlobExists("sha256:nonexistent"))
121
122	// Test GetBlobPath
123	path, err := storage.GetBlobPath(digest)
124	assert.NoError(t, err)
125	assert.FileExists(t, path)
126
127	// Test GetBlob
128	reader, err := storage.GetBlob(digest)
129	assert.NoError(t, err)
130	defer reader.Close()
131
132	readData, err := io.ReadAll(reader)
133	assert.NoError(t, err)
134	assert.Equal(t, blobData, string(readData))
135
136	// Test DeleteBlob
137	err = storage.DeleteBlob(digest)
138	assert.NoError(t, err)
139
140	// Test GetBlob after deletion
141	_, err = storage.GetBlob(digest)
142	assert.Error(t, err)
143	assert.Contains(t, err.Error(), "blob not found")
144}
145
146func TestFilesystemStorage_GetBlob_NotFound(t *testing.T) {
147	storage := createTestStorage(t)
148
149	_, err := storage.GetBlob("sha256:nonexistent")
150	assert.Error(t, err)
151	assert.Contains(t, err.Error(), "blob not found")
152}
153
154func TestFilesystemStorage_GetBlobPath_NotFound(t *testing.T) {
155	storage := createTestStorage(t)
156
157	_, err := storage.GetBlobPath("sha256:nonexistent")
158	assert.Error(t, err)
159	assert.Contains(t, err.Error(), "blob not found")
160}
161
162func TestFilesystemStorage_DeleteBlob_NotFound(t *testing.T) {
163	storage := createTestStorage(t)
164
165	err := storage.DeleteBlob("sha256:nonexistent")
166	assert.Error(t, err)
167	assert.Contains(t, err.Error(), "blob not found")
168}
169
170func TestFilesystemStorage_PutBlob_SizeMismatch(t *testing.T) {
171	storage := createTestStorage(t)
172
173	digest := "sha256:abc123"
174	blobData := "test blob data"
175	wrongSize := int64(100) // Wrong size
176
177	err := storage.PutBlob(digest, strings.NewReader(blobData), wrongSize)
178	assert.Error(t, err)
179	assert.Contains(t, err.Error(), "blob size mismatch")
180}
181
182func TestFilesystemStorage_BlobUploadOperations(t *testing.T) {
183	storage := createTestStorage(t)
184
185	name := "myapp"
186
187	// Test StartBlobUpload
188	uuid, err := storage.StartBlobUpload(name)
189	assert.NoError(t, err)
190	assert.NotEmpty(t, uuid)
191
192	// Test upload file exists
193	uploadPath := storage.getUploadPath(uuid)
194	assert.FileExists(t, uploadPath)
195
196	// Test AppendBlobChunk
197	chunk1 := []byte("chunk1")
198	length1, err := storage.AppendBlobChunk(name, uuid, chunk1)
199	assert.NoError(t, err)
200	assert.Equal(t, int64(len(chunk1)), length1)
201
202	chunk2 := []byte("chunk2")
203	length2, err := storage.AppendBlobChunk(name, uuid, chunk2)
204	assert.NoError(t, err)
205	assert.Equal(t, int64(len(chunk1)+len(chunk2)), length2)
206
207	// Test GetBlobUpload
208	writer, err := storage.GetBlobUpload(uuid)
209	assert.NoError(t, err)
210	writer.Close()
211
212	// Test FinishBlobUpload
213	digest := "sha256:test123"
214	err = storage.FinishBlobUpload(uuid, digest)
215	assert.NoError(t, err)
216
217	// Test blob was moved to correct location
218	blobPath := storage.getBlobPath(digest)
219	assert.FileExists(t, blobPath)
220
221	// Test upload file was removed
222	assert.NoFileExists(t, uploadPath)
223
224	// Verify blob content
225	data, err := os.ReadFile(blobPath)
226	assert.NoError(t, err)
227	assert.Equal(t, "chunk1chunk2", string(data))
228}
229
230func TestFilesystemStorage_AppendBlobChunk_NotFound(t *testing.T) {
231	storage := createTestStorage(t)
232
233	_, err := storage.AppendBlobChunk("myapp", "nonexistent-uuid", []byte("chunk"))
234	assert.Error(t, err)
235	assert.Contains(t, err.Error(), "upload not found")
236}
237
238func TestFilesystemStorage_GetBlobUpload_NotFound(t *testing.T) {
239	storage := createTestStorage(t)
240
241	_, err := storage.GetBlobUpload("nonexistent-uuid")
242	assert.Error(t, err)
243	assert.Contains(t, err.Error(), "upload not found")
244}
245
246func TestFilesystemStorage_CancelBlobUpload(t *testing.T) {
247	storage := createTestStorage(t)
248
249	name := "myapp"
250	uuid, err := storage.StartBlobUpload(name)
251	require.NoError(t, err)
252
253	uploadPath := storage.getUploadPath(uuid)
254	assert.FileExists(t, uploadPath)
255
256	err = storage.CancelBlobUpload(uuid)
257	assert.NoError(t, err)
258
259	// Upload file should be removed
260	assert.NoFileExists(t, uploadPath)
261}
262
263func TestFilesystemStorage_CancelBlobUpload_NotFound(t *testing.T) {
264	storage := createTestStorage(t)
265
266	err := storage.CancelBlobUpload("nonexistent-uuid")
267	assert.Error(t, err)
268	assert.Contains(t, err.Error(), "upload not found")
269}
270
271func TestFilesystemStorage_TagOperations(t *testing.T) {
272	storage := createTestStorage(t)
273
274	name := "myapp"
275
276	// Test ListTags when no tags exist
277	tags, err := storage.ListTags(name)
278	assert.NoError(t, err)
279	assert.Empty(t, tags)
280
281	// Add some manifests to create tags
282	err = storage.PutManifest(name, "v1.0", "application/vnd.docker.distribution.manifest.v2+json", []byte(`{"test": "v1.0"}`))
283	assert.NoError(t, err)
284
285	err = storage.PutManifest(name, "latest", "application/vnd.docker.distribution.manifest.v2+json", []byte(`{"test": "latest"}`))
286	assert.NoError(t, err)
287
288	// Test ListTags after adding manifests
289	tags, err = storage.ListTags(name)
290	assert.NoError(t, err)
291	assert.Len(t, tags, 2)
292	assert.Contains(t, tags, "v1.0")
293	assert.Contains(t, tags, "latest")
294
295	// Test adding duplicate tag (should not duplicate)
296	err = storage.PutManifest(name, "v1.0", "application/vnd.docker.distribution.manifest.v2+json", []byte(`{"test": "v1.0-updated"}`))
297	assert.NoError(t, err)
298
299	tags, err = storage.ListTags(name)
300	assert.NoError(t, err)
301	assert.Len(t, tags, 2) // Should still be 2
302
303	// Test removing a tag
304	err = storage.DeleteManifest(name, "v1.0")
305	assert.NoError(t, err)
306
307	tags, err = storage.ListTags(name)
308	assert.NoError(t, err)
309	assert.Len(t, tags, 1)
310	assert.Contains(t, tags, "latest")
311	assert.NotContains(t, tags, "v1.0")
312}
313
314func TestFilesystemStorage_ListRepositories(t *testing.T) {
315	storage := createTestStorage(t)
316
317	// Test when no repositories exist
318	repos, err := storage.ListRepositories()
319	assert.NoError(t, err)
320	assert.Empty(t, repos)
321
322	// Add some repositories by creating manifests
323	err = storage.PutManifest("app1", "latest", "application/vnd.docker.distribution.manifest.v2+json", []byte(`{"test": "app1"}`))
324	assert.NoError(t, err)
325
326	err = storage.PutManifest("namespace/app2", "v1.0", "application/vnd.docker.distribution.manifest.v2+json", []byte(`{"test": "app2"}`))
327	assert.NoError(t, err)
328
329	// Test ListRepositories
330	repos, err = storage.ListRepositories()
331	assert.NoError(t, err)
332	assert.Len(t, repos, 2)
333	assert.Contains(t, repos, "app1")
334	assert.Contains(t, repos, "namespace/app2")
335}
336
337func TestFilesystemStorage_PathGenerationMethods(t *testing.T) {
338	storage := createTestStorage(t)
339
340	// Test manifest path generation
341	manifestPath := storage.getManifestPath("myapp", "latest")
342	expected := filepath.Join(storage.rootDir, "repositories", "myapp", "manifests", "latest")
343	assert.Equal(t, expected, manifestPath)
344
345	// Test blob path generation with standard digest
346	blobPath := storage.getBlobPath("sha256:abc123def456")
347	expected = filepath.Join(storage.rootDir, "blobs", "sha256", "ab", "abc123def456")
348	assert.Equal(t, expected, blobPath)
349
350	// Test blob path generation with short hash
351	blobPath = storage.getBlobPath("sha256:a")
352	expected = filepath.Join(storage.rootDir, "blobs", "sha256", "a")
353	assert.Equal(t, expected, blobPath)
354
355	// Test blob path generation with invalid digest format
356	blobPath = storage.getBlobPath("invaliddigest")
357	expected = filepath.Join(storage.rootDir, "blobs", "invaliddigest")
358	assert.Equal(t, expected, blobPath)
359
360	// Test upload path generation
361	uploadPath := storage.getUploadPath("test-uuid")
362	expected = filepath.Join(storage.rootDir, "uploads", "test-uuid")
363	assert.Equal(t, expected, uploadPath)
364
365	// Test tags path generation
366	tagsPath := storage.getTagsPath("myapp")
367	expected = filepath.Join(storage.rootDir, "repositories", "myapp", "tags.json")
368	assert.Equal(t, expected, tagsPath)
369
370	// Test content type path generation
371	ctPath := storage.getManifestContentTypePath("myapp", "latest")
372	expected = filepath.Join(storage.rootDir, "repositories", "myapp", "manifests", "latest.contenttype")
373	assert.Equal(t, expected, ctPath)
374}
375
376func TestFilesystemStorage_ContentTypeOperations(t *testing.T) {
377	storage := createTestStorage(t)
378
379	name := "myapp"
380	reference := "latest"
381	contentType := "application/vnd.oci.image.manifest.v1+json"
382
383	// Create the directory structure first
384	manifestPath := storage.getManifestPath(name, reference)
385	err := os.MkdirAll(filepath.Dir(manifestPath), 0755)
386	require.NoError(t, err)
387
388	// Test putManifestContentType
389	err = storage.putManifestContentType(name, reference, contentType)
390	assert.NoError(t, err)
391
392	// Test getManifestContentType
393	ct, err := storage.getManifestContentType(name, reference)
394	assert.NoError(t, err)
395	assert.Equal(t, contentType, ct)
396
397	// Test deleteManifestContentType
398	err = storage.deleteManifestContentType(name, reference)
399	assert.NoError(t, err)
400
401	// Test getManifestContentType after deletion
402	_, err = storage.getManifestContentType(name, reference)
403	assert.Error(t, err)
404
405	// Test deleting non-existent content type (should not error)
406	err = storage.deleteManifestContentType(name, reference)
407	assert.NoError(t, err)
408}
409
410func TestFilesystemStorage_TagsManagement(t *testing.T) {
411	storage := createTestStorage(t)
412
413	name := "myapp"
414	tags := []string{"v1.0", "latest", "stable"}
415
416	// Test saveTagsList
417	err := storage.saveTagsList(name, tags)
418	assert.NoError(t, err)
419
420	// Test reading tags back
421	readTags, err := storage.ListTags(name)
422	assert.NoError(t, err)
423	assert.Equal(t, tags, readTags)
424
425	// Test updateTagsList (add new tag)
426	err = storage.updateTagsList(name, "v2.0")
427	assert.NoError(t, err)
428
429	readTags, err = storage.ListTags(name)
430	assert.NoError(t, err)
431	assert.Len(t, readTags, 4)
432	assert.Contains(t, readTags, "v2.0")
433
434	// Test updateTagsList (add existing tag - should not duplicate)
435	err = storage.updateTagsList(name, "v1.0")
436	assert.NoError(t, err)
437
438	readTags, err = storage.ListTags(name)
439	assert.NoError(t, err)
440	assert.Len(t, readTags, 4) // Should still be 4
441
442	// Test removeFromTagsList
443	err = storage.removeFromTagsList(name, "v1.0")
444	assert.NoError(t, err)
445
446	readTags, err = storage.ListTags(name)
447	assert.NoError(t, err)
448	assert.Len(t, readTags, 3)
449	assert.NotContains(t, readTags, "v1.0")
450
451	// Test removing non-existent tag
452	err = storage.removeFromTagsList(name, "nonexistent")
453	assert.NoError(t, err)
454
455	readTags, err = storage.ListTags(name)
456	assert.NoError(t, err)
457	assert.Len(t, readTags, 3) // Should still be 3
458}
459
460// Helper function to create test storage
461func createTestStorage(t *testing.T) *FilesystemStorage {
462	tmpDir := t.TempDir()
463	storage, err := NewFilesystemStorage(tmpDir)
464	require.NoError(t, err)
465	return storage
466}

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