nip/tests/test_nexter_manifest.nim

291 lines
9.2 KiB
Nim

## Property-Based Tests for NEXTER Manifest - TDD Approach
##
## **Feature:** 01-nip-unified-storage-and-formats
## **Property 3:** Manifest Roundtrip
## **Validates:** Requirements 6.4
##
## **Property Statement:**
## For any NEXTER manifest, parsing and regenerating SHALL produce semantically equivalent KDL
##
## **TDD Strategy:**
## Thistten FIRST to expose gaps in the implementation.
## Test failures will drive the implementation of parseNEXTERManifest and generateNEXTERManifest.
## We use a MAXIMALLY POPULATED manifest to ensure ALL fields are tested.
import std/[unittest, times, options, strutils, tables]
import nip/nexter_manifest
import nip/manifest_parser
# ============================================================================
# Helper: Create Maximally Populated NEXTER Manifest
# ============================================================================
proc createFullNEXTERManifest*(): NEXTERManifest =
## Creates a maximally populated NEXTERManifest instance for roundtrip testing.
## This ensures we test ALL fields, not just the minimal required ones.
##
## **Philosophy:** Test the hardest case first. If this passes, simpler cases will too.
result = NEXTERManifest(
name: "dev-environment",
version: parseSemanticVersion("2.1.0-beta.3+build.456"),
buildDate: parse("2025-11-20T15:30:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'"),
metadata: ContainerInfo(
description: "Development environment container with full toolchain.",
license: "MIT",
homepage: some("https://nexus.os/containers/dev-env"),
author: some("DevOps Team"),
maintainer: some("devops@nexus.os"),
purpose: some("development"),
tags: @["development", "toolchain", "isolated"]
),
provenance: ProvenanceInfo(
source: "https://nexus.os/sources/dev-env-2.1.0.tar.gz",
sourceHash: "xxh3-container-source-hash-abc",
upstream: some("https://github.com/nexusos/dev-containers"),
buildTimestamp: parse("2025-11-20T15:30:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'"),
builder: some("ci-bot@nexus.os")
),
buildConfig: BuildConfiguration(
configureFlags: @["--enable-dev-tools", "--with-debugger"],
compilerFlags: @["-O2", "-g", "-march=x86-64"],
compilerVersion: "gcc-13.2.0",
targetArchitecture: "x86_64",
libc: "musl",
allocator: "jemalloc",
buildSystem: "custom"
),
base: BaseConfig(
baseImage: some("alpine"),
baseVersion: some("3.18"),
packages: @["bash", "git", "vim", "gcc", "make"]
),
environment: {
"PATH": "/usr/local/bin:/usr/bin:/bin",
"HOME": "/home/developer",
"LANG": "en_US.UTF-8",
"TERM": "xterm-256color"
}.toTable(),
casChunks: @[
ChunkReference(
hash: "xxh3-chunk1-base",
size: 52428800, # 50MB
chunkType: Base,
path: "base/alpine-3.18.tar"
),
ChunkReference(
hash: "xxh3-chunk2-tools",
size: 10485760, # 10MB
chunkType: Tools,
path: "tools/dev-tools.tar"
),
ChunkReference(
hash: "xxh3-chunk3-config",
size: 8192,
chunkType: Config,
path: "config/container.conf"
)
],
namespace: ContainerNamespace(
isolationType: "full",
capabilities: @["CAP_NET_ADMIN", "CAP_SYS_PTRACE"],
mounts: @[
MountSpec(
source: "/home/user/projects",
target: "/workspace",
mountType: "bind",
readOnly: false,
options: @["rw", "rbind"]
),
MountSpec(
source: "tmpfs",
target: "/tmp",
mountType: "tmpfs",
readOnly: false,
options: @["size=1G"]
)
],
devices: @[
DeviceSpec(
path: "/dev/null",
deviceType: "c",
major: 1,
minor: 3,
permissions: "rwm"
),
DeviceSpec(
path: "/dev/zero",
deviceType: "c",
major: 1,
minor: 5,
permissions: "rwm"
)
]
),
startup: StartupConfig(
command: @["/bin/bash", "-l"],
workingDir: "/workspace",
user: some("developer"),
entrypoint: some("/usr/local/bin/container-init.sh")
),
buildHash: "xxh3-container-build-hash",
signature: SignatureInfo(
algorithm: "ed25519",
keyId: "container-builder-2024",
signature: "base64-encoded-container-signature"
)
)
# ============================================================================
# Property-Based Tests
# ============================================================================
suite "NEXTER Manifest Roundtrip Property Tests (Task 8.1)":
test "Property 3: KDL Generation - Verify output structure":
## **Phase 1:** Test KDL generation in isolation
## This verifies generateNEXTERManifest works before testing roundtrip
let manifest = createFullNEXTERManifest()
let kdlString = generateNEXTERManifest(manifest)
echo "\n=== Generated KDL (Phase 1) ===\n"
echo kdlString
echo "=== End KDL ===\n"
# Verify KDL structure
check kdlString.contains("container \"dev-environment\"")
check kdlString.contains("version \"2.1.0-beta.3+build.456\"")
check kdlString.contains("metadata {")
check kdlString.contains("provenance {")
check kdlString.contains("build_config {")
check kdlString.contains("base {")
check kdlString.contains("environment {")
check kdlString.contains("cas_chunks {")
check kdlString.contains("namespace {")
check kdlString.contains("startup {")
check kdlString.contains("build_hash \"xxh3-container-build-hash\"")
check kdlString.contains("signature {")
test "Property 3: Full Roundtrip - ALL FIELDS":
## **Phase 2:** Full roundtrip test
## This is the MAIN test that will expose all gaps
##
## **Feature: 01-nip-unified-storage-and-formats, Property 3: Manifest Roundtrip**
## **Validates: Requirements 6.4**
let originalManifest = createFullNEXTERManifest()
# Step 1: Generate KDL
let kdlString = generateNEXTERManifest(originalManifest)
check kdlString.len > 0
# Step 2: Parse KDL back
let parsedManifest = parseNEXTERManifest(kdlString)
# Step 3: Verify ALL fields preserved
if parsedManifest.name != originalManifest.name:
echo "Name mismatch: ", parsedManifest.name, " != ", originalManifest.name
check parsedManifest.name == originalManifest.name
if parsedManifest.version != originalManifest.version:
echo "Version mismatch:"
echo " Parsed: ", parsedManifest.version
echo " Original: ", originalManifest.version
check parsedManifest.version == originalManifest.version
if parsedManifest.buildHash != originalManifest.buildHash:
echo "BuildHash mismatch: ", parsedManifest.buildHash, " != ", originalManifest.buildHash
check parsedManifest.buildHash == originalManifest.buildHash
# Step 4: Verify deterministic generation
let kdlString2 = generateNEXTERManifest(parsedManifest)
if kdlString != kdlString2:
echo "KDL strings don't match!"
echo "=== Original KDL ==="
echo kdlString
echo "=== Regenerated KDL ==="
echo kdlString2
check kdlString == kdlString2
suite "NEXTER Manifest Validation Tests":
test "Validate manifest with valid xxh3 hashes":
let validManifest = NEXTERManifest(
name: "valid-container",
version: parseSemanticVersion("1.0.0"),
buildDate: now(),
metadata: ContainerInfo(
description: "Valid container",
license: "MIT"
),
provenance: ProvenanceInfo(
source: "https://example.com/valid.tar.gz",
sourceHash: "xxh3-valid123",
buildTimestamp: now()
),
buildConfig: BuildConfiguration(
compilerVersion: "gcc-13.2.0",
targetArchitecture: "x86_64",
libc: "musl",
allocator: "default",
buildSystem: "custom"
),
base: BaseConfig(
baseImage: some("alpine"),
baseVersion: some("3.18"),
packages: @[]
),
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-build123",
signature: SignatureInfo(
algorithm: "ed25519",
keyId: "test-key",
signature: "test-signature"
)
)
let issues = validateNEXTERManifest(validManifest)
check issues.len == 0
test "Property 3: Determinism - Same input produces same output":
## **Feature: 01-nip-unified-storage-and-formats, Property 9: Manifest Hash Determinism**
## **Validates: Requirements 6.4, 7.5**
##
## This test verifies that generateNEXTERManifest is deterministic
let manifest = createFullNEXTERManifest()
# Generate KDL multiple times
let kdl1 = generateNEXTERManifest(manifest)
let kdl2 = generateNEXTERManifest(manifest)
let kdl3 = generateNEXTERManifest(manifest)
# All outputs should be identical
check kdl1 == kdl2
check kdl2 == kdl3
check kdl1 == kdl3