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}