nip/tests/test_nexter_installer.nim

338 lines
12 KiB
Nim

## NEXTER Container Installation Tests
##
## Tests for the NEXTER container installation workflow.
## Verifies atomic installation, rollback, and verification.
import std/[unittest, os, tempfiles, options, strutils, times, tables]
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 Installation Tests":
setup:
let tempDir = createTempDir("nexter_install_test_", "")
let storageRoot = tempDir / "storage"
createDir(storageRoot)
teardown:
removeDir(tempDir)
test "Install NEXTER container successfully":
## Verify container can be installed atomically
let container = createTestContainer("test-container", "1.0.0")
let archivePath = storageRoot / "test-container.nexter"
# Create archive
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
# Install container
let result = installNEXTER(archivePath, storageRoot)
check result.success
check result.containerName == "test-container"
check result.version == "1.0.0"
check result.chunksInstalled >= 0
check result.error == ""
test "Installation creates correct directory structure":
## Verify installation creates all required directories and files
let container = createTestContainer("struct-test", "2.0.0")
let archivePath = storageRoot / "struct-test.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
let result = installNEXTER(archivePath, storageRoot)
check result.success
check dirExists(storageRoot / "nexters" / "struct-test")
check fileExists(storageRoot / "nexters" / "struct-test" / "manifest.kdl")
check fileExists(storageRoot / "nexters" / "struct-test" / "environment.kdl")
check fileExists(storageRoot / "nexters" / "struct-test" / "signature.sig")
test "Installation fails for already installed container":
## Verify error when container already installed
let container = createTestContainer("dup-test", "1.0.0")
let archivePath = storageRoot / "dup-test.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
# First installation should succeed
let result1 = installNEXTER(archivePath, storageRoot)
check result1.success
# Second installation should fail
let result2 = installNEXTER(archivePath, storageRoot)
check not result2.success
check "already installed" in result2.error.toLowerAscii()
test "Check if container is installed":
## Verify isContainerInstalled() works correctly
let container = createTestContainer("check-test", "1.0.0")
let archivePath = storageRoot / "check-test.nexter"
# Before installation
check not isContainerInstalled("check-test", storageRoot)
# Install
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
# After installation
check isContainerInstalled("check-test", storageRoot)
test "Get installed container version":
## Verify getInstalledContainerVersion() returns correct version
let container = createTestContainer("version-test", "3.2.1")
let archivePath = storageRoot / "version-test.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
let version = getInstalledContainerVersion("version-test", storageRoot)
check version.isSome
check version.get() == "3.2.1"
test "List installed containers":
## Verify listInstalledContainers() returns all installed containers
# Install multiple containers
for i in 1..3:
let container = createTestContainer("list-test-" & $i, "1.0.0")
let archivePath = storageRoot / ("list-test-" & $i & ".nexter")
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
let containers = listInstalledContainers(storageRoot)
check containers.len == 3
check "list-test-1" in containers
check "list-test-2" in containers
check "list-test-3" in containers
test "Verify valid container installation":
## Verify verifyContainerInstallation() detects valid installations
let container = createTestContainer("verify-test", "1.0.0")
let archivePath = storageRoot / "verify-test.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
discard installNEXTER(archivePath, storageRoot)
check verifyContainerInstallation("verify-test", storageRoot)
test "Verify fails for non-existent container":
## Verify verifyContainerInstallation() fails for missing containers
check not verifyContainerInstallation("nonexistent", storageRoot)
test "Format installation result - success":
## Verify formatting of successful installation result
let result = ContainerInstallResult(
success: true,
containerName: "test",
version: "1.0.0",
installPath: "/path/to/test",
chunksInstalled: 5,
error: ""
)
let formatted = $result
check "" in formatted
check "test" in formatted
check "1.0.0" in formatted
test "Format installation result - failure":
## Verify formatting of failed installation result
let result = ContainerInstallResult(
success: false,
containerName: "",
version: "",
installPath: "",
chunksInstalled: 0,
error: "Test error"
)
let formatted = $result
check "" in formatted
check "Test error" in formatted
suite "NEXTER Container Installation Property Tests":
test "Property: Successful installation has ALL artifacts":
## Verify successful installation creates all required artifacts
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let container = createTestContainer("prop-test-1", "1.0.0")
let archivePath = tempDir / "prop-test-1.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
let result = installNEXTER(archivePath, tempDir / "storage")
if result.success:
check dirExists(tempDir / "storage" / "nexters" / "prop-test-1")
check fileExists(tempDir / "storage" / "nexters" / "prop-test-1" / "manifest.kdl")
check fileExists(tempDir / "storage" / "nexters" / "prop-test-1" / "environment.kdl")
check fileExists(tempDir / "storage" / "nexters" / "prop-test-1" / "signature.sig")
test "Property: Failed installation has NO artifacts (complete rollback)":
## Verify failed installation leaves no partial state
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
# Try to install to non-existent parent directory
let result = installNEXTER("/nonexistent/path/container.nexter", tempDir / "storage")
check not result.success
# Verify no partial state was created
check not dirExists(tempDir / "storage" / "nexters")
test "Property: Multiple containers share CAS chunks":
## Verify CAS deduplication works across multiple containers
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let storageRoot = tempDir / "storage"
createDir(storageRoot)
# Create two containers with same chunk
let sharedChunk = ChunkData(
hash: "xxh3-shared",
data: "shared chunk data",
size: 16,
chunkType: Binary
)
var container1 = createTestContainer("shared-1", "1.0.0")
container1.chunks = @[sharedChunk]
var container2 = createTestContainer("shared-2", "1.0.0")
container2.chunks = @[sharedChunk]
let archive1 = storageRoot / "shared-1.nexter"
let archive2 = storageRoot / "shared-2.nexter"
createNEXTER(container1.manifest, container1.environment, container1.chunks,
container1.signature, archive1)
createNEXTER(container2.manifest, container2.environment, container2.chunks,
container2.signature, archive2)
let result1 = installNEXTER(archive1, storageRoot)
let result2 = installNEXTER(archive2, storageRoot)
check result1.success
check result2.success
# Verify both containers reference the same chunk
let refsPath1 = storageRoot / "cas" / "refs" / "nexters" / "shared-1.refs"
let refsPath2 = storageRoot / "cas" / "refs" / "nexters" / "shared-2.refs"
if fileExists(refsPath1) and fileExists(refsPath2):
let refs1 = readFile(refsPath1).split('\n')
let refs2 = readFile(refsPath2).split('\n')
check refs1[0] == refs2[0] # Same chunk hash
test "Property: Installation preserves manifest integrity":
## Verify manifest is preserved exactly through installation
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let container = createTestContainer("integrity-test", "2.5.3")
let archivePath = tempDir / "integrity-test.nexter"
createNEXTER(container.manifest, container.environment, container.chunks,
container.signature, archivePath)
let result = installNEXTER(archivePath, tempDir / "storage")
if result.success:
let manifestPath = tempDir / "storage" / "nexters" / "integrity-test" / "manifest.kdl"
check fileExists(manifestPath)
let manifestContent = readFile(manifestPath)
check "integrity-test" in manifestContent
check "2.5.3" in manifestContent