## 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)