1package filesystem
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "io"
9 "os"
10 "path/filepath"
11 "sync"
12 "testing"
13
14 "github.com/bnema/zerowrap"
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17
18 "github.com/bnema/gordon/internal/domain"
19)
20
21const (
22 testDigest1 = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
23 testDigest2 = "sha256:b3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d5"
24 testDigestSHA512 = "sha512:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
25 testDigestMissing = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
26)
27
28func testLogger() zerowrap.Logger {
29 return zerowrap.Default()
30}
31
32type readFailer struct {
33 read bool
34}
35
36func (r *readFailer) Read(_ []byte) (int, error) {
37 r.read = true
38 return 0, io.ErrUnexpectedEOF
39}
40
41func TestNewBlobStorage(t *testing.T) {
42 tmpDir := t.TempDir()
43 log := testLogger()
44
45 storage, err := NewBlobStorage(tmpDir, log)
46
47 require.NoError(t, err)
48 assert.NotNil(t, storage)
49
50 // Verify directories were created
51 assert.DirExists(t, filepath.Join(tmpDir, "blobs"))
52 assert.DirExists(t, filepath.Join(tmpDir, "uploads"))
53}
54
55func TestNewBlobStorage_InvalidPath(t *testing.T) {
56 log := testLogger()
57
58 // Try to create in a path that can't be created (no permissions)
59 _, err := NewBlobStorage("/nonexistent/deeply/nested/path/that/should/fail", log)
60
61 assert.Error(t, err)
62}
63
64func TestBlobStorage_PutAndGetBlob(t *testing.T) {
65 tmpDir := t.TempDir()
66 log := testLogger()
67
68 storage, err := NewBlobStorage(tmpDir, log)
69 require.NoError(t, err)
70
71 blobData := []byte("test blob content")
72
73 // Put blob
74 err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
75 require.NoError(t, err)
76
77 // Get blob
78 reader, err := storage.GetBlob(testDigest1)
79 require.NoError(t, err)
80 defer reader.Close()
81
82 data, err := io.ReadAll(reader)
83 require.NoError(t, err)
84
85 assert.Equal(t, blobData, data)
86}
87
88func TestBlobStorage_GetBlob_NotFound(t *testing.T) {
89 tmpDir := t.TempDir()
90 log := testLogger()
91
92 storage, err := NewBlobStorage(tmpDir, log)
93 require.NoError(t, err)
94
95 reader, err := storage.GetBlob(testDigestMissing)
96
97 assert.Error(t, err)
98 assert.Nil(t, reader)
99 assert.Contains(t, err.Error(), "blob not found")
100}
101
102func TestBlobStorage_GetBlobPath(t *testing.T) {
103 tmpDir := t.TempDir()
104 log := testLogger()
105
106 storage, err := NewBlobStorage(tmpDir, log)
107 require.NoError(t, err)
108
109 blobData := []byte("test blob content")
110
111 // Put blob first
112 err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
113 require.NoError(t, err)
114
115 // Get blob path
116 path, err := storage.GetBlobPath(testDigest1)
117 require.NoError(t, err)
118 assert.NotEmpty(t, path)
119
120 // Verify file exists at path
121 _, err = os.Stat(path)
122 assert.NoError(t, err)
123}
124
125func TestBlobStorage_GetBlobPath_NotFound(t *testing.T) {
126 tmpDir := t.TempDir()
127 log := testLogger()
128
129 storage, err := NewBlobStorage(tmpDir, log)
130 require.NoError(t, err)
131
132 path, err := storage.GetBlobPath(testDigestMissing)
133
134 assert.Error(t, err)
135 assert.Empty(t, path)
136 assert.Contains(t, err.Error(), "blob not found")
137}
138
139func TestBlobStorage_PutBlob_SizeMismatch(t *testing.T) {
140 tmpDir := t.TempDir()
141 log := testLogger()
142
143 storage, err := NewBlobStorage(tmpDir, log)
144 require.NoError(t, err)
145
146 blobData := []byte("test blob content")
147
148 // Put blob with wrong size
149 err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), 999)
150
151 assert.Error(t, err)
152 assert.Contains(t, err.Error(), "size mismatch")
153}
154
155func TestBlobStorage_PutBlob_ZeroSize(t *testing.T) {
156 tmpDir := t.TempDir()
157 log := testLogger()
158
159 storage, err := NewBlobStorage(tmpDir, log)
160 require.NoError(t, err)
161
162 blobData := []byte("test blob content")
163
164 // Put blob with size=0 (should skip size check)
165 err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), 0)
166
167 assert.NoError(t, err)
168
169 // Verify blob was stored
170 assert.True(t, storage.BlobExists(testDigest2))
171}
172
173func TestBlobStorage_DeleteBlob(t *testing.T) {
174 tmpDir := t.TempDir()
175 log := testLogger()
176
177 storage, err := NewBlobStorage(tmpDir, log)
178 require.NoError(t, err)
179
180 blobData := []byte("test blob content")
181
182 // Put blob
183 err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), int64(len(blobData)))
184 require.NoError(t, err)
185
186 // Verify it exists
187 assert.True(t, storage.BlobExists(testDigest2))
188
189 // Delete blob
190 size, err := storage.DeleteBlob(testDigest2)
191 require.NoError(t, err)
192 assert.Equal(t, int64(len(blobData)), size)
193
194 // Verify it's gone
195 assert.False(t, storage.BlobExists(testDigest2))
196}
197
198func TestBlobStorage_DeleteBlob_NotFound(t *testing.T) {
199 tmpDir := t.TempDir()
200 log := testLogger()
201
202 storage, err := NewBlobStorage(tmpDir, log)
203 require.NoError(t, err)
204
205 size, err := storage.DeleteBlob(testDigestMissing)
206
207 assert.Error(t, err)
208 assert.Equal(t, int64(0), size)
209 assert.Contains(t, err.Error(), "blob not found")
210}
211
212func TestBlobStorage_BlobExists(t *testing.T) {
213 tmpDir := t.TempDir()
214 log := testLogger()
215
216 storage, err := NewBlobStorage(tmpDir, log)
217 require.NoError(t, err)
218
219 // Should not exist initially
220 assert.False(t, storage.BlobExists(testDigest2))
221
222 // Put blob
223 blobData := []byte("test blob content")
224 err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), int64(len(blobData)))
225 require.NoError(t, err)
226
227 // Should exist now
228 assert.True(t, storage.BlobExists(testDigest2))
229}
230
231func TestBlobStorage_ListBlobs(t *testing.T) {
232 tmpDir := t.TempDir()
233 log := testLogger()
234
235 storage, err := NewBlobStorage(tmpDir, log)
236 require.NoError(t, err)
237
238 blobData := []byte("test blob content")
239
240 err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
241 require.NoError(t, err)
242
243 err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), int64(len(blobData)))
244 require.NoError(t, err)
245
246 digests, err := storage.ListBlobs()
247 require.NoError(t, err)
248
249 assert.ElementsMatch(t, []string{testDigest1, testDigest2}, digests)
250}
251
252func TestBlobStorage_ListBlobs_Empty(t *testing.T) {
253 tmpDir := t.TempDir()
254 log := testLogger()
255
256 storage, err := NewBlobStorage(tmpDir, log)
257 require.NoError(t, err)
258
259 digests, err := storage.ListBlobs()
260 require.NoError(t, err)
261 assert.Empty(t, digests)
262}
263
264func TestBlobStorage_StartBlobUpload(t *testing.T) {
265 tmpDir := t.TempDir()
266 log := testLogger()
267
268 storage, err := NewBlobStorage(tmpDir, log)
269 require.NoError(t, err)
270
271 uuid, err := storage.StartBlobUpload("myapp")
272
273 require.NoError(t, err)
274 assert.NotEmpty(t, uuid)
275 assert.Len(t, uuid, 36) // standard UUID format
276
277 // Verify upload file was created
278 uploadPath := filepath.Join(tmpDir, "uploads", uuid)
279 _, err = os.Stat(uploadPath)
280 assert.NoError(t, err)
281}
282
283func TestBlobStorage_AppendBlobChunk(t *testing.T) {
284 tmpDir := t.TempDir()
285 log := testLogger()
286
287 storage, err := NewBlobStorage(tmpDir, log)
288 require.NoError(t, err)
289
290 uuid, err := storage.StartBlobUpload("myapp")
291 require.NoError(t, err)
292
293 // Append first chunk
294 chunk1 := []byte("first chunk")
295 size1, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk1), -1, 0)
296 require.NoError(t, err)
297 assert.Equal(t, int64(len(chunk1)), size1)
298
299 // Append second chunk
300 chunk2 := []byte(" second chunk")
301 size2, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk2), -1, 0)
302 require.NoError(t, err)
303 assert.Equal(t, int64(len(chunk1)+len(chunk2)), size2)
304}
305
306func TestBlobStorage_AppendBlobChunk_NotFound(t *testing.T) {
307 tmpDir := t.TempDir()
308 log := testLogger()
309
310 storage, err := NewBlobStorage(tmpDir, log)
311 require.NoError(t, err)
312
313 _, err = storage.AppendBlobChunk("myapp", "00000000-0000-4000-a000-000000000000", bytes.NewReader([]byte("data")), -1, 100)
314
315 assert.Error(t, err)
316 assert.Contains(t, err.Error(), "upload not found")
317}
318
319func TestBlobStorage_AppendBlobChunk_MaxBlobSize(t *testing.T) {
320 tmpDir := t.TempDir()
321 log := testLogger()
322
323 storage, err := NewBlobStorage(tmpDir, log)
324 require.NoError(t, err)
325
326 uuid, err := storage.StartBlobUpload("myapp")
327 require.NoError(t, err)
328
329 size, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("12345")), -1, 10)
330 require.NoError(t, err)
331 assert.Equal(t, int64(5), size)
332
333 size, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("67890")), -1, 10)
334 require.NoError(t, err)
335 assert.Equal(t, int64(10), size)
336
337 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("x")), -1, 10)
338 require.Error(t, err)
339 assert.True(t, errors.Is(err, domain.ErrBlobSizeExceeded))
340
341 uploadPath := filepath.Join(tmpDir, "uploads", uuid)
342 info, statErr := os.Stat(uploadPath)
343 require.NoError(t, statErr)
344 assert.Equal(t, int64(10), info.Size())
345}
346
347func TestBlobStorage_AppendBlobChunk_MaxBlobSizeContentLengthTooLargeDoesNotRead(t *testing.T) {
348 tmpDir := t.TempDir()
349 log := testLogger()
350
351 storage, err := NewBlobStorage(tmpDir, log)
352 require.NoError(t, err)
353
354 uuid, err := storage.StartBlobUpload("myapp")
355 require.NoError(t, err)
356
357 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("12345")), 5, 10)
358 require.NoError(t, err)
359
360 reader := &readFailer{}
361 _, err = storage.AppendBlobChunk("myapp", uuid, reader, 6, 10)
362 require.Error(t, err)
363 assert.ErrorIs(t, err, domain.ErrBlobSizeExceeded)
364 assert.False(t, reader.read)
365}
366
367func TestBlobStorage_AppendBlobChunk_MaxBlobSizeOverflowMidReadTruncates(t *testing.T) {
368 tmpDir := t.TempDir()
369 log := testLogger()
370
371 storage, err := NewBlobStorage(tmpDir, log)
372 require.NoError(t, err)
373
374 uuid, err := storage.StartBlobUpload("myapp")
375 require.NoError(t, err)
376
377 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("12345")), -1, 10)
378 require.NoError(t, err)
379
380 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("6789012345")), -1, 10)
381 require.Error(t, err)
382 assert.True(t, errors.Is(err, domain.ErrBlobSizeExceeded))
383
384 uploadPath := filepath.Join(tmpDir, "uploads", uuid)
385 info, statErr := os.Stat(uploadPath)
386 require.NoError(t, statErr)
387 assert.Equal(t, int64(5), info.Size())
388}
389
390func TestBlobStorage_GetBlobUpload(t *testing.T) {
391 tmpDir := t.TempDir()
392 log := testLogger()
393
394 storage, err := NewBlobStorage(tmpDir, log)
395 require.NoError(t, err)
396
397 uuid, err := storage.StartBlobUpload("myapp")
398 require.NoError(t, err)
399
400 writer, err := storage.GetBlobUpload(uuid)
401 require.NoError(t, err)
402 defer writer.Close()
403
404 // Write some data
405 _, err = writer.Write([]byte("test data"))
406 assert.NoError(t, err)
407}
408
409func TestBlobStorage_GetBlobUpload_NotFound(t *testing.T) {
410 tmpDir := t.TempDir()
411 log := testLogger()
412
413 storage, err := NewBlobStorage(tmpDir, log)
414 require.NoError(t, err)
415
416 writer, err := storage.GetBlobUpload("00000000-0000-4000-a000-000000000000")
417
418 assert.Error(t, err)
419 assert.Nil(t, writer)
420 assert.Contains(t, err.Error(), "upload not found")
421}
422
423func TestBlobStorage_FinishBlobUpload(t *testing.T) {
424 tmpDir := t.TempDir()
425 log := testLogger()
426
427 storage, err := NewBlobStorage(tmpDir, log)
428 require.NoError(t, err)
429
430 // Start upload
431 uuid, err := storage.StartBlobUpload("myapp")
432 require.NoError(t, err)
433
434 // Append data - content that hashes to testDigest1
435 blobData := []byte("complete blob content")
436 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(blobData), -1, 0)
437 require.NoError(t, err)
438
439 // Finish upload - use testDigest1 (validation will pass, but hash won't match)
440 // This tests the flow, actual digest verification happens in verifyUploadDigest
441 err = storage.FinishBlobUpload(uuid, testDigest1)
442 // This will fail because content doesn't match digest
443 assert.Error(t, err)
444 assert.Contains(t, err.Error(), "digest")
445}
446
447func TestBlobStorage_CancelBlobUpload(t *testing.T) {
448 tmpDir := t.TempDir()
449 log := testLogger()
450
451 storage, err := NewBlobStorage(tmpDir, log)
452 require.NoError(t, err)
453
454 // Start upload
455 uuid, err := storage.StartBlobUpload("myapp")
456 require.NoError(t, err)
457
458 // Verify upload file exists
459 uploadPath := filepath.Join(tmpDir, "uploads", uuid)
460 _, err = os.Stat(uploadPath)
461 require.NoError(t, err)
462
463 // Cancel upload
464 err = storage.CancelBlobUpload(uuid)
465 require.NoError(t, err)
466
467 // Verify upload file is gone
468 _, err = os.Stat(uploadPath)
469 assert.True(t, os.IsNotExist(err))
470}
471
472func TestBlobStorage_CancelBlobUpload_NotFound(t *testing.T) {
473 tmpDir := t.TempDir()
474 log := testLogger()
475
476 storage, err := NewBlobStorage(tmpDir, log)
477 require.NoError(t, err)
478
479 err = storage.CancelBlobUpload("00000000-0000-4000-a000-000000000000")
480
481 assert.Error(t, err)
482 assert.Contains(t, err.Error(), "upload not found")
483}
484
485func TestBlobStorage_BlobPathStructure(t *testing.T) {
486 tmpDir := t.TempDir()
487 log := testLogger()
488
489 storage, err := NewBlobStorage(tmpDir, log)
490 require.NoError(t, err)
491
492 // Test that blobs are stored with proper directory structure
493 blobData := []byte("test blob content")
494
495 err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
496 require.NoError(t, err)
497
498 // Check the path structure: blobs/sha256/a3/a3ed95caeb02...
499 // First 2 chars of hash after sha256: are "a3"
500 expectedPath := filepath.Join(tmpDir, "blobs", "sha256", "a3", "a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
501 _, err = os.Stat(expectedPath)
502 assert.NoError(t, err)
503}
504
505func TestBlobStorage_DigestFormats(t *testing.T) {
506 tmpDir := t.TempDir()
507 log := testLogger()
508
509 storage, err := NewBlobStorage(tmpDir, log)
510 require.NoError(t, err)
511
512 tests := []struct {
513 name string
514 digest string
515 }{
516 {"sha256 standard", testDigest1},
517 {"sha512", testDigestSHA512},
518 }
519
520 for _, tt := range tests {
521 t.Run(tt.name, func(t *testing.T) {
522 blobData := []byte("test content for " + tt.digest)
523
524 err := storage.PutBlob(tt.digest, bytes.NewReader(blobData), int64(len(blobData)))
525 require.NoError(t, err)
526
527 assert.True(t, storage.BlobExists(tt.digest))
528
529 reader, err := storage.GetBlob(tt.digest)
530 require.NoError(t, err)
531 defer reader.Close()
532
533 data, err := io.ReadAll(reader)
534 require.NoError(t, err)
535 assert.Equal(t, blobData, data)
536 })
537 }
538}
539
540func TestBlobStorage_CompleteUploadFlow(t *testing.T) {
541 tmpDir := t.TempDir()
542 log := testLogger()
543
544 storage, err := NewBlobStorage(tmpDir, log)
545 require.NoError(t, err)
546
547 // Simulate a complete upload flow
548 repoName := "myorg/myapp"
549
550 // 1. Start upload
551 uuid, err := storage.StartBlobUpload(repoName)
552 require.NoError(t, err)
553
554 // 2. Send multiple chunks
555 chunks := [][]byte{
556 []byte("first chunk of data"),
557 []byte(" - middle chunk"),
558 []byte(" - final chunk"),
559 }
560
561 var totalSize int64
562 for _, chunk := range chunks {
563 size, err := storage.AppendBlobChunk(repoName, uuid, bytes.NewReader(chunk), -1, 0)
564 require.NoError(t, err)
565 totalSize = size
566 }
567
568 expectedTotal := int64(len(chunks[0]) + len(chunks[1]) + len(chunks[2]))
569 assert.Equal(t, expectedTotal, totalSize)
570
571 // 3. Cancel upload (since we can't easily compute the real digest)
572 err = storage.CancelBlobUpload(uuid)
573 require.NoError(t, err)
574}
575
576func TestBlobStorage_ConcurrentAppendAndFinalize(t *testing.T) {
577 tmpDir := t.TempDir()
578 log := testLogger()
579
580 storage, err := NewBlobStorage(tmpDir, log)
581 require.NoError(t, err)
582
583 uuid, err := storage.StartBlobUpload("myapp")
584 require.NoError(t, err)
585
586 initialData := []byte("initial-")
587 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(initialData), -1, 0)
588 require.NoError(t, err)
589
590 appendChunk := []byte("chunk")
591 appenders := 8
592 var wg sync.WaitGroup
593 appendReady := make(chan struct{})
594 finalizeErrs := make(chan error, 1)
595 appendErrs := make(chan error, appenders)
596
597 for range appenders {
598 wg.Add(1)
599 go func() {
600 defer wg.Done()
601 <-appendReady
602 _, appendErr := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(appendChunk), -1, 0)
603 appendErrs <- appendErr
604 }()
605 }
606
607 close(appendReady)
608 fullyAppended := appendRepeated(initialData, appendChunk, appenders)
609
610 wg.Add(1)
611 go func() {
612 defer wg.Done()
613 finalizeErrs <- storage.FinishBlobUpload(uuid, digestForData(fullyAppended))
614 }()
615
616 wg.Wait()
617 close(appendErrs)
618
619 finalizeErr := <-finalizeErrs
620 if finalizeErr == nil {
621 for appendErr := range appendErrs {
622 require.NoError(t, appendErr)
623 }
624
625 reader, err := storage.GetBlob(digestForData(fullyAppended))
626 require.NoError(t, err)
627 defer reader.Close()
628
629 blobData, err := io.ReadAll(reader)
630 require.NoError(t, err)
631 assert.Equal(t, fullyAppended, blobData)
632 } else {
633 assert.Contains(t, finalizeErr.Error(), "digest verification failed")
634
635 appendCount := 0
636 for appendErr := range appendErrs {
637 require.NoError(t, appendErr)
638 appendCount++
639 }
640
641 require.Equal(t, appenders, appendCount)
642
643 retryDigest := digestForData(fullyAppended)
644 err = storage.FinishBlobUpload(uuid, retryDigest)
645 require.NoError(t, err)
646
647 reader, err := storage.GetBlob(retryDigest)
648 require.NoError(t, err)
649 defer reader.Close()
650
651 blobData, err := io.ReadAll(reader)
652 require.NoError(t, err)
653 assert.Equal(t, fullyAppended, blobData)
654 }
655
656 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("after")), -1, 0)
657 assert.Error(t, err)
658 assert.Contains(t, err.Error(), "upload not found")
659}
660
661func TestBlobStorage_AppendAfterFinalize(t *testing.T) {
662 tmpDir := t.TempDir()
663 log := testLogger()
664
665 storage, err := NewBlobStorage(tmpDir, log)
666 require.NoError(t, err)
667
668 uuid, err := storage.StartBlobUpload("myapp")
669 require.NoError(t, err)
670
671 blobData := []byte("finalized content")
672 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(blobData), -1, 0)
673 require.NoError(t, err)
674
675 err = storage.FinishBlobUpload(uuid, digestForData(blobData))
676 require.NoError(t, err)
677
678 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader([]byte("more")), -1, 0)
679 assert.Error(t, err)
680 assert.Contains(t, err.Error(), "upload not found")
681}
682
683func TestBlobStorage_FailedFinalizeRetryable(t *testing.T) {
684 tmpDir := t.TempDir()
685 log := testLogger()
686
687 storage, err := NewBlobStorage(tmpDir, log)
688 require.NoError(t, err)
689
690 uuid, err := storage.StartBlobUpload("myapp")
691 require.NoError(t, err)
692
693 blobData := []byte("retryable finalize")
694 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(blobData), -1, 0)
695 require.NoError(t, err)
696
697 err = storage.FinishBlobUpload(uuid, testDigest1)
698 assert.Error(t, err)
699 assert.Contains(t, err.Error(), "digest verification failed")
700
701 err = storage.FinishBlobUpload(uuid, digestForData(blobData))
702 require.NoError(t, err)
703}
704
705func TestBlobStorage_SequentialFlow(t *testing.T) {
706 tmpDir := t.TempDir()
707 log := testLogger()
708
709 storage, err := NewBlobStorage(tmpDir, log)
710 require.NoError(t, err)
711
712 uuid, err := storage.StartBlobUpload("myapp")
713 require.NoError(t, err)
714
715 chunks := [][]byte{
716 []byte("one-"),
717 []byte("two-"),
718 []byte("three"),
719 }
720
721 var blobData []byte
722 for _, chunk := range chunks {
723 blobData = append(blobData, chunk...)
724 _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk), -1, 0)
725 require.NoError(t, err)
726 }
727
728 digest := digestForData(blobData)
729 err = storage.FinishBlobUpload(uuid, digest)
730 require.NoError(t, err)
731
732 reader, err := storage.GetBlob(digest)
733 require.NoError(t, err)
734 defer reader.Close()
735
736 storedData, err := io.ReadAll(reader)
737 require.NoError(t, err)
738 assert.Equal(t, blobData, storedData)
739}
740
741func digestForData(data []byte) string {
742 sum := sha256.Sum256(data)
743 return "sha256:" + hex.EncodeToString(sum[:])
744}
745
746func appendRepeated(initialData, chunk []byte, repetitions int) []byte {
747 data := append([]byte{}, initialData...)
748 for range repetitions {
749 data = append(data, chunk...)
750 }
751 return data
752}