nip/tests/test_nexter_archive.nim

318 lines
9.9 KiB
Nim

## NEXTER Archive Handler Tests
##
## Tests for the NEXTER archive handler that creates and parses .nexter containers.
## This verifies that containers can be packaged and extracted correctly.
import std/[unittest, os, tempfiles, options, strutils, times, tables]
import nip/nexter
import nip/nexter_manifest
import nip/manifest_parser
# Helper to create a fully initialized NEXTER manifest
proc createTestManifest(name: string, version: string, description: string = "Test container"): NEXTERManifest =
let buildDate = parse("2025-11-28T12:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
return NEXTERManifest(
name: name,
version: parseSemanticVersion(version),
buildDate: buildDate,
metadata: ContainerInfo(
description: description,
license: "MIT"
),
provenance: ProvenanceInfo(
source: "https://example.com/source.tar.gz",
sourceHash: "xxh3-source-hash",
buildTimestamp: buildDate,
builder: some("test-builder")
),
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"
)
)
suite "NEXTER Archive Handler Tests":
setup:
let tempDir = createTempDir("nexter_test_archive_", "")
let archivePath = tempDir / "test-container.nexter"
teardown:
removeDir(tempDir)
test "Create NEXTER archive with all components":
## Verify NEXTER archive can be created with manifest, environment, chunks, and signature
# Create manifest
var manifest = createTestManifest("test-container", "1.0.0")
# Create environment
let environment = "PATH=/usr/bin:/bin\nHOME=/home/user"
# Create chunks
var chunks: seq[ChunkData] = @[
ChunkData(
hash: "xxh3-chunk1",
data: "chunk1 data",
size: 11,
chunkType: Binary
),
ChunkData(
hash: "xxh3-chunk2",
data: "chunk2 data",
size: 11,
chunkType: Config
)
]
# Create signature
let signature = "ed25519-signature-placeholder"
# Create archive
createNEXTER(manifest, environment, chunks, signature, archivePath)
# Verify archive exists
check fileExists(archivePath)
check getFileSize(archivePath) > 0
test "Parse NEXTER archive and extract components":
## Verify NEXTER archive can be parsed and all components extracted
# Create and write archive first
var manifest = createTestManifest("parse-test", "2.0.0")
let environment = "TEST=value"
var chunks: seq[ChunkData] = @[
ChunkData(
hash: "xxh3-parse-chunk",
data: "parse test data",
size: 15,
chunkType: Binary
)
]
let signature = "ed25519-parse-sig"
createNEXTER(manifest, environment, chunks, signature, archivePath)
# Parse archive
let container = parseNEXTER(archivePath)
# Verify components
check container.manifest.name == "parse-test"
check container.manifest.version.major == 2
check container.environment.contains("TEST=value")
check container.signature == "ed25519-parse-sig"
check container.chunks.len == 1
test "Archive creation fails for non-existent output path":
## Verify error handling for invalid output paths
let invalidPath = "/nonexistent/path/container.nexter"
var manifest = createTestManifest("fail-test", "1.0.0")
# This should fail gracefully
expect OSError:
createNEXTER(manifest, "", @[], "", invalidPath)
test "Parse fails for non-existent archive":
## Verify error handling for missing archives
let nonExistentPath = tempDir / "nonexistent.nexter"
expect NEXTERArchiveError:
discard parseNEXTER(nonExistentPath)
test "Verify NEXTER archive integrity":
## Verify archive integrity checking works
# Create valid archive
var manifest = createTestManifest("verify-test", "1.0.0")
createNEXTER(manifest, "TEST=1", @[], "sig", archivePath)
# Verify archive
check verifyNEXTER(archivePath) == true
test "List chunks in archive":
## Verify chunk listing works
var manifest = createTestManifest("list-test", "1.0.0")
var chunks: seq[ChunkData] = @[
ChunkData(hash: "xxh3-chunk-a", data: "a", size: 1, chunkType: Binary),
ChunkData(hash: "xxh3-chunk-b", data: "b", size: 1, chunkType: Config),
ChunkData(hash: "xxh3-chunk-c", data: "c", size: 1, chunkType: Data)
]
createNEXTER(manifest, "", chunks, "", archivePath)
# List chunks
let chunkList = listChunksInArchive(archivePath)
check chunkList.len == 3
check "xxh3-chunk-a" in chunkList
check "xxh3-chunk-b" in chunkList
check "xxh3-chunk-c" in chunkList
test "Get archive size":
## Verify archive size calculation
var manifest = createTestManifest("size-test", "1.0.0")
createNEXTER(manifest, "ENV=test", @[], "sig", archivePath)
let size = getArchiveSize(archivePath)
check size > 0
test "Get container info from archive":
## Verify container info extraction
var manifest = createTestManifest("info-test", "3.2.1", "Info test container")
createNEXTER(manifest, "", @[], "", archivePath)
let info = getContainerInfo(archivePath)
check info.isSome
check info.get().name == "info-test"
check info.get().version.major == 3
check info.get().version.minor == 2
check info.get().version.patch == 1
check info.get().metadata.description == "Info test container"
test "Archive with multiple chunks":
## Verify archives with many chunks work correctly
var manifest = createTestManifest("multi-chunk", "1.0.0")
# Create 10 chunks
var chunks: seq[ChunkData] = @[]
for i in 0..<10:
chunks.add(ChunkData(
hash: "xxh3-chunk-" & $i,
data: "chunk data " & $i,
size: (11 + i).int64,
chunkType: Binary
))
createNEXTER(manifest, "", chunks, "", archivePath)
# Verify all chunks present
let chunkList = listChunksInArchive(archivePath)
check chunkList.len == 10
test "Archive roundtrip: create and parse":
## Verify create → parse roundtrip preserves data
var manifest = createTestManifest("roundtrip-test", "1.5.2", "Roundtrip test")
manifest.metadata.author = some("Roundtrip Author")
let environment = "VAR1=value1\nVAR2=value2\nVAR3=value3"
var chunks: seq[ChunkData] = @[
ChunkData(hash: "xxh3-rt-1", data: "roundtrip data 1", size: 16, chunkType: Binary),
ChunkData(hash: "xxh3-rt-2", data: "roundtrip data 2", size: 16, chunkType: Config)
]
let signature = "ed25519-roundtrip-signature"
# Create archive
createNEXTER(manifest, environment, chunks, signature, archivePath)
# Parse archive
let container = parseNEXTER(archivePath)
# Verify roundtrip
check container.manifest.name == "roundtrip-test"
check container.manifest.version.major == 1
check container.manifest.version.minor == 5
check container.manifest.version.patch == 2
check container.manifest.metadata.description == "Roundtrip test"
check container.manifest.metadata.author.get() == "Roundtrip Author"
check container.environment == environment
check container.signature == signature
check container.chunks.len == 2
## Property-Based Tests
suite "NEXTER Archive Property Tests":
test "Property: Archive creation is deterministic":
## Verify creating the same archive twice produces identical results
let tempDir = createTempDir("nexter_prop_", "")
defer: removeDir(tempDir)
let path1 = tempDir / "archive1.nexter"
let path2 = tempDir / "archive2.nexter"
var manifest = createTestManifest("deterministic", "1.0.0")
let environment = "FIXED=value"
var chunks: seq[ChunkData] = @[
ChunkData(hash: "xxh3-det-1", data: "fixed data", size: 10, chunkType: Binary)
]
let signature = "fixed-signature"
# Create two archives with identical data
createNEXTER(manifest, environment, chunks, signature, path1)
createNEXTER(manifest, environment, chunks, signature, path2)
# Verify both archives are identical
let size1 = getFileSize(path1)
let size2 = getFileSize(path2)
check size1 == size2
test "Property: Archive parsing preserves all data":
## Verify parsing doesn't lose or corrupt data
let tempDir = createTempDir("nexter_preserve_", "")
defer: removeDir(tempDir)
let archivePath = tempDir / "preserve.nexter"
var manifest = createTestManifest("preserve-test", "2.3.4")
let environment = "PRESERVE=yes\nDATA=intact"
var chunks: seq[ChunkData] = @[
ChunkData(hash: "xxh3-p-1", data: "preserved", size: 9, chunkType: Binary),
ChunkData(hash: "xxh3-p-2", data: "data", size: 4, chunkType: Config)
]
let signature = "preserved-sig"
# Create and parse
createNEXTER(manifest, environment, chunks, signature, archivePath)
let container = parseNEXTER(archivePath)
# Verify all data preserved
check container.manifest.name == manifest.name
check container.manifest.version == manifest.version
check container.environment == environment
check container.signature == signature
check container.chunks.len == chunks.len