## Integration tests for the bootstrap system ## Tests end-to-end bootstrap installation flow import std/[os, osproc, strutils, json, times] import ../src/nimpak/build/recipe_manager import ../src/nimpak/build/download_manager import ../src/nimpak/build/installation_manager import ../src/nimpak/build/recipe_parser const TestDir = "/tmp/nip-bootstrap-test" RecipeRepoUrl = "https://git.maiwald.work/Nexus/nip-recipes.git" type TestResult = object name: string passed: bool duration: float error: string var testResults: seq[TestResult] proc startTest(name: string): float = echo "\n๐Ÿงช Testing: ", name result = cpuTime() proc endTest(name: string, startTime: float, passed: bool, error: string = "") = let duration = cpuTime() - startTime testResults.add(TestResult( name: name, passed: passed, duration: duration, error: error )) if passed: echo "โœ… PASSED (", duration.formatFloat(ffDecimal, 3), "s)" else: echo "โŒ FAILED (", duration.formatFloat(ffDecimal, 3), "s)" if error.len > 0: echo " Error: ", error proc setupTestEnvironment() = echo "๐Ÿ”ง Setting up test environment..." # Clean up any previous test runs if dirExists(TestDir): removeDir(TestDir) createDir(TestDir) createDir(TestDir / "bootstrap") createDir(TestDir / "cache") createDir(TestDir / "recipes") echo "โœ… Test environment ready: ", TestDir proc cleanupTestEnvironment() = echo "\n๐Ÿงน Cleaning up test environment..." if dirExists(TestDir): removeDir(TestDir) echo "โœ… Cleanup complete" proc testRecipeManagerInit(): bool = let start = startTest("RecipeManager initialization") try: let manager = newRecipeManager(TestDir / "recipes") result = true endTest("RecipeManager initialization", start, true) except Exception as e: endTest("RecipeManager initialization", start, false, e.msg) result = false proc testRecipeLoading(): bool = let start = startTest("Recipe loading and parsing") try: let manager = newRecipeManager(TestDir / "recipes") # Try to load a recipe (will fail if repo not cloned, which is expected) # This tests the error handling try: discard manager.loadRecipe("nix") # If we get here, recipe loaded successfully result = true except RecipeError: # Expected if recipes not available echo " Note: Recipe not available (expected in test environment)" result = true endTest("Recipe loading and parsing", start, result) except Exception as e: endTest("Recipe loading and parsing", start, false, e.msg) result = false proc testDownloadManagerInit(): bool = let start = startTest("DownloadManager initialization") try: let manager = newDownloadManager(TestDir / "cache") result = true endTest("DownloadManager initialization", start, true) except Exception as e: endTest("DownloadManager initialization", start, false, e.msg) result = false proc testChecksumVerification(): bool = let start = startTest("Checksum verification") try: # Create a test file with known content let testFile = TestDir / "test.txt" writeFile(testFile, "Hello, NIP Bootstrap!") let manager = newDownloadManager(TestDir / "cache") # Calculate checksum let checksum = manager.calculateChecksum(testFile) # Verify it's in the correct format (blake2b-512 multihash) if checksum.startsWith("blake2b-"): result = true else: result = false endTest("Checksum verification", start, false, "Invalid checksum format: " & checksum) return # Verify the checksum let verified = manager.verifyChecksum(testFile, checksum) result = verified if verified: endTest("Checksum verification", start, true) else: endTest("Checksum verification", start, false, "Checksum verification failed") except Exception as e: endTest("Checksum verification", start, false, e.msg) result = false proc testInstallationManagerInit(): bool = let start = startTest("InstallationManager initialization") try: let manager = newInstallationManager(TestDir / "bootstrap") result = true endTest("InstallationManager initialization", start, true) except Exception as e: endTest("InstallationManager initialization", start, false, e.msg) result = false proc testArchiveExtraction(): bool = let start = startTest("Archive extraction") try: # Create a test tar.gz archive let testDir = TestDir / "archive-test" let extractDir = TestDir / "extracted" createDir(testDir) writeFile(testDir / "test.txt", "Test content") writeFile(testDir / "test2.txt", "More content") # Create tar.gz let archivePath = TestDir / "test.tar.gz" let cmd = "tar -czf " & archivePath & " -C " & testDir & " ." let (output, exitCode) = execCmdEx(cmd) if exitCode != 0: endTest("Archive extraction", start, false, "Failed to create test archive") return false # Test extraction let manager = newInstallationManager(TestDir / "bootstrap") manager.extractArchive(archivePath, extractDir) # Verify files were extracted result = fileExists(extractDir / "test.txt") and fileExists(extractDir / "test2.txt") if result: endTest("Archive extraction", start, true) else: endTest("Archive extraction", start, false, "Extracted files not found") except Exception as e: endTest("Archive extraction", start, false, e.msg) result = false proc testScriptExecution(): bool = let start = startTest("Script execution") try: # Create a test script let scriptPath = TestDir / "test-script.sh" writeFile(scriptPath, """#!/bin/bash echo "Test script executed" exit 0 """) # Make executable setFilePermissions(scriptPath, {fpUserExec, fpUserRead, fpUserWrite}) # Execute script let manager = newInstallationManager(TestDir / "bootstrap") let (output, exitCode) = manager.executeScript(scriptPath, TestDir) result = exitCode == 0 and "Test script executed" in output if result: endTest("Script execution", start, true) else: endTest("Script execution", start, false, "Script execution failed or wrong output") except Exception as e: endTest("Script execution", start, false, e.msg) result = false proc testRecipeValidation(): bool = let start = startTest("Recipe validation") try: # Create a test recipe let recipePath = TestDir / "test-recipe.kdl" writeFile(recipePath, """ recipe "test-tool" { version "1.0.0" description "Test tool for integration testing" platform "linux-x86_64" { url "https://example.com/test-tool.tar.gz" checksum "blake2b-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" install-script "install.sh" verify-script "verify.sh" } } """) # Parse and validate let recipe = parseRecipe(recipePath) result = recipe.name == "test-tool" and recipe.version == "1.0.0" and recipe.platforms.len > 0 if result: endTest("Recipe validation", start, true) else: endTest("Recipe validation", start, false, "Recipe validation failed") except Exception as e: endTest("Recipe validation", start, false, e.msg) result = false proc testPlatformSelection(): bool = let start = startTest("Platform selection") try: # Create a recipe with multiple platforms let recipePath = TestDir / "multi-platform.kdl" writeFile(recipePath, """ recipe "multi-tool" { version "1.0.0" description "Multi-platform test tool" platform "linux-x86_64" { url "https://example.com/linux-x86_64.tar.gz" checksum "blake2b-1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" } platform "linux-aarch64" { url "https://example.com/linux-aarch64.tar.gz" checksum "blake2b-2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" } } """) let recipe = parseRecipe(recipePath) # Test platform selection when defined(amd64) or defined(x86_64): let platform = recipe.selectPlatform() result = platform.isSome and "x86_64" in platform.get().name else: # On other architectures, just check that selection works result = true if result: endTest("Platform selection", start, true) else: endTest("Platform selection", start, false, "Platform selection failed") except Exception as e: endTest("Platform selection", start, false, e.msg) result = false proc testErrorHandling(): bool = let start = startTest("Error handling") try: var allPassed = true # Test 1: Invalid recipe file try: let invalidRecipe = TestDir / "invalid.kdl" writeFile(invalidRecipe, "this is not valid KDL {{{") discard parseRecipe(invalidRecipe) allPassed = false # Should have thrown except RecipeError: discard # Expected # Test 2: Missing file try: discard parseRecipe(TestDir / "nonexistent.kdl") allPassed = false # Should have thrown except IOError, OSError: discard # Expected # Test 3: Invalid checksum let manager = newDownloadManager(TestDir / "cache") let testFile = TestDir / "test-checksum.txt" writeFile(testFile, "content") let verified = manager.verifyChecksum(testFile, "blake2b-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") if verified: allPassed = false # Should have failed result = allPassed if result: endTest("Error handling", start, true) else: endTest("Error handling", start, false, "Error handling not working correctly") except Exception as e: endTest("Error handling", start, false, e.msg) result = false proc testCaching(): bool = let start = startTest("Download caching") try: let manager = newDownloadManager(TestDir / "cache") # Create a "downloaded" file in cache let cacheDir = TestDir / "cache" let testUrl = "https://example.com/test-file.tar.gz" let cachedFile = manager.getCachePath(testUrl) createDir(cachedFile.parentDir()) writeFile(cachedFile, "cached content") # Check if cache hit works let isCached = manager.isCached(testUrl) result = isCached if result: endTest("Download caching", start, true) else: endTest("Download caching", start, false, "Cache detection failed") except Exception as e: endTest("Download caching", start, false, e.msg) result = false proc printTestSummary() = echo "\n" & "=".repeat(60) echo "TEST SUMMARY" echo "=".repeat(60) var passed = 0 var failed = 0 var totalDuration = 0.0 for result in testResults: totalDuration += result.duration if result.passed: inc passed echo "โœ… ", result.name else: inc failed echo "โŒ ", result.name if result.error.len > 0: echo " ", result.error echo "" echo "Total: ", testResults.len, " tests" echo "Passed: ", passed, " (", (passed * 100 div testResults.len), "%)" echo "Failed: ", failed echo "Duration: ", totalDuration.formatFloat(ffDecimal, 3), "s" echo "=".repeat(60) if failed > 0: quit(1) proc main() = echo "NIP Bootstrap Integration Tests" echo "================================\n" setupTestEnvironment() # Run all tests discard testRecipeManagerInit() discard testRecipeLoading() discard testDownloadManagerInit() discard testChecksumVerification() discard testInstallationManagerInit() discard testArchiveExtraction() discard testScriptExecution() discard testRecipeValidation() discard testPlatformSelection() discard testErrorHandling() discard testCaching() cleanupTestEnvironment() printTestSummary() when isMainModule: main()