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}