test(archive): add edge case tests for Orchestrator

Add tests for important orchestrator edge cases:
- Nested archives (zip-inside-zip): verifies multi-round extraction
  discovers archives within previously-extracted archives
- Context cancellation: graceful handling without hanging
- Multiple archives at same level: both extracted in one round
- Corrupt archive: skipped without error, doesn't block other work
- Empty archive: not counted as extracted
- Per-archive file limit: partial extraction handled correctly
- Relationship structure: contains relationships emit the archive's
  coordinates as the source side

Signed-off-by: Thomas Bechtold <thomasbechtold@jpberlin.de>
This commit is contained in:
Thomas Bechtold
2026-04-27 08:49:00 +02:00
parent f7ea072900
commit 5d6ad0b9e8
+222
View File
@@ -290,6 +290,228 @@ func TestOrchestrator_DoesNotReprocess(t *testing.T) {
assert.Equal(t, 1, orch.Resolver().ChildCount())
}
func TestOrchestrator_NestedArchives_ZipInsideZip(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()
ctx := context.Background()
// Create inner zip
innerZipPath := createTestZip(t, dir, map[string]string{
"deep.txt": "deeply nested content",
})
innerZipContent, err := os.ReadFile(innerZipPath)
require.NoError(t, err)
// Create outer zip containing the inner zip
outerDir := filepath.Join(dir, "outer")
require.NoError(t, os.MkdirAll(outerDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(outerDir, "inner.zip"), innerZipContent, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(outerDir, "readme.txt"), []byte("readme"), 0o644))
outerZipPath := createTestZip(t, outerDir, map[string]string{
"inner.zip": string(innerZipContent),
"readme.txt": "readme",
})
outerZipContent, err := os.ReadFile(outerZipPath)
require.NoError(t, err)
parent := newMockResolver(map[string]string{
"/outer.zip": string(outerZipContent),
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 3
cfg.IncludeIndexedArchives = true
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
// Round 1: extract outer.zip
count1 := orch.DiscoverAndExtract(ctx, 0)
assert.Equal(t, 1, count1, "should extract outer.zip")
assert.Equal(t, 1, orch.Resolver().ChildCount())
// Round 2: extract inner.zip found inside outer.zip
count2 := orch.DiscoverAndExtract(ctx, 1)
assert.Equal(t, 1, count2, "should extract inner.zip from within outer.zip")
assert.Equal(t, 2, orch.Resolver().ChildCount())
// Round 3: no more archives to extract
count3 := orch.DiscoverAndExtract(ctx, 2)
assert.Equal(t, 0, count3, "no more archives to extract")
}
func TestOrchestrator_ContextCancellation(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()
zipPath := createTestZip(t, dir, map[string]string{"inner.txt": "data"})
zipContent, err := os.ReadFile(zipPath)
require.NoError(t, err)
parent := newMockResolver(map[string]string{
"/test.zip": string(zipContent),
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 1
cfg.IncludeIndexedArchives = true
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancel immediately
// should handle gracefully without hanging
newCount := orch.DiscoverAndExtract(ctx, 0)
assert.True(t, newCount >= 0) // may be 0 or 1 depending on timing
}
func TestOrchestrator_MultipleArchivesSameLevel(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()
ctx := context.Background()
zip1Path := createTestZip(t, dir, map[string]string{"file1.txt": "from zip1"})
zip1Content, err := os.ReadFile(zip1Path)
require.NoError(t, err)
// Need a different dir to avoid overwriting test.zip
dir2 := t.TempDir()
zip2Path := createTestZip(t, dir2, map[string]string{"file2.txt": "from zip2"})
zip2Content, err := os.ReadFile(zip2Path)
require.NoError(t, err)
parent := newMockResolver(map[string]string{
"/archive1.zip": string(zip1Content),
"/archive2.zip": string(zip2Content),
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 1
cfg.IncludeIndexedArchives = true
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
newCount := orch.DiscoverAndExtract(ctx, 0)
assert.Equal(t, 2, newCount, "should extract both archives")
assert.Equal(t, 2, orch.Resolver().ChildCount())
}
func TestOrchestrator_CorruptArchiveSkipped(t *testing.T) {
tmpDir := t.TempDir()
ctx := context.Background()
// A file with .zip extension but invalid content
parent := newMockResolver(map[string]string{
"/corrupt.zip": "this is not a valid zip file",
"/normal.txt": "just text",
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 1
cfg.IncludeIndexedArchives = true
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
// Should not panic or error out; corrupt archives should be skipped
newCount := orch.DiscoverAndExtract(ctx, 0)
assert.Equal(t, 0, newCount, "corrupt archive should be skipped")
}
func TestOrchestrator_EmptyArchive(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()
ctx := context.Background()
// Create an empty zip
emptyZipPath := createTestZip(t, dir, map[string]string{})
emptyZipContent, err := os.ReadFile(emptyZipPath)
require.NoError(t, err)
parent := newMockResolver(map[string]string{
"/empty.zip": string(emptyZipContent),
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 1
cfg.IncludeIndexedArchives = true
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
newCount := orch.DiscoverAndExtract(ctx, 0)
assert.Equal(t, 0, newCount, "empty archive should not count as extracted")
}
func TestOrchestrator_PerArchiveFileLimitApplied(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()
ctx := context.Background()
// Create a zip with many files
files := map[string]string{}
for i := 0; i < 10; i++ {
files[filepath.Join("dir", string(rune('a'+i))+".txt")] = "content"
}
zipPath := createTestZip(t, dir, files)
zipContent, err := os.ReadFile(zipPath)
require.NoError(t, err)
parent := newMockResolver(map[string]string{
"/big.zip": string(zipContent),
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 1
cfg.IncludeIndexedArchives = true
cfg.MaxFileCount = 3 // only allow 3 files to be extracted
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
// Should partially extract but still register the archive
newCount := orch.DiscoverAndExtract(ctx, 0)
// Extraction will fail after 3 files, but partial extraction counts
assert.True(t, newCount >= 0)
}
func TestOrchestrator_RelationshipsHaveArchiveCoordinates(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()
ctx := context.Background()
zipPath := createTestZip(t, dir, map[string]string{"file.txt": "data"})
zipContent, err := os.ReadFile(zipPath)
require.NoError(t, err)
parent := newMockResolver(map[string]string{
"/archive.zip": string(zipContent),
})
cfg := cataloging.DefaultArchiveSearchConfig()
cfg.MaxDepth = 1
cfg.IncludeIndexedArchives = true
orch := NewOrchestrator(parent, cfg, tmpDir, testResolverFactory)
defer orch.Cleanup()
orch.DiscoverAndExtract(ctx, 0)
rels := orch.Relationships()
require.Len(t, rels, 1)
// verify the relationship structure
rel := rels[0]
assert.Equal(t, "contains", string(rel.Type))
// From should be the archive's coordinates
fromCoords, ok := rel.From.(file.Coordinates)
require.True(t, ok)
assert.Equal(t, "/archive.zip", fromCoords.RealPath)
}
func TestOrchestrator_ZipDisabledByConfig(t *testing.T) {
dir := t.TempDir()
tmpDir := t.TempDir()