280 lines
8.6 KiB
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)
|