323 lines
10 KiB
Nim
323 lines
10 KiB
Nim
## NIP Launcher Tests
|
|
##
|
|
## Tests for the NIP launcher that runs applications in isolated namespaces.
|
|
## This verifies that applications can be launched with proper sandbox restrictions.
|
|
|
|
import std/[unittest, os, tempfiles, options, strutils, posix]
|
|
import nip/namespace
|
|
import nip/manifest_parser
|
|
import nip/nip_installer
|
|
import nip/cas
|
|
|
|
suite "NIP Launcher Tests":
|
|
|
|
setup:
|
|
let tempDir = createTempDir("nip_test_launcher_", "")
|
|
let casRoot = tempDir / "cas"
|
|
let installRoot = tempDir / "nips"
|
|
|
|
createDir(casRoot)
|
|
createDir(installRoot)
|
|
discard initCasManager(casRoot, casRoot)
|
|
|
|
teardown:
|
|
removeDir(tempDir)
|
|
|
|
test "Create Launcher from Manifest":
|
|
## Verify launcher can be created from a manifest
|
|
let manifest = PackageManifest(
|
|
name: "test-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher != nil
|
|
check launcher.manifest.name == "test-app"
|
|
check launcher.installDir == installRoot
|
|
check launcher.casRoot == casRoot
|
|
|
|
test "Launcher with Sandbox Configuration":
|
|
## Verify launcher respects sandbox configuration
|
|
var manifest = PackageManifest(
|
|
name: "sandboxed-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStrict,
|
|
namespaces: @["user", "mount", "pid", "ipc"]
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.level == SandboxStrict
|
|
check "user" in sb.namespaces
|
|
check "mount" in sb.namespaces
|
|
|
|
test "Launcher with Desktop Integration":
|
|
## Verify launcher works with desktop-integrated applications
|
|
var manifest = PackageManifest(
|
|
name: "desktop-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.desktop = some(DesktopIntegration(
|
|
displayName: "Desktop Application",
|
|
categories: @["Utility", "Development"],
|
|
icon: some("app-icon"),
|
|
terminal: false
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.desktop.isSome
|
|
let dt = launcher.manifest.desktop.get()
|
|
check dt.displayName == "Desktop Application"
|
|
check dt.icon.isSome
|
|
check dt.icon.get() == "app-icon"
|
|
|
|
test "Launcher with Seccomp Profile":
|
|
## Verify launcher respects seccomp configuration
|
|
var manifest = PackageManifest(
|
|
name: "seccomp-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStandard,
|
|
namespaces: @["user", "mount"],
|
|
seccompProfile: some("strict")
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.seccompProfile.isSome
|
|
check sb.seccompProfile.get() == "strict"
|
|
|
|
test "Launcher with Capabilities":
|
|
## Verify launcher respects capability restrictions
|
|
var manifest = PackageManifest(
|
|
name: "cap-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStandard,
|
|
namespaces: @["user", "mount"],
|
|
capabilities: @["CAP_NET_ADMIN", "CAP_SYS_ADMIN"]
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.capabilities.len == 2
|
|
check "CAP_NET_ADMIN" in sb.capabilities
|
|
check "CAP_SYS_ADMIN" in sb.capabilities
|
|
|
|
test "Launcher with Pledge (BSD)":
|
|
## Verify launcher respects pledge configuration (OpenBSD)
|
|
var manifest = PackageManifest(
|
|
name: "pledge-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStandard,
|
|
namespaces: @["user"],
|
|
pledge: some("stdio rpath wpath inet")
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.pledge.isSome
|
|
check sb.pledge.get() == "stdio rpath wpath inet"
|
|
|
|
test "Launcher Isolation Levels":
|
|
## Verify launcher supports different isolation levels
|
|
for level in [SandboxStrict, SandboxStandard]:
|
|
var manifest = PackageManifest(
|
|
name: "isolation-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: level,
|
|
namespaces: @["user", "mount"]
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
check launcher.manifest.sandbox.get().level == level
|
|
|
|
test "Launcher with CAS Root":
|
|
## Verify launcher correctly references CAS root for read-only mounts
|
|
let manifest = PackageManifest(
|
|
name: "cas-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.casRoot == casRoot
|
|
# The launcher should mount casRoot as read-only in the namespace
|
|
# This is verified by the namespace setup code
|
|
|
|
test "Launcher with Multiple Namespaces":
|
|
## Verify launcher can create multiple namespace types
|
|
var manifest = PackageManifest(
|
|
name: "multi-ns-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStrict,
|
|
namespaces: @["user", "mount", "pid", "net", "ipc"]
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.namespaces.len == 5
|
|
check "user" in sb.namespaces
|
|
check "mount" in sb.namespaces
|
|
check "pid" in sb.namespaces
|
|
check "net" in sb.namespaces
|
|
check "ipc" in sb.namespaces
|
|
|
|
test "Launcher Manifest Validation":
|
|
## Verify launcher validates manifest before launch
|
|
let manifest = PackageManifest(
|
|
name: "valid-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
# Launcher should have valid manifest
|
|
check launcher.manifest.name.len > 0
|
|
check launcher.manifest.version.major >= 0
|
|
check launcher.manifest.license.len > 0
|
|
|
|
test "Launcher with No Sandbox (Unrestricted)":
|
|
## Verify launcher can run without sandbox for trusted applications
|
|
let manifest = PackageManifest(
|
|
name: "unrestricted-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
# No sandbox configuration = unrestricted
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isNone
|
|
# Launcher should still work, just without isolation
|
|
|
|
test "Launcher with Minimal Sandbox":
|
|
## Verify launcher works with minimal sandbox configuration
|
|
var manifest = PackageManifest(
|
|
name: "minimal-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStandard,
|
|
namespaces: @[]
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, installRoot, casRoot)
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.level == SandboxStandard
|
|
check sb.namespaces.len == 0
|
|
|
|
## Property-Based Tests
|
|
|
|
suite "NIP Launcher Property Tests":
|
|
|
|
test "Property: Launcher preserves manifest integrity":
|
|
## Verify launcher doesn't modify manifest during creation
|
|
let manifest = PackageManifest(
|
|
name: "prop-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
let originalName = manifest.name
|
|
let originalVersion = manifest.version
|
|
let originalLicense = manifest.license
|
|
|
|
let launcher = newLauncher(manifest, "/tmp/install", "/tmp/cas")
|
|
|
|
check launcher.manifest.name == originalName
|
|
check launcher.manifest.version == originalVersion
|
|
check launcher.manifest.license == originalLicense
|
|
|
|
test "Property: Launcher paths are correctly set":
|
|
## Verify launcher stores paths correctly
|
|
let manifest = PackageManifest(
|
|
name: "path-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
let installDir = "/custom/install"
|
|
let casRoot = "/custom/cas"
|
|
|
|
let launcher = newLauncher(manifest, installDir, casRoot)
|
|
|
|
check launcher.installDir == installDir
|
|
check launcher.casRoot == casRoot
|
|
|
|
test "Property: Launcher supports all namespace combinations":
|
|
## Verify launcher can handle all namespace combinations
|
|
var manifest = PackageManifest(
|
|
name: "ns-app",
|
|
version: parseSemanticVersion("1.0.0"),
|
|
license: "MIT",
|
|
artifactHash: "hash123"
|
|
)
|
|
|
|
# Test various namespace combinations
|
|
let namespaceCombos = @[
|
|
@["user"],
|
|
@["user", "mount"],
|
|
@["user", "mount", "pid"],
|
|
@["user", "mount", "pid", "net"],
|
|
@["user", "mount", "pid", "net", "ipc"]
|
|
]
|
|
|
|
for namespaces in namespaceCombos:
|
|
manifest.sandbox = some(SandboxConfig(
|
|
level: SandboxStandard,
|
|
namespaces: namespaces
|
|
))
|
|
|
|
let launcher = newLauncher(manifest, "/tmp/install", "/tmp/cas")
|
|
check launcher.manifest.sandbox.isSome
|
|
let sb = launcher.manifest.sandbox.get()
|
|
check sb.namespaces == namespaces
|