nip/tests/test_nexter_removal.nim

280 lines
8.6 KiB
Nim

## NEXTER Container Removal Tests
##
## Tests for atomic removal of NEXTER containers including stopping,
## reference cleanup, and garbage collection marking.
import std/[unittest, os, tempfiles, options, strutils, times, tables]
import nip/nexter_removal
import nip/nexter_installer
import nip/nexter
import nip/nexter_manifest
import nip/manifest_parser
# Helper to create a test container
proc createTestContainer(name: string, version: string): NEXTERContainer =
let buildDate = parse("2025-11-28T12:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
return NEXTERContainer(
manifest: NEXTERManifest(
name: name,
version: parseSemanticVersion(version),
buildDate: buildDate,
metadata: ContainerInfo(
description: "Test container",
license: "MIT"
),
provenance: ProvenanceInfo(
source: "https://example.com/source.tar.gz",
sourceHash: "xxh3-source-hash",
buildTimestamp: buildDate
),
buildConfig: BuildConfiguration(
configureFlags: @[],
compilerFlags: @[],
compilerVersion: "gcc-13",
targetArchitecture: "x86_64",
libc: "musl",
allocator: "jemalloc",
buildSystem: "custom"
),
base: BaseConfig(
baseImage: some("alpine"),
baseVersion: some("3.18")
),
environment: initTable[string, string](),
casChunks: @[],
namespace: ContainerNamespace(
isolationType: "full",
capabilities: @[],
mounts: @[],
devices: @[]
),
startup: StartupConfig(
command: @["/bin/sh"],
workingDir: "/",
user: none(string),
entrypoint: none(string)
),
buildHash: "xxh3-build-hash",
signature: SignatureInfo(
algorithm: "ed25519",
keyId: "test-key",
signature: "test-sig"
)
),
environment: "PATH=/usr/bin:/bin",
chunks: @[
ChunkData(
hash: "xxh3-chunk1",
data: "chunk1 data",
size: 11,
chunkType: Binary
)
],
signature: "ed25519-signature",
archivePath: ""
)
suite "NEXTER Container Removal Tests":
setup:
let tempDir = createTempDir("nexter_removal_test_", "")
let storageRoot = tempDir / "storage"
createDir(storageRoot)
teardown:
removeDir(tempDir)
test "Remove NEXTER container successfully":
## Verify container can be removed
let container = createTestContainer("remove-test", "1.0.0")
let archivePath = storageRoot / "remove-test.nexter"
# Create and install container
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Verify it exists
check dirExists(storageRoot / "nexters" / "remove-test")
# Remove container
let result = removeNEXTER("remove-test", storageRoot)
check result.success
check result.containerName == "remove-test"
check result.chunksMarkedForGC >= 0
test "Removal fails for non-existent container":
## Verify error handling for missing container
let result = removeNEXTER("nonexistent", storageRoot)
check not result.success
check "not found" in result.error.toLowerAscii()
test "Verify removal - container removed":
## Verify container is actually removed
let container = createTestContainer("verify-remove", "1.0.0")
let archivePath = storageRoot / "verify-remove.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Remove container
discard removeNEXTER("verify-remove", storageRoot)
# Verify removal
check verifyRemoval("verify-remove", storageRoot)
test "Verify removal - container still exists":
## Verify detection of existing container
let container = createTestContainer("still-exists", "1.0.0")
let archivePath = storageRoot / "still-exists.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Don't remove, just verify
check not verifyRemoval("still-exists", storageRoot)
test "Remove multiple containers":
## Verify batch removal works
# Install multiple containers
for i in 1..3:
let container = createTestContainer("batch-" & $i, "1.0.0")
let archivePath = storageRoot / ("batch-" & $i & ".nexter")
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Remove all
let results = removeAllNEXTER(storageRoot)
check results.len == 3
for result in results:
check result.success
test "Cleanup orphaned references":
## Verify orphaned reference cleanup
let container = createTestContainer("orphan-test", "1.0.0")
let archivePath = storageRoot / "orphan-test.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Remove container but leave references (simulate orphaned state)
removeDir(storageRoot / "nexters" / "orphan-test")
# Cleanup orphaned references
let cleanedCount = cleanupOrphanedReferences(storageRoot)
check cleanedCount >= 0
test "Format removal result - success":
## Verify success result formatting
let result = RemovalResult(
success: true,
containerName: "test",
removedPath: "/path/to/test",
chunksMarkedForGC: 5,
error: ""
)
let formatted = $result
check "" in formatted
check "test" in formatted
check "5" in formatted
test "Format removal result - failure":
## Verify failure result formatting
let result = RemovalResult(
success: false,
containerName: "test",
removedPath: "/path/to/test",
chunksMarkedForGC: 0,
error: "Test error"
)
let formatted = $result
check "" in formatted
check "test" in formatted
check "Test error" in formatted
suite "NEXTER Removal Property Tests":
test "Property: Successful removal marks chunks for GC":
## Verify chunks are marked for garbage collection
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let storageRoot = tempDir / "storage"
createDir(storageRoot)
let container = createTestContainer("prop-gc", "1.0.0")
let archivePath = storageRoot / "prop-gc.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
let result = removeNEXTER("prop-gc", storageRoot)
if result.success:
check result.chunksMarkedForGC >= 0
test "Property: Removal is idempotent":
## Verify removing twice doesn't cause issues
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let storageRoot = tempDir / "storage"
createDir(storageRoot)
let container = createTestContainer("prop-idem", "1.0.0")
let archivePath = storageRoot / "prop-idem.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Remove twice
let result1 = removeNEXTER("prop-idem", storageRoot)
let result2 = removeNEXTER("prop-idem", storageRoot)
check result1.success
check not result2.success # Second removal should fail (already removed)
test "Property: Verification matches actual state":
## Verify removal verification is accurate
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let storageRoot = tempDir / "storage"
createDir(storageRoot)
let container = createTestContainer("prop-verify", "1.0.0")
let archivePath = storageRoot / "prop-verify.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# Before removal
check not verifyRemoval("prop-verify", storageRoot)
# After removal
discard removeNEXTER("prop-verify", storageRoot)
check verifyRemoval("prop-verify", storageRoot)