291 lines
9.2 KiB
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
|