206 lines
7.1 KiB
Nim
206 lines
7.1 KiB
Nim
## Property-Based Tests for Package Metadata (metadata.json)
|
|
##
|
|
## **Feature:** 01-nip-unified-storage-and-formats
|
|
## **Property 10:** Provenance Preservation
|
|
## **Validates:** Requirements 7.1, 7.2, 7.3
|
|
##
|
|
## **Property Statement:**
|
|
## For any package, the metadata.json SHALL accurately reflect the complete build chain
|
|
##
|
|
## **TDD Strategy:**
|
|
## Test-driven approach to ensure complete provenance tracking across all formats.
|
|
|
|
import std/[unittest, times, options, tables, json]
|
|
import nip/package_metadata
|
|
import nip/manifest_parser
|
|
|
|
# ============================================================================
|
|
# Helper: Create Maximally Populated Metadata
|
|
# ============================================================================
|
|
|
|
proc createFullMetadata*(formatType: string): PackageMetadata =
|
|
## Create maximally populated metadata for testing
|
|
## Tests all fields including optional ones
|
|
|
|
var envVars = initTable[string, string]()
|
|
envVars["CC"] = "gcc"
|
|
envVars["CFLAGS"] = "-O2"
|
|
envVars["PATH"] = "/usr/bin:/bin"
|
|
|
|
var extensibleMeta = initTable[string, string]()
|
|
extensibleMeta["custom_field"] = "custom_value"
|
|
extensibleMeta["build_id"] = "12345"
|
|
|
|
result = PackageMetadata(
|
|
formatType: formatType,
|
|
formatVersion: "1.0",
|
|
name: "test-package",
|
|
version: "1.2.3-alpha.1+build.2",
|
|
description: "Test package for metadata validation",
|
|
license: "MIT",
|
|
tags: @["test", "metadata", "provenance"],
|
|
|
|
source: SourceProvenance(
|
|
origin: "https://github.com/test/package",
|
|
sourceHash: "xxh3-source-abc123",
|
|
upstream: some("https://upstream.org/package"),
|
|
upstreamVersion: some("1.2.3"),
|
|
fetchedAt: parse("2025-11-20T10:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'"),
|
|
fetchMethod: "git"
|
|
),
|
|
|
|
build: BuildProvenance(
|
|
buildTimestamp: parse("2025-11-20T11:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'"),
|
|
builder: "ci-bot@nexus.os",
|
|
buildHost: "build-server-01",
|
|
buildDuration: some(3600),
|
|
buildEnvironment: BuildEnvironment(
|
|
compilerVersion: "gcc-13.2.0",
|
|
compilerFlags: @["-O3", "-march=native", "-flto"],
|
|
configureFlags: @["--prefix=/usr", "--enable-shared"],
|
|
targetArchitecture: "x86_64",
|
|
libc: "musl",
|
|
allocator: "jemalloc",
|
|
buildSystem: "cmake",
|
|
environmentVars: envVars
|
|
)
|
|
),
|
|
|
|
installation: InstallationProvenance(
|
|
installedAt: parse("2025-11-20T12:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'"),
|
|
installedBy: "admin",
|
|
installPath: "/Programs/TestPackage/1.2.3",
|
|
installMethod: "nip install",
|
|
installHost: "workstation-01"
|
|
),
|
|
|
|
hashes: IntegrityHashes(
|
|
sourceHash: "xxh3-source-abc123",
|
|
buildHash: "xxh3-build-def456",
|
|
artifactHash: "xxh3-artifact-ghi789",
|
|
manifestHash: "xxh3-manifest-jkl012"
|
|
),
|
|
|
|
signatures: @[
|
|
SignatureRecord(
|
|
algorithm: "ed25519",
|
|
keyId: "builder-key-2024",
|
|
signature: "base64-signature-1",
|
|
signedBy: "builder@nexus.os",
|
|
signedAt: parse("2025-11-20T11:30:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
|
|
),
|
|
SignatureRecord(
|
|
algorithm: "ed25519",
|
|
keyId: "maintainer-key-2024",
|
|
signature: "base64-signature-2",
|
|
signedBy: "maintainer@nexus.os",
|
|
signedAt: parse("2025-11-20T11:45:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
|
|
)
|
|
],
|
|
|
|
metadata: extensibleMeta
|
|
)
|
|
|
|
# ============================================================================
|
|
# Property-Based Tests
|
|
# ============================================================================
|
|
|
|
suite "Package Metadata Property Tests (Task 9.1)":
|
|
|
|
test "Property 10: JSON Generation - Verify structure":
|
|
## **Phase 1:** Test JSON generation in isolation
|
|
|
|
let metadata = createFullMetadata("npk")
|
|
let jsonString = generateMetadataJson(metadata)
|
|
|
|
echo "\n=== Generated JSON (Phase 1) ===\n"
|
|
echo jsonString
|
|
echo "\n=== End JSON ===\n"
|
|
|
|
# Verify JSON structure
|
|
let json = parseJson(jsonString)
|
|
check json.hasKey("format_type")
|
|
check json.hasKey("source_provenance")
|
|
check json.hasKey("build_provenance")
|
|
check json.hasKey("installation_provenance")
|
|
check json.hasKey("integrity_hashes")
|
|
check json.hasKey("signatures")
|
|
|
|
# Verify required fields
|
|
check json["format_type"].getStr() == "npk"
|
|
check json["name"].getStr() == "test-package"
|
|
check json["source_provenance"]["source_hash"].getStr().startsWith("xxh3-")
|
|
check json["integrity_hashes"]["build_hash"].getStr().startsWith("xxh3-")
|
|
|
|
test "Property 10: Full Roundtrip - ALL FIELDS":
|
|
## **Phase 2:** Full roundtrip test
|
|
##
|
|
## **Feature: 01-nip-unified-storage-and-formats, Property 10: Provenance Preservation**
|
|
## **Validates: Requirements 7.1, 7.2, 7.3**
|
|
|
|
for formatType in ["npk", "nip", "nexter"]:
|
|
let originalMetadata = createFullMetadata(formatType)
|
|
|
|
# Step 1: Generate JSON
|
|
let jsonString = generateMetadataJson(originalMetadata)
|
|
check jsonString.len > 0
|
|
|
|
# Step 2: Parse JSON back
|
|
let parsedMetadata = parseMetadataJson(jsonString)
|
|
|
|
# Step 3: Verify ALL fields preserved
|
|
check parsedMetadata.formatType == originalMetadata.formatType
|
|
check parsedMetadata.name == originalMetadata.name
|
|
check parsedMetadata.version == originalMetadata.version
|
|
check parsedMetadata.hashes.buildHash == originalMetadata.hashes.buildHash
|
|
check parsedMetadata.hashes.sourceHash == originalMetadata.hashes.sourceHash
|
|
check parsedMetadata.build.builder == originalMetadata.build.builder
|
|
check parsedMetadata.source.origin == originalMetadata.source.origin
|
|
|
|
# Step 4: Verify deterministic generation
|
|
let jsonString2 = generateMetadataJson(parsedMetadata)
|
|
|
|
# Parse both to compare structure (JSON key order may vary)
|
|
let json1 = parseJson(jsonString)
|
|
let json2 = parseJson(jsonString2)
|
|
check json1 == json2
|
|
|
|
suite "Package Metadata Validation Tests":
|
|
|
|
test "Validate metadata with valid xxh3 hashes":
|
|
let validMetadata = createFullMetadata("npk")
|
|
let issues = validateMetadata(validMetadata)
|
|
check issues.len == 0
|
|
|
|
test "Reject metadata with invalid hash format":
|
|
var invalidMetadata = createFullMetadata("npk")
|
|
invalidMetadata.hashes.buildHash = "sha256-invalid"
|
|
|
|
let issues = validateMetadata(invalidMetadata)
|
|
check issues.len > 0
|
|
check issues[0].contains("xxh3-128")
|
|
|
|
test "Reject metadata with invalid signature algorithm":
|
|
var invalidMetadata = createFullMetadata("npk")
|
|
invalidMetadata.signatures[0].algorithm = "rsa"
|
|
|
|
let issues = validateMetadata(invalidMetadata)
|
|
check issues.len > 0
|
|
check issues[0].contains("ed25519")
|
|
|
|
test "Property 10: Determinism - Same input produces same JSON structure":
|
|
## Verify that generateMetadataJson is deterministic
|
|
## (JSON key order may vary, but structure should be identical)
|
|
|
|
let metadata = createFullMetadata("nip")
|
|
|
|
# Generate JSON multiple times
|
|
let json1 = parseJson(generateMetadataJson(metadata))
|
|
let json2 = parseJson(generateMetadataJson(metadata))
|
|
let json3 = parseJson(generateMetadataJson(metadata))
|
|
|
|
# All outputs should be structurally identical
|
|
check json1 == json2
|
|
check json2 == json3
|
|
check json1 == json3
|