v2.2.3 133ff88

blob_test.go RawHistoryFolder

1package filesystem
2
3import (
4	"bytes"
5	"io"
6	"os"
7	"path/filepath"
8	"testing"
9
10	"github.com/bnema/zerowrap"
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/require"
13)
14
15const (
16	testDigest1       = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
17	testDigest2       = "sha256:b3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d5"
18	testDigestSHA512  = "sha512:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
19	testDigestMissing = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
20)
21
22func testLogger() zerowrap.Logger {
23	return zerowrap.Default()
24}
25
26func TestNewBlobStorage(t *testing.T) {
27	tmpDir := t.TempDir()
28	log := testLogger()
29
30	storage, err := NewBlobStorage(tmpDir, log)
31
32	require.NoError(t, err)
33	assert.NotNil(t, storage)
34
35	// Verify directories were created
36	assert.DirExists(t, filepath.Join(tmpDir, "blobs"))
37	assert.DirExists(t, filepath.Join(tmpDir, "uploads"))
38}
39
40func TestNewBlobStorage_InvalidPath(t *testing.T) {
41	log := testLogger()
42
43	// Try to create in a path that can't be created (no permissions)
44	_, err := NewBlobStorage("/nonexistent/deeply/nested/path/that/should/fail", log)
45
46	assert.Error(t, err)
47}
48
49func TestBlobStorage_PutAndGetBlob(t *testing.T) {
50	tmpDir := t.TempDir()
51	log := testLogger()
52
53	storage, err := NewBlobStorage(tmpDir, log)
54	require.NoError(t, err)
55
56	blobData := []byte("test blob content")
57
58	// Put blob
59	err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
60	require.NoError(t, err)
61
62	// Get blob
63	reader, err := storage.GetBlob(testDigest1)
64	require.NoError(t, err)
65	defer reader.Close()
66
67	data, err := io.ReadAll(reader)
68	require.NoError(t, err)
69
70	assert.Equal(t, blobData, data)
71}
72
73func TestBlobStorage_GetBlob_NotFound(t *testing.T) {
74	tmpDir := t.TempDir()
75	log := testLogger()
76
77	storage, err := NewBlobStorage(tmpDir, log)
78	require.NoError(t, err)
79
80	reader, err := storage.GetBlob(testDigestMissing)
81
82	assert.Error(t, err)
83	assert.Nil(t, reader)
84	assert.Contains(t, err.Error(), "blob not found")
85}
86
87func TestBlobStorage_GetBlobPath(t *testing.T) {
88	tmpDir := t.TempDir()
89	log := testLogger()
90
91	storage, err := NewBlobStorage(tmpDir, log)
92	require.NoError(t, err)
93
94	blobData := []byte("test blob content")
95
96	// Put blob first
97	err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
98	require.NoError(t, err)
99
100	// Get blob path
101	path, err := storage.GetBlobPath(testDigest1)
102	require.NoError(t, err)
103	assert.NotEmpty(t, path)
104
105	// Verify file exists at path
106	_, err = os.Stat(path)
107	assert.NoError(t, err)
108}
109
110func TestBlobStorage_GetBlobPath_NotFound(t *testing.T) {
111	tmpDir := t.TempDir()
112	log := testLogger()
113
114	storage, err := NewBlobStorage(tmpDir, log)
115	require.NoError(t, err)
116
117	path, err := storage.GetBlobPath(testDigestMissing)
118
119	assert.Error(t, err)
120	assert.Empty(t, path)
121	assert.Contains(t, err.Error(), "blob not found")
122}
123
124func TestBlobStorage_PutBlob_SizeMismatch(t *testing.T) {
125	tmpDir := t.TempDir()
126	log := testLogger()
127
128	storage, err := NewBlobStorage(tmpDir, log)
129	require.NoError(t, err)
130
131	blobData := []byte("test blob content")
132
133	// Put blob with wrong size
134	err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), 999)
135
136	assert.Error(t, err)
137	assert.Contains(t, err.Error(), "size mismatch")
138}
139
140func TestBlobStorage_PutBlob_ZeroSize(t *testing.T) {
141	tmpDir := t.TempDir()
142	log := testLogger()
143
144	storage, err := NewBlobStorage(tmpDir, log)
145	require.NoError(t, err)
146
147	blobData := []byte("test blob content")
148
149	// Put blob with size=0 (should skip size check)
150	err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), 0)
151
152	assert.NoError(t, err)
153
154	// Verify blob was stored
155	assert.True(t, storage.BlobExists(testDigest2))
156}
157
158func TestBlobStorage_DeleteBlob(t *testing.T) {
159	tmpDir := t.TempDir()
160	log := testLogger()
161
162	storage, err := NewBlobStorage(tmpDir, log)
163	require.NoError(t, err)
164
165	blobData := []byte("test blob content")
166
167	// Put blob
168	err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), int64(len(blobData)))
169	require.NoError(t, err)
170
171	// Verify it exists
172	assert.True(t, storage.BlobExists(testDigest2))
173
174	// Delete blob
175	err = storage.DeleteBlob(testDigest2)
176	require.NoError(t, err)
177
178	// Verify it's gone
179	assert.False(t, storage.BlobExists(testDigest2))
180}
181
182func TestBlobStorage_DeleteBlob_NotFound(t *testing.T) {
183	tmpDir := t.TempDir()
184	log := testLogger()
185
186	storage, err := NewBlobStorage(tmpDir, log)
187	require.NoError(t, err)
188
189	err = storage.DeleteBlob(testDigestMissing)
190
191	assert.Error(t, err)
192	assert.Contains(t, err.Error(), "blob not found")
193}
194
195func TestBlobStorage_BlobExists(t *testing.T) {
196	tmpDir := t.TempDir()
197	log := testLogger()
198
199	storage, err := NewBlobStorage(tmpDir, log)
200	require.NoError(t, err)
201
202	// Should not exist initially
203	assert.False(t, storage.BlobExists(testDigest2))
204
205	// Put blob
206	blobData := []byte("test blob content")
207	err = storage.PutBlob(testDigest2, bytes.NewReader(blobData), int64(len(blobData)))
208	require.NoError(t, err)
209
210	// Should exist now
211	assert.True(t, storage.BlobExists(testDigest2))
212}
213
214func TestBlobStorage_StartBlobUpload(t *testing.T) {
215	tmpDir := t.TempDir()
216	log := testLogger()
217
218	storage, err := NewBlobStorage(tmpDir, log)
219	require.NoError(t, err)
220
221	uuid, err := storage.StartBlobUpload("myapp")
222
223	require.NoError(t, err)
224	assert.NotEmpty(t, uuid)
225	assert.Contains(t, uuid, "myapp")
226
227	// Verify upload file was created
228	uploadPath := filepath.Join(tmpDir, "uploads", uuid)
229	_, err = os.Stat(uploadPath)
230	assert.NoError(t, err)
231}
232
233func TestBlobStorage_AppendBlobChunk(t *testing.T) {
234	tmpDir := t.TempDir()
235	log := testLogger()
236
237	storage, err := NewBlobStorage(tmpDir, log)
238	require.NoError(t, err)
239
240	uuid, err := storage.StartBlobUpload("myapp")
241	require.NoError(t, err)
242
243	// Append first chunk
244	chunk1 := []byte("first chunk")
245	size1, err := storage.AppendBlobChunk("myapp", uuid, chunk1)
246	require.NoError(t, err)
247	assert.Equal(t, int64(len(chunk1)), size1)
248
249	// Append second chunk
250	chunk2 := []byte(" second chunk")
251	size2, err := storage.AppendBlobChunk("myapp", uuid, chunk2)
252	require.NoError(t, err)
253	assert.Equal(t, int64(len(chunk1)+len(chunk2)), size2)
254}
255
256func TestBlobStorage_AppendBlobChunk_NotFound(t *testing.T) {
257	tmpDir := t.TempDir()
258	log := testLogger()
259
260	storage, err := NewBlobStorage(tmpDir, log)
261	require.NoError(t, err)
262
263	_, err = storage.AppendBlobChunk("myapp", "1234567890-invalid", []byte("data"))
264
265	assert.Error(t, err)
266	assert.Contains(t, err.Error(), "upload not found")
267}
268
269func TestBlobStorage_GetBlobUpload(t *testing.T) {
270	tmpDir := t.TempDir()
271	log := testLogger()
272
273	storage, err := NewBlobStorage(tmpDir, log)
274	require.NoError(t, err)
275
276	uuid, err := storage.StartBlobUpload("myapp")
277	require.NoError(t, err)
278
279	writer, err := storage.GetBlobUpload(uuid)
280	require.NoError(t, err)
281	defer writer.Close()
282
283	// Write some data
284	_, err = writer.Write([]byte("test data"))
285	assert.NoError(t, err)
286}
287
288func TestBlobStorage_GetBlobUpload_NotFound(t *testing.T) {
289	tmpDir := t.TempDir()
290	log := testLogger()
291
292	storage, err := NewBlobStorage(tmpDir, log)
293	require.NoError(t, err)
294
295	writer, err := storage.GetBlobUpload("1234567890-invalid")
296
297	assert.Error(t, err)
298	assert.Nil(t, writer)
299	assert.Contains(t, err.Error(), "upload not found")
300}
301
302func TestBlobStorage_FinishBlobUpload(t *testing.T) {
303	tmpDir := t.TempDir()
304	log := testLogger()
305
306	storage, err := NewBlobStorage(tmpDir, log)
307	require.NoError(t, err)
308
309	// Start upload
310	uuid, err := storage.StartBlobUpload("myapp")
311	require.NoError(t, err)
312
313	// Append data - content that hashes to testDigest1
314	blobData := []byte("complete blob content")
315	_, err = storage.AppendBlobChunk("myapp", uuid, blobData)
316	require.NoError(t, err)
317
318	// Finish upload - use testDigest1 (validation will pass, but hash won't match)
319	// This tests the flow, actual digest verification happens in verifyUploadDigest
320	err = storage.FinishBlobUpload(uuid, testDigest1)
321	// This will fail because content doesn't match digest
322	assert.Error(t, err)
323	assert.Contains(t, err.Error(), "digest")
324}
325
326func TestBlobStorage_CancelBlobUpload(t *testing.T) {
327	tmpDir := t.TempDir()
328	log := testLogger()
329
330	storage, err := NewBlobStorage(tmpDir, log)
331	require.NoError(t, err)
332
333	// Start upload
334	uuid, err := storage.StartBlobUpload("myapp")
335	require.NoError(t, err)
336
337	// Verify upload file exists
338	uploadPath := filepath.Join(tmpDir, "uploads", uuid)
339	_, err = os.Stat(uploadPath)
340	require.NoError(t, err)
341
342	// Cancel upload
343	err = storage.CancelBlobUpload(uuid)
344	require.NoError(t, err)
345
346	// Verify upload file is gone
347	_, err = os.Stat(uploadPath)
348	assert.True(t, os.IsNotExist(err))
349}
350
351func TestBlobStorage_CancelBlobUpload_NotFound(t *testing.T) {
352	tmpDir := t.TempDir()
353	log := testLogger()
354
355	storage, err := NewBlobStorage(tmpDir, log)
356	require.NoError(t, err)
357
358	err = storage.CancelBlobUpload("1234567890-invalid")
359
360	assert.Error(t, err)
361	assert.Contains(t, err.Error(), "upload not found")
362}
363
364func TestBlobStorage_BlobPathStructure(t *testing.T) {
365	tmpDir := t.TempDir()
366	log := testLogger()
367
368	storage, err := NewBlobStorage(tmpDir, log)
369	require.NoError(t, err)
370
371	// Test that blobs are stored with proper directory structure
372	blobData := []byte("test blob content")
373
374	err = storage.PutBlob(testDigest1, bytes.NewReader(blobData), int64(len(blobData)))
375	require.NoError(t, err)
376
377	// Check the path structure: blobs/sha256/a3/a3ed95caeb02...
378	// First 2 chars of hash after sha256: are "a3"
379	expectedPath := filepath.Join(tmpDir, "blobs", "sha256", "a3", "a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
380	_, err = os.Stat(expectedPath)
381	assert.NoError(t, err)
382}
383
384func TestBlobStorage_DigestFormats(t *testing.T) {
385	tmpDir := t.TempDir()
386	log := testLogger()
387
388	storage, err := NewBlobStorage(tmpDir, log)
389	require.NoError(t, err)
390
391	tests := []struct {
392		name   string
393		digest string
394	}{
395		{"sha256 standard", testDigest1},
396		{"sha512", testDigestSHA512},
397	}
398
399	for _, tt := range tests {
400		t.Run(tt.name, func(t *testing.T) {
401			blobData := []byte("test content for " + tt.digest)
402
403			err := storage.PutBlob(tt.digest, bytes.NewReader(blobData), int64(len(blobData)))
404			require.NoError(t, err)
405
406			assert.True(t, storage.BlobExists(tt.digest))
407
408			reader, err := storage.GetBlob(tt.digest)
409			require.NoError(t, err)
410			defer reader.Close()
411
412			data, err := io.ReadAll(reader)
413			require.NoError(t, err)
414			assert.Equal(t, blobData, data)
415		})
416	}
417}
418
419func TestBlobStorage_CompleteUploadFlow(t *testing.T) {
420	tmpDir := t.TempDir()
421	log := testLogger()
422
423	storage, err := NewBlobStorage(tmpDir, log)
424	require.NoError(t, err)
425
426	// Simulate a complete upload flow
427	repoName := "myorg/myapp"
428
429	// 1. Start upload
430	uuid, err := storage.StartBlobUpload(repoName)
431	require.NoError(t, err)
432
433	// 2. Send multiple chunks
434	chunks := [][]byte{
435		[]byte("first chunk of data"),
436		[]byte(" - middle chunk"),
437		[]byte(" - final chunk"),
438	}
439
440	var totalSize int64
441	for _, chunk := range chunks {
442		size, err := storage.AppendBlobChunk(repoName, uuid, chunk)
443		require.NoError(t, err)
444		totalSize = size
445	}
446
447	expectedTotal := int64(len(chunks[0]) + len(chunks[1]) + len(chunks[2]))
448	assert.Equal(t, expectedTotal, totalSize)
449
450	// 3. Cancel upload (since we can't easily compute the real digest)
451	err = storage.CancelBlobUpload(uuid)
452	require.NoError(t, err)
453}

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