## Integration tests for the bootstrap system ## Tests end-to-end bootstrap installation flow import std/[os, osproc, strutils, times, options] import ../src/nimpak/build/recipe_manager import ../src/nimpak/build/download_manager import ../src/nimpak/build/installation_manager import ../src/nimpak/build/recipe_parser import ../src/nimpak/cas const TestDir = "/tmp/nip-bootstrap-test" 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 = manager != nil endTest("RecipeManager initialization", start, result) except Exception as e: endTest("RecipeManager initialization", start, false, e.msg) result = false proc testDownloadManagerInit(): bool = let start = startTest("DownloadManager initialization") try: let manager = newDownloadManager(TestDir / "cache") result = manager != nil and dirExists(manager.cacheDir) endTest("DownloadManager initialization", start, result) 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!") # Calculate checksum using the standalone function let checksumResult = calculateBlake2b(testFile) if checksumResult.isErr: endTest("Checksum verification", start, false, "Failed to calculate checksum") return false let checksum = checksumResult.value # Verify it's in the correct format (blake2b-512 multihash) if not checksum.startsWith("blake2b-"): endTest("Checksum verification", start, false, "Invalid checksum format: " & checksum) return false # Verify the checksum using the standalone function let verified = 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 = manager != nil and dirExists(manager.toolsDir) endTest("InstallationManager initialization", start, result) 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") let (success, message) = manager.extractArchive(archivePath, extractDir) # Verify files were extracted result = success and fileExists(extractDir / "test.txt") and fileExists(extractDir / "test2.txt") if result: endTest("Archive extraction", start, true) else: endTest("Archive extraction", start, false, message) 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 (success, output) = manager.executeScript(scriptPath, TestDir) result = success 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 recipeContent = readFile(recipePath) let recipeOpt = parseRecipe(recipeContent) if recipeOpt.isNone: endTest("Recipe validation", start, false, "Failed to parse recipe") return false let recipe = recipeOpt.get() 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 recipeContent = readFile(recipePath) let recipeOpt = parseRecipe(recipeContent) if recipeOpt.isNone: endTest("Platform selection", start, false, "Failed to parse recipe") return false let recipe = recipeOpt.get() # Test platform selection when defined(amd64) or defined(x86_64): let platform = recipe.selectPlatform(paX86_64, "linux") if platform.isSome: result = platform.get().arch == paX86_64 else: result = false 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 - parseRecipe returns None on error let invalidRecipe = TestDir / "invalid.kdl" writeFile(invalidRecipe, "this is not valid KDL {{{") let invalidContent = readFile(invalidRecipe) let invalidResult = parseRecipe(invalidContent) if invalidResult.isSome: allPassed = false # Should have returned None # Test 2: Missing file - should throw IOError try: discard readFile(TestDir / "nonexistent.kdl") allPassed = false # Should have thrown except IOError, OSError: discard # Expected # Test 3: Invalid checksum let testFile = TestDir / "test-checksum.txt" writeFile(testFile, "content") let verified = 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 testToolManagement(): bool = let start = startTest("Tool management") try: let manager = newInstallationManager(TestDir / "bootstrap") # Test tool path generation let toolPath = manager.getToolPath("test-tool") if not toolPath.contains("test-tool"): endTest("Tool management", start, false, "Tool path generation failed") return false # Test tool installation check let isInstalled = manager.isToolInstalled("test-tool") if isInstalled: endTest("Tool management", start, false, "Tool should not be installed yet") return false # Create a fake tool installation createDir(toolPath) # Check again let nowInstalled = manager.isToolInstalled("test-tool") if not nowInstalled: endTest("Tool management", start, false, "Tool should be detected as installed") return false # Test getting installed tools let tools = manager.getInstalledTools() if "test-tool" notin tools: endTest("Tool management", start, false, "Installed tool not in list") return false result = true endTest("Tool management", start, true) except Exception as e: endTest("Tool management", 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 testDownloadManagerInit() discard testChecksumVerification() discard testInstallationManagerInit() discard testArchiveExtraction() discard testScriptExecution() discard testRecipeValidation() discard testPlatformSelection() discard testErrorHandling() discard testToolManagement() cleanupTestEnvironment() printTestSummary() when isMainModule: main()