408 lines
12 KiB
Nim
408 lines
12 KiB
Nim
## Tests for NPK Installation Workflow
|
|
##
|
|
## **Feature:** 01-nip-unified-storage-and-formats
|
|
## **Task:** 13. Implement NPK installation workflow
|
|
## **Requirements:** 3.5, 11.1, 11.2
|
|
##
|
|
## **Test Strategy:**
|
|
## - Test atomic installation workflow
|
|
## - Test chunk extraction to CAS with deduplication
|
|
## - Test manifest creation
|
|
## - Test reference tracking
|
|
## - Test rollback on failure
|
|
## - Test installation verification
|
|
|
|
import std/[unittest, os, times, json, options, strutils]
|
|
import nip/[npk, npk_manifest, npk_installer, manifest_parser, cas]
|
|
|
|
# ============================================================================
|
|
# Test Fixtures
|
|
# ============================================================================
|
|
|
|
proc createTestPackage(name: string, version: string, testDir: string): string =
|
|
## Create a test NPK package and return its path
|
|
let manifest = NPKManifest(
|
|
name: name,
|
|
version: parseSemanticVersion(version),
|
|
buildDate: now(),
|
|
|
|
metadata: PackageInfo(
|
|
description: "Test package for installation",
|
|
license: "MIT"
|
|
),
|
|
|
|
provenance: ProvenanceInfo(
|
|
source: "https://example.com/test.tar.gz",
|
|
sourceHash: "xxh3-test123",
|
|
buildTimestamp: now()
|
|
),
|
|
|
|
buildConfig: BuildConfiguration(
|
|
compilerVersion: "gcc-13.2.0",
|
|
targetArchitecture: "x86_64",
|
|
libc: "musl",
|
|
allocator: "default",
|
|
buildSystem: "cmake"
|
|
),
|
|
|
|
casChunks: @[
|
|
ChunkReference(
|
|
hash: "xxh3-chunk1",
|
|
size: 1024,
|
|
chunkType: Binary,
|
|
path: "bin/test"
|
|
),
|
|
ChunkReference(
|
|
hash: "xxh3-chunk2",
|
|
size: 2048,
|
|
chunkType: Library,
|
|
path: "lib/libtest.so"
|
|
)
|
|
],
|
|
|
|
install: InstallPaths(
|
|
programsPath: "/Programs/" & name & "/" & version,
|
|
binPath: "/Programs/" & name & "/" & version & "/bin",
|
|
libPath: "/Programs/" & name & "/" & version & "/lib",
|
|
sharePath: "/Programs/" & name & "/" & version & "/share",
|
|
etcPath: "/Programs/" & name & "/" & version & "/etc"
|
|
),
|
|
|
|
system: SystemIntegration(),
|
|
|
|
buildHash: "xxh3-build123",
|
|
|
|
signature: SignatureInfo(
|
|
algorithm: "ed25519",
|
|
keyId: "test-key",
|
|
signature: "test-signature"
|
|
)
|
|
)
|
|
|
|
let chunks = @[
|
|
ChunkData(
|
|
hash: "xxh3-chunk1",
|
|
data: "test chunk data 1",
|
|
size: 17,
|
|
chunkType: Binary
|
|
),
|
|
ChunkData(
|
|
hash: "xxh3-chunk2",
|
|
data: "test chunk data 2 - larger content",
|
|
size: 35,
|
|
chunkType: Library
|
|
)
|
|
]
|
|
|
|
let metadata = %* {
|
|
"package": name,
|
|
"version": version,
|
|
"created": "2025-11-20T14:00:00Z"
|
|
}
|
|
|
|
let signature = "test-signature-data"
|
|
let pkgPath = testDir / (name & ".npk")
|
|
|
|
discard createNPK(manifest, chunks, metadata, signature, pkgPath)
|
|
return pkgPath
|
|
|
|
# ============================================================================
|
|
# Installation Tests
|
|
# ============================================================================
|
|
|
|
suite "NPK Installation Workflow (Task 13)":
|
|
|
|
setup:
|
|
let testDir = getTempDir() / "npk-install-test-" & $getTime().toUnix()
|
|
let storageRoot = testDir / "storage"
|
|
createDir(testDir)
|
|
createDir(storageRoot)
|
|
|
|
teardown:
|
|
if dirExists(testDir):
|
|
removeDir(testDir)
|
|
|
|
test "Install NPK package successfully":
|
|
## **Requirement 3.5:** Extract chunks to CAS and create manifest
|
|
## **Requirement 11.1:** Atomic installation
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
let result = installNPK(pkgPath, storageRoot)
|
|
|
|
# Verify installation succeeded
|
|
check result.success
|
|
check result.packageName == "test-pkg"
|
|
check result.version == "1.0.0"
|
|
check result.chunksInstalled == 2
|
|
check result.error == ""
|
|
|
|
# Verify manifest was created
|
|
let manifestPath = storageRoot / "npks" / "test-pkg.kdl"
|
|
check fileExists(manifestPath)
|
|
|
|
# Verify references were created
|
|
let refsPath = storageRoot / "cas" / "refs" / "npks" / "test-pkg.refs"
|
|
check fileExists(refsPath)
|
|
|
|
# Verify chunks were extracted to CAS
|
|
let casDir = storageRoot / "cas" / "chunks"
|
|
check dirExists(casDir)
|
|
|
|
test "Install creates correct directory structure":
|
|
## Verify all required directories are created
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
# Check directory structure
|
|
check dirExists(storageRoot / "npks")
|
|
check dirExists(storageRoot / "cas")
|
|
check dirExists(storageRoot / "cas" / "chunks")
|
|
check dirExists(storageRoot / "cas" / "refs")
|
|
check dirExists(storageRoot / "cas" / "refs" / "npks")
|
|
|
|
test "Install fails for already installed package":
|
|
## Verify duplicate installation is prevented
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
|
|
# First installation should succeed
|
|
let result1 = installNPK(pkgPath, storageRoot)
|
|
check result1.success
|
|
|
|
# Second installation should fail
|
|
let result2 = installNPK(pkgPath, storageRoot)
|
|
check not result2.success
|
|
check "already installed" in result2.error.toLowerAscii()
|
|
|
|
test "Install creates valid manifest file":
|
|
## Verify manifest can be parsed after installation
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
let manifestPath = storageRoot / "npks" / "test-pkg.kdl"
|
|
let manifestKdl = readFile(manifestPath)
|
|
|
|
# Parse manifest to verify it's valid
|
|
let manifest = parseNPKManifest(manifestKdl)
|
|
check manifest.name == "test-pkg"
|
|
check manifest.version.major == 1
|
|
check manifest.version.minor == 0
|
|
check manifest.version.patch == 0
|
|
|
|
test "Install creates valid references file":
|
|
## Verify references file contains correct information
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
let refsPath = storageRoot / "cas" / "refs" / "npks" / "test-pkg.refs"
|
|
let refsContent = readFile(refsPath)
|
|
|
|
check "test-pkg" in refsContent
|
|
check "1.0.0" in refsContent
|
|
check "chunks:" in refsContent
|
|
|
|
# ============================================================================
|
|
# Deduplication Tests
|
|
# ============================================================================
|
|
|
|
suite "NPK Installation Deduplication (Task 13)":
|
|
|
|
setup:
|
|
let testDir = getTempDir() / "npk-dedup-test-" & $getTime().toUnix()
|
|
let storageRoot = testDir / "storage"
|
|
createDir(testDir)
|
|
createDir(storageRoot)
|
|
|
|
teardown:
|
|
if dirExists(testDir):
|
|
removeDir(testDir)
|
|
|
|
test "Multiple packages share chunks via CAS":
|
|
## Verify CAS deduplication works across packages
|
|
|
|
# Create two packages with overlapping chunks
|
|
let pkg1Path = createTestPackage("pkg1", "1.0.0", testDir)
|
|
let pkg2Path = createTestPackage("pkg2", "1.0.0", testDir)
|
|
|
|
# Install both packages
|
|
let result1 = installNPK(pkg1Path, storageRoot)
|
|
let result2 = installNPK(pkg2Path, storageRoot)
|
|
|
|
check result1.success
|
|
check result2.success
|
|
|
|
# Both packages should be installed
|
|
check isInstalled("pkg1", storageRoot)
|
|
check isInstalled("pkg2", storageRoot)
|
|
|
|
# ============================================================================
|
|
# Query Function Tests
|
|
# ============================================================================
|
|
|
|
suite "NPK Installation Queries (Task 13)":
|
|
|
|
setup:
|
|
let testDir = getTempDir() / "npk-query-test-" & $getTime().toUnix()
|
|
let storageRoot = testDir / "storage"
|
|
createDir(testDir)
|
|
createDir(storageRoot)
|
|
|
|
teardown:
|
|
if dirExists(testDir):
|
|
removeDir(testDir)
|
|
|
|
test "Check if package is installed":
|
|
## Test isInstalled() function
|
|
|
|
check not isInstalled("test-pkg", storageRoot)
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
check isInstalled("test-pkg", storageRoot)
|
|
|
|
test "Get installed package version":
|
|
## Test getInstalledVersion() function
|
|
|
|
let version1 = getInstalledVersion("test-pkg", storageRoot)
|
|
check version1.isNone
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
let version2 = getInstalledVersion("test-pkg", storageRoot)
|
|
check version2.isSome
|
|
check version2.get() == "1.0.0"
|
|
|
|
test "List installed packages":
|
|
## Test listInstalledPackages() function
|
|
|
|
let list1 = listInstalledPackages(storageRoot)
|
|
check list1.len == 0
|
|
|
|
let pkg1Path = createTestPackage("pkg1", "1.0.0", testDir)
|
|
let pkg2Path = createTestPackage("pkg2", "2.0.0", testDir)
|
|
|
|
discard installNPK(pkg1Path, storageRoot)
|
|
discard installNPK(pkg2Path, storageRoot)
|
|
|
|
let list2 = listInstalledPackages(storageRoot)
|
|
check list2.len == 2
|
|
check "pkg1" in list2
|
|
check "pkg2" in list2
|
|
|
|
# ============================================================================
|
|
# Verification Tests
|
|
# ============================================================================
|
|
|
|
suite "NPK Installation Verification (Task 13)":
|
|
|
|
setup:
|
|
let testDir = getTempDir() / "npk-verify-test-" & $getTime().toUnix()
|
|
let storageRoot = testDir / "storage"
|
|
createDir(testDir)
|
|
createDir(storageRoot)
|
|
|
|
teardown:
|
|
if dirExists(testDir):
|
|
removeDir(testDir)
|
|
|
|
test "Verify valid installation":
|
|
## Test verifyInstallation() function
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
let isValid = verifyInstallation("test-pkg", storageRoot)
|
|
check isValid
|
|
|
|
test "Verify fails for non-existent package":
|
|
## Verify detection of missing package
|
|
|
|
let isValid = verifyInstallation("nonexistent", storageRoot)
|
|
check not isValid
|
|
|
|
test "Verify fails for corrupted installation":
|
|
## Verify detection of corrupted installation
|
|
|
|
let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
|
|
discard installNPK(pkgPath, storageRoot)
|
|
|
|
# Remove the references file to simulate corruption
|
|
let refsPath = storageRoot / "cas" / "refs" / "npks" / "test-pkg.refs"
|
|
removeFile(refsPath)
|
|
|
|
# Verification should fail
|
|
let isValid = verifyInstallation("test-pkg", storageRoot)
|
|
check not isValid
|
|
|
|
# ============================================================================
|
|
# Rollback Tests
|
|
# ============================================================================
|
|
|
|
suite "NPK Installation Rollback (Task 13)":
|
|
|
|
setup:
|
|
let testDir = getTempDir() / "npk-rollback-test-" & $getTime().toUnix()
|
|
let storageRoot = testDir / "storage"
|
|
createDir(testDir)
|
|
createDir(storageRoot)
|
|
|
|
teardown:
|
|
if dirExists(testDir):
|
|
removeDir(testDir)
|
|
|
|
test "Rollback on installation failure":
|
|
## **Requirement 11.2:** Rollback to previous state on failure
|
|
|
|
# Create invalid package path
|
|
let invalidPath = testDir / "nonexistent.npk"
|
|
|
|
# Installation should fail
|
|
let result = installNPK(invalidPath, storageRoot)
|
|
check not result.success
|
|
|
|
# Verify no artifacts left behind
|
|
let manifestPath = storageRoot / "npks" / "test-pkg.kdl"
|
|
check not fileExists(manifestPath)
|
|
|
|
# ============================================================================
|
|
# Utility Function Tests
|
|
# ============================================================================
|
|
|
|
suite "NPK Installation Utilities (Task 13)":
|
|
|
|
test "Format install result - success":
|
|
let result = InstallResult(
|
|
success: true,
|
|
packageName: "test-pkg",
|
|
version: "1.0.0",
|
|
installPath: "/path/to/manifest.kdl",
|
|
chunksInstalled: 5,
|
|
error: ""
|
|
)
|
|
|
|
let str = $result
|
|
check str.contains("Successfully installed")
|
|
check str.contains("test-pkg")
|
|
check str.contains("1.0.0")
|
|
check str.contains("5")
|
|
|
|
test "Format install result - failure":
|
|
let result = InstallResult(
|
|
success: false,
|
|
packageName: "test-pkg",
|
|
version: "",
|
|
installPath: "",
|
|
chunksInstalled: 0,
|
|
error: "Package not found"
|
|
)
|
|
|
|
let str = $result
|
|
check str.contains("Failed")
|
|
check str.contains("test-pkg")
|
|
check str.contains("Package not found")
|