Skip to content

Commit 209a6cf

Browse files
leodidoona-agent
andcommitted
test: add comprehensive tests for provenance bundle operations
Add integration tests for provenance bundle upload/download functionality: TestS3Cache_ProvenanceUpload: - Successful upload with valid provenance - Skip upload when provenance file missing - Handle empty provenance files TestS3Cache_ProvenanceDownload: - Successful download with content verification - Backward compatibility (missing provenance) - Empty file detection and rejection TestS3Cache_ProvenanceRoundTrip: - End-to-end upload and download - Content integrity verification TestS3Cache_ProvenanceAtomicMove: - Atomic file operations - Temporary file cleanup - No leftover .tmp files All tests pass and verify: - Non-blocking behavior - Proper error handling - File integrity checks - Backward compatibility - Atomic operations Co-authored-by: Ona <no-reply@ona.com>
1 parent 3a42cb4 commit 209a6cf

File tree

1 file changed

+370
-0
lines changed

1 file changed

+370
-0
lines changed
Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
package remote
2+
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/gitpod-io/leeway/pkg/leeway/cache"
10+
"golang.org/x/time/rate"
11+
)
12+
13+
// TestS3Cache_ProvenanceUpload tests provenance bundle upload functionality
14+
func TestS3Cache_ProvenanceUpload(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
createProvenanceFile bool
18+
provenanceContent string
19+
expectUpload bool
20+
expectedLogContains string
21+
}{
22+
{
23+
name: "successful provenance upload",
24+
createProvenanceFile: true,
25+
provenanceContent: `{"predicate":{"buildType":"test"}}`,
26+
expectUpload: true,
27+
expectedLogContains: "Successfully uploaded provenance bundle",
28+
},
29+
{
30+
name: "missing provenance file (skip upload)",
31+
createProvenanceFile: false,
32+
expectUpload: false,
33+
expectedLogContains: "Provenance bundle not found locally",
34+
},
35+
{
36+
name: "empty provenance file",
37+
createProvenanceFile: true,
38+
provenanceContent: "",
39+
expectUpload: true,
40+
expectedLogContains: "Successfully uploaded provenance bundle",
41+
},
42+
}
43+
44+
for _, tt := range tests {
45+
t.Run(tt.name, func(t *testing.T) {
46+
// Create temporary directory for test
47+
tmpDir := t.TempDir()
48+
49+
// Create mock package
50+
pkg := &mockPackage{
51+
version: "v1.0.0",
52+
}
53+
54+
// Create artifact file
55+
artifactPath := filepath.Join(tmpDir, "v1.0.0.tar.gz")
56+
if err := os.WriteFile(artifactPath, []byte("test artifact"), 0644); err != nil {
57+
t.Fatalf("Failed to create artifact: %v", err)
58+
}
59+
60+
// Create provenance file if needed
61+
if tt.createProvenanceFile {
62+
provenancePath := artifactPath + ".provenance.jsonl"
63+
if err := os.WriteFile(provenancePath, []byte(tt.provenanceContent), 0644); err != nil {
64+
t.Fatalf("Failed to create provenance file: %v", err)
65+
}
66+
}
67+
68+
// Create mock S3 storage
69+
mockStorage := &mockS3StorageForProvenance{
70+
objects: make(map[string][]byte),
71+
}
72+
73+
// Create S3 cache
74+
s3Cache := &S3Cache{
75+
storage: mockStorage,
76+
rateLimiter: rate.NewLimiter(rate.Limit(100), 200),
77+
cfg: &cache.RemoteConfig{
78+
BucketName: "test-bucket",
79+
},
80+
}
81+
82+
// Test upload
83+
ctx := context.Background()
84+
s3Cache.uploadProvenanceBundle(ctx, pkg.FullName(), "v1.0.0.tar.gz", artifactPath)
85+
86+
// Verify upload
87+
provenanceKey := "v1.0.0.tar.gz.provenance.jsonl"
88+
if tt.expectUpload {
89+
if _, exists := mockStorage.objects[provenanceKey]; !exists {
90+
t.Errorf("Expected provenance to be uploaded but it wasn't")
91+
}
92+
if tt.provenanceContent != "" {
93+
if string(mockStorage.objects[provenanceKey]) != tt.provenanceContent {
94+
t.Errorf("Provenance content mismatch: got %q, want %q",
95+
string(mockStorage.objects[provenanceKey]), tt.provenanceContent)
96+
}
97+
}
98+
} else {
99+
if _, exists := mockStorage.objects[provenanceKey]; exists {
100+
t.Errorf("Expected provenance not to be uploaded but it was")
101+
}
102+
}
103+
})
104+
}
105+
}
106+
107+
// TestS3Cache_ProvenanceDownload tests provenance bundle download functionality
108+
func TestS3Cache_ProvenanceDownload(t *testing.T) {
109+
tests := []struct {
110+
name string
111+
provenanceExists bool
112+
provenanceContent string
113+
expectDownload bool
114+
expectFileCreated bool
115+
expectedLogContains string
116+
}{
117+
{
118+
name: "successful provenance download",
119+
provenanceExists: true,
120+
provenanceContent: `{"predicate":{"buildType":"test"}}`,
121+
expectDownload: true,
122+
expectFileCreated: true,
123+
},
124+
{
125+
name: "missing provenance (backward compatibility)",
126+
provenanceExists: false,
127+
expectDownload: false,
128+
expectFileCreated: false,
129+
},
130+
{
131+
name: "empty provenance file",
132+
provenanceExists: true,
133+
provenanceContent: "",
134+
expectDownload: false, // Should fail verification (empty file)
135+
expectFileCreated: false,
136+
},
137+
}
138+
139+
for _, tt := range tests {
140+
t.Run(tt.name, func(t *testing.T) {
141+
// Create temporary directory for test
142+
tmpDir := t.TempDir()
143+
144+
// Create mock package
145+
pkg := &mockPackage{
146+
147+
version: "v1.0.0",
148+
}
149+
150+
// Create artifact file
151+
artifactPath := filepath.Join(tmpDir, "v1.0.0.tar.gz")
152+
if err := os.WriteFile(artifactPath, []byte("test artifact"), 0644); err != nil {
153+
t.Fatalf("Failed to create artifact: %v", err)
154+
}
155+
156+
// Create mock S3 storage
157+
mockStorage := &mockS3StorageForProvenance{
158+
objects: make(map[string][]byte),
159+
}
160+
161+
// Add provenance to mock storage if it should exist
162+
if tt.provenanceExists {
163+
provenanceKey := "v1.0.0.tar.gz.provenance.jsonl"
164+
mockStorage.objects[provenanceKey] = []byte(tt.provenanceContent)
165+
}
166+
167+
// Create S3 cache
168+
s3Cache := &S3Cache{
169+
storage: mockStorage,
170+
rateLimiter: rate.NewLimiter(rate.Limit(100), 200),
171+
cfg: &cache.RemoteConfig{
172+
BucketName: "test-bucket",
173+
},
174+
}
175+
176+
// Test download
177+
ctx := context.Background()
178+
success := s3Cache.downloadProvenanceBundle(ctx, pkg.FullName(), "v1.0.0.tar.gz", artifactPath)
179+
180+
// Verify download result
181+
if success != tt.expectDownload {
182+
t.Errorf("Download success mismatch: got %v, want %v", success, tt.expectDownload)
183+
}
184+
185+
// Verify file creation
186+
provenancePath := artifactPath + ".provenance.jsonl"
187+
fileExists := fileExists(provenancePath)
188+
if fileExists != tt.expectFileCreated {
189+
t.Errorf("File creation mismatch: got %v, want %v", fileExists, tt.expectFileCreated)
190+
}
191+
192+
// Verify content if file should exist
193+
if tt.expectFileCreated && tt.provenanceContent != "" {
194+
content, err := os.ReadFile(provenancePath)
195+
if err != nil {
196+
t.Fatalf("Failed to read provenance file: %v", err)
197+
}
198+
if string(content) != tt.provenanceContent {
199+
t.Errorf("Provenance content mismatch: got %q, want %q",
200+
string(content), tt.provenanceContent)
201+
}
202+
}
203+
})
204+
}
205+
}
206+
207+
// TestS3Cache_ProvenanceRoundTrip tests upload and download together
208+
func TestS3Cache_ProvenanceRoundTrip(t *testing.T) {
209+
// Create temporary directories
210+
uploadDir := t.TempDir()
211+
downloadDir := t.TempDir()
212+
213+
// Create mock package
214+
pkg := &mockPackage{
215+
216+
version: "v1.0.0",
217+
}
218+
219+
// Create artifact and provenance in upload directory
220+
uploadArtifactPath := filepath.Join(uploadDir, "v1.0.0.tar.gz")
221+
if err := os.WriteFile(uploadArtifactPath, []byte("test artifact"), 0644); err != nil {
222+
t.Fatalf("Failed to create artifact: %v", err)
223+
}
224+
225+
provenanceContent := `{"predicate":{"buildType":"test","materials":[{"uri":"git+https://github.com/test/repo"}]}}`
226+
uploadProvenancePath := uploadArtifactPath + ".provenance.jsonl"
227+
if err := os.WriteFile(uploadProvenancePath, []byte(provenanceContent), 0644); err != nil {
228+
t.Fatalf("Failed to create provenance file: %v", err)
229+
}
230+
231+
// Create mock S3 storage (shared between upload and download)
232+
mockStorage := &mockS3StorageForProvenance{
233+
objects: make(map[string][]byte),
234+
}
235+
236+
// Create S3 cache
237+
s3Cache := &S3Cache{
238+
storage: mockStorage,
239+
rateLimiter: rate.NewLimiter(rate.Limit(100), 200),
240+
cfg: &cache.RemoteConfig{
241+
BucketName: "test-bucket",
242+
},
243+
}
244+
245+
// Upload
246+
ctx := context.Background()
247+
s3Cache.uploadProvenanceBundle(ctx, pkg.FullName(), "v1.0.0.tar.gz", uploadArtifactPath)
248+
249+
// Verify upload
250+
provenanceKey := "v1.0.0.tar.gz.provenance.jsonl"
251+
if _, exists := mockStorage.objects[provenanceKey]; !exists {
252+
t.Fatal("Provenance was not uploaded")
253+
}
254+
255+
// Download to different directory
256+
downloadArtifactPath := filepath.Join(downloadDir, "v1.0.0.tar.gz")
257+
if err := os.WriteFile(downloadArtifactPath, []byte("test artifact"), 0644); err != nil {
258+
t.Fatalf("Failed to create download artifact: %v", err)
259+
}
260+
261+
success := s3Cache.downloadProvenanceBundle(ctx, pkg.FullName(), "v1.0.0.tar.gz", downloadArtifactPath)
262+
if !success {
263+
t.Fatal("Provenance download failed")
264+
}
265+
266+
// Verify downloaded content matches uploaded content
267+
downloadProvenancePath := downloadArtifactPath + ".provenance.jsonl"
268+
downloadedContent, err := os.ReadFile(downloadProvenancePath)
269+
if err != nil {
270+
t.Fatalf("Failed to read downloaded provenance: %v", err)
271+
}
272+
273+
if string(downloadedContent) != provenanceContent {
274+
t.Errorf("Downloaded content mismatch:\ngot: %q\nwant: %q",
275+
string(downloadedContent), provenanceContent)
276+
}
277+
}
278+
279+
// TestS3Cache_ProvenanceAtomicMove tests atomic move behavior
280+
func TestS3Cache_ProvenanceAtomicMove(t *testing.T) {
281+
tmpDir := t.TempDir()
282+
283+
pkg := &mockPackage{
284+
285+
version: "v1.0.0",
286+
}
287+
288+
artifactPath := filepath.Join(tmpDir, "v1.0.0.tar.gz")
289+
if err := os.WriteFile(artifactPath, []byte("test artifact"), 0644); err != nil {
290+
t.Fatalf("Failed to create artifact: %v", err)
291+
}
292+
293+
// Create mock S3 storage with provenance
294+
provenanceContent := `{"predicate":{"buildType":"test"}}`
295+
mockStorage := &mockS3StorageForProvenance{
296+
objects: map[string][]byte{
297+
"v1.0.0.tar.gz.provenance.jsonl": []byte(provenanceContent),
298+
},
299+
}
300+
301+
s3Cache := &S3Cache{
302+
storage: mockStorage,
303+
rateLimiter: rate.NewLimiter(rate.Limit(100), 200),
304+
cfg: &cache.RemoteConfig{
305+
BucketName: "test-bucket",
306+
},
307+
}
308+
309+
// Download provenance
310+
ctx := context.Background()
311+
success := s3Cache.downloadProvenanceBundle(ctx, pkg.FullName(), "v1.0.0.tar.gz", artifactPath)
312+
if !success {
313+
t.Fatal("Provenance download failed")
314+
}
315+
316+
// Verify no .tmp file left behind
317+
tmpFiles, err := filepath.Glob(filepath.Join(tmpDir, "*.tmp"))
318+
if err != nil {
319+
t.Fatalf("Failed to check for tmp files: %v", err)
320+
}
321+
if len(tmpFiles) > 0 {
322+
t.Errorf("Found temporary files that should have been cleaned up: %v", tmpFiles)
323+
}
324+
325+
// Verify final file exists
326+
provenancePath := artifactPath + ".provenance.jsonl"
327+
if !fileExists(provenancePath) {
328+
t.Error("Final provenance file does not exist")
329+
}
330+
}
331+
332+
// mockS3StorageForProvenance is a mock implementation for provenance testing
333+
type mockS3StorageForProvenance struct {
334+
objects map[string][]byte
335+
}
336+
337+
func (m *mockS3StorageForProvenance) HasObject(ctx context.Context, key string) (bool, error) {
338+
_, exists := m.objects[key]
339+
return exists, nil
340+
}
341+
342+
func (m *mockS3StorageForProvenance) GetObject(ctx context.Context, key string, dest string) (int64, error) {
343+
data, exists := m.objects[key]
344+
if !exists {
345+
return 0, &mockNotFoundError{key: key}
346+
}
347+
348+
if err := os.WriteFile(dest, data, 0644); err != nil {
349+
return 0, err
350+
}
351+
352+
return int64(len(data)), nil
353+
}
354+
355+
func (m *mockS3StorageForProvenance) UploadObject(ctx context.Context, key string, src string) error {
356+
data, err := os.ReadFile(src)
357+
if err != nil {
358+
return err
359+
}
360+
m.objects[key] = data
361+
return nil
362+
}
363+
364+
func (m *mockS3StorageForProvenance) ListObjects(ctx context.Context, prefix string) ([]string, error) {
365+
var keys []string
366+
for key := range m.objects {
367+
keys = append(keys, key)
368+
}
369+
return keys, nil
370+
}

0 commit comments

Comments
 (0)