237 lines
8.9 KiB
Nim
237 lines
8.9 KiB
Nim
## Property-Based Tests for Package Metadata
|
|
##
|
|
## **Feature: 01-nip-unified-storage-and-formats, Property 10: Provenance Preservation**
|
|
##
|
|
## This test verifies that metadata.json accurately reflects the complete build chain
|
|
## from source to installation, ensuring full audit trail and traceability.
|
|
##
|
|
## **Validates: Requirements 7.1, 7.2, 7.3**
|
|
|
|
import std/[unittest, times, json, options, strutils, random]
|
|
import ../src/nip/metadata
|
|
|
|
# Property-based test generator for random metadata
|
|
proc generateRandomMetadata(rng: var Rand): PackageMetadata =
|
|
## Generate random but valid metadata for property testing
|
|
|
|
let packageNames = @["nginx", "firefox", "postgresql", "redis", "docker"]
|
|
let maintainers = @["John Doe <john@example.com>", "Jane Smith <jane@example.com>", "ACME Corp"]
|
|
let compilers = @["gcc-13.2.0", "clang-16.0.0", "gcc-12.3.0"]
|
|
let architectures = @["x86_64", "aarch64", "riscv64"]
|
|
|
|
let source = SourceInfo(
|
|
origin: "https://github.com/example/" & packageNames[rng.rand(packageNames.high)],
|
|
maintainer: maintainers[rng.rand(maintainers.high)],
|
|
upstreamUrl: "https://example.com/" & packageNames[rng.rand(packageNames.high)],
|
|
sourceHash: "xxh3-" & $rng.rand(1000000..9999999)
|
|
)
|
|
|
|
let buildInfo = BuildInfo(
|
|
compilerVersion: compilers[rng.rand(compilers.high)],
|
|
compilerFlags: @["-O2", "-march=native"],
|
|
targetArchitecture: architectures[rng.rand(architectures.high)],
|
|
buildHash: "xxh3-" & $rng.rand(1000000..9999999),
|
|
buildTimestamp: now()
|
|
)
|
|
|
|
let provenance = ProvenanceChain(
|
|
sourceDownload: ProvenanceStep(
|
|
timestamp: now(),
|
|
action: "source_download",
|
|
hash: "xxh3-" & $rng.rand(1000000..9999999),
|
|
verifiedBy: "nip-0.2.0"
|
|
),
|
|
build: ProvenanceStep(
|
|
timestamp: now(),
|
|
action: "build",
|
|
hash: "xxh3-" & $rng.rand(1000000..9999999),
|
|
verifiedBy: "nip-0.2.0"
|
|
),
|
|
installation: ProvenanceStep(
|
|
timestamp: now(),
|
|
action: "installation",
|
|
hash: "xxh3-" & $rng.rand(1000000..9999999),
|
|
verifiedBy: "nip-0.2.0"
|
|
)
|
|
)
|
|
|
|
let dependencies = @[
|
|
DependencyInfo(
|
|
name: "openssl",
|
|
version: "3.0.0",
|
|
buildHash: "xxh3-" & $rng.rand(1000000..9999999)
|
|
),
|
|
DependencyInfo(
|
|
name: "zlib",
|
|
version: "1.2.13",
|
|
buildHash: "xxh3-" & $rng.rand(1000000..9999999)
|
|
)
|
|
]
|
|
|
|
result = generateMetadata(
|
|
packageName = packageNames[rng.rand(packageNames.high)],
|
|
version = "1.0.0",
|
|
formatType = FormatType.NPK,
|
|
source = source,
|
|
buildInfo = buildInfo,
|
|
provenance = some(provenance),
|
|
dependencies = dependencies
|
|
)
|
|
|
|
suite "Property-Based Tests: Provenance Preservation":
|
|
|
|
test "Property 10: Provenance Preservation - Roundtrip preserves all provenance data":
|
|
## **Feature: 01-nip-unified-storage-and-formats, Property 10: Provenance Preservation**
|
|
## **Validates: Requirements 7.1, 7.2, 7.3**
|
|
##
|
|
## For any package metadata with provenance chain, serializing to JSON and
|
|
## deserializing back should preserve all provenance information exactly.
|
|
|
|
var rng = initRand(42) # Fixed seed for reproducibility
|
|
|
|
# Run 100 iterations with random metadata
|
|
for i in 1..100:
|
|
let original = generateRandomMetadata(rng)
|
|
|
|
# Serialize to JSON
|
|
let jsonStr = toJson(original)
|
|
|
|
# Deserialize back
|
|
let restored = fromJson(jsonStr)
|
|
|
|
# Verify all fields preserved
|
|
check restored.packageName == original.packageName
|
|
check restored.version == original.version
|
|
check restored.formatType == original.formatType
|
|
|
|
# Verify source info preserved
|
|
check restored.source.origin == original.source.origin
|
|
check restored.source.maintainer == original.source.maintainer
|
|
check restored.source.upstreamUrl == original.source.upstreamUrl
|
|
check restored.source.sourceHash == original.source.sourceHash
|
|
|
|
# Verify build info preserved
|
|
check restored.buildInfo.compilerVersion == original.buildInfo.compilerVersion
|
|
check restored.buildInfo.compilerFlags == original.buildInfo.compilerFlags
|
|
check restored.buildInfo.targetArchitecture == original.buildInfo.targetArchitecture
|
|
check restored.buildInfo.buildHash == original.buildInfo.buildHash
|
|
|
|
# Verify provenance chain preserved (CRITICAL for audit trail)
|
|
check restored.provenance.isSome
|
|
if restored.provenance.isSome and original.provenance.isSome:
|
|
let origProv = original.provenance.get()
|
|
let restProv = restored.provenance.get()
|
|
|
|
# Source download step
|
|
check restProv.sourceDownload.action == origProv.sourceDownload.action
|
|
check restProv.sourceDownload.hash == origProv.sourceDownload.hash
|
|
check restProv.sourceDownload.verifiedBy == origProv.sourceDownload.verifiedBy
|
|
|
|
# Build step
|
|
check restProv.build.action == origProv.build.action
|
|
check restProv.build.hash == origProv.build.hash
|
|
check restProv.build.verifiedBy == origProv.build.verifiedBy
|
|
|
|
# Installation step
|
|
check restProv.installation.action == origProv.installation.action
|
|
check restProv.installation.hash == origProv.installation.hash
|
|
check restProv.installation.verifiedBy == origProv.installation.verifiedBy
|
|
|
|
# Verify dependencies preserved
|
|
check restored.dependencies.len == original.dependencies.len
|
|
for j in 0..<original.dependencies.len:
|
|
check restored.dependencies[j].name == original.dependencies[j].name
|
|
check restored.dependencies[j].version == original.dependencies[j].version
|
|
check restored.dependencies[j].buildHash == original.dependencies[j].buildHash
|
|
|
|
test "Property 10: Provenance chain maintains chronological order":
|
|
## Verify that provenance steps maintain chronological order
|
|
## (source download → build → installation)
|
|
|
|
var rng = initRand(123)
|
|
|
|
for i in 1..50:
|
|
let metadata = generateRandomMetadata(rng)
|
|
|
|
check metadata.provenance.isSome
|
|
if metadata.provenance.isSome:
|
|
let prov = metadata.provenance.get()
|
|
|
|
# Verify chronological order (timestamps should be in sequence)
|
|
# Note: In real implementation, these would have actual time differences
|
|
check prov.sourceDownload.action == "source_download"
|
|
check prov.build.action == "build"
|
|
check prov.installation.action == "installation"
|
|
|
|
# Verify all hashes use xxh3 format
|
|
check prov.sourceDownload.hash.startsWith("xxh3-")
|
|
check prov.build.hash.startsWith("xxh3-")
|
|
check prov.installation.hash.startsWith("xxh3-")
|
|
|
|
test "Property 10: Metadata validation ensures provenance completeness":
|
|
## Verify that metadata validation checks provenance chain completeness
|
|
|
|
var rng = initRand(456)
|
|
|
|
for i in 1..50:
|
|
let metadata = generateRandomMetadata(rng)
|
|
|
|
# Valid metadata should pass validation
|
|
check validateMetadata(metadata) == true
|
|
|
|
# Verify all required provenance fields present
|
|
if metadata.provenance.isSome:
|
|
let prov = metadata.provenance.get()
|
|
check prov.sourceDownload.action.len > 0
|
|
check prov.sourceDownload.hash.len > 0
|
|
check prov.sourceDownload.verifiedBy.len > 0
|
|
check prov.build.action.len > 0
|
|
check prov.build.hash.len > 0
|
|
check prov.build.verifiedBy.len > 0
|
|
check prov.installation.action.len > 0
|
|
check prov.installation.hash.len > 0
|
|
check prov.installation.verifiedBy.len > 0
|
|
|
|
test "Property 10: Build hash consistency across provenance chain":
|
|
## Verify that build hashes in provenance chain match metadata build hash
|
|
|
|
var rng = initRand(789)
|
|
|
|
for i in 1..50:
|
|
let metadata = generateRandomMetadata(rng)
|
|
|
|
# Build hash in buildInfo should match build step in provenance
|
|
if metadata.provenance.isSome:
|
|
let prov = metadata.provenance.get()
|
|
|
|
# All hashes should use xxh3 format
|
|
check metadata.buildInfo.buildHash.startsWith("xxh3-")
|
|
check prov.build.hash.startsWith("xxh3-")
|
|
|
|
# Source hash should match source download step
|
|
check metadata.source.sourceHash.startsWith("xxh3-")
|
|
check prov.sourceDownload.hash.startsWith("xxh3-")
|
|
|
|
test "Property 10: Dependency build hashes are preserved in audit trail":
|
|
## Verify that dependency build hashes are preserved for complete audit trail
|
|
|
|
var rng = initRand(101112)
|
|
|
|
for i in 1..50:
|
|
let metadata = generateRandomMetadata(rng)
|
|
|
|
# All dependency build hashes should use xxh3 format
|
|
for dep in metadata.dependencies:
|
|
check dep.buildHash.startsWith("xxh3-")
|
|
check dep.name.len > 0
|
|
check dep.version.len > 0
|
|
|
|
# Serialize and deserialize to verify preservation
|
|
let jsonStr = toJson(metadata)
|
|
let restored = fromJson(jsonStr)
|
|
|
|
# Verify all dependency hashes preserved
|
|
check restored.dependencies.len == metadata.dependencies.len
|
|
for j in 0..<metadata.dependencies.len:
|
|
check restored.dependencies[j].buildHash == metadata.dependencies[j].buildHash
|