358 lines
9.8 KiB
Nim
358 lines
9.8 KiB
Nim
## NEXTER Container Startup Tests
|
|
##
|
|
## Tests for container startup and lifecycle management.
|
|
## Verifies configuration validation, process setup, and execution.
|
|
|
|
import std/[unittest, os, tempfiles, options, strutils, times, tables]
|
|
import nip/container_startup
|
|
import nip/nexter_manifest
|
|
import nip/manifest_parser
|
|
|
|
# Helper to create a test manifest
|
|
proc createTestManifest(name: string, version: string): NEXTERManifest =
|
|
let buildDate = parse("2025-11-28T12:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
|
|
return NEXTERManifest(
|
|
name: name,
|
|
version: parseSemanticVersion(version),
|
|
buildDate: buildDate,
|
|
metadata: ContainerInfo(
|
|
description: "Test container",
|
|
license: "MIT"
|
|
),
|
|
provenance: ProvenanceInfo(
|
|
source: "https://example.com/source.tar.gz",
|
|
sourceHash: "xxh3-source-hash",
|
|
buildTimestamp: buildDate
|
|
),
|
|
buildConfig: BuildConfiguration(
|
|
configureFlags: @[],
|
|
compilerFlags: @[],
|
|
compilerVersion: "gcc-13",
|
|
targetArchitecture: "x86_64",
|
|
libc: "musl",
|
|
allocator: "jemalloc",
|
|
buildSystem: "custom"
|
|
),
|
|
base: BaseConfig(
|
|
baseImage: some("alpine"),
|
|
baseVersion: some("3.18")
|
|
),
|
|
environment: initTable[string, string](),
|
|
casChunks: @[],
|
|
namespace: ContainerNamespace(
|
|
isolationType: "full",
|
|
capabilities: @[],
|
|
mounts: @[],
|
|
devices: @[]
|
|
),
|
|
startup: StartupConfig(
|
|
command: @["/bin/sh"],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string)
|
|
),
|
|
buildHash: "xxh3-build-hash",
|
|
signature: SignatureInfo(
|
|
algorithm: "ed25519",
|
|
keyId: "test-key",
|
|
signature: "test-sig"
|
|
)
|
|
)
|
|
|
|
suite "Container Startup Configuration Tests":
|
|
|
|
test "Create startup config from manifest":
|
|
## Verify startup config can be created from manifest
|
|
|
|
let manifest = createTestManifest("startup-test", "1.0.0")
|
|
let config = createStartupConfig(manifest)
|
|
|
|
check config.command.len > 0
|
|
check config.workingDir == "/"
|
|
check config.user.isNone
|
|
|
|
test "Startup config with custom command":
|
|
## Verify custom command is preserved
|
|
|
|
let manifest = createTestManifest("cmd-test", "1.0.0")
|
|
var cmdManifest = manifest
|
|
cmdManifest.startup.command = @["/bin/bash", "-c", "echo hello"]
|
|
|
|
let config = createStartupConfig(cmdManifest)
|
|
|
|
check config.command.len == 3
|
|
check config.command[0] == "/bin/bash"
|
|
check config.command[1] == "-c"
|
|
check config.command[2] == "echo hello"
|
|
|
|
test "Startup config with working directory":
|
|
## Verify working directory is preserved
|
|
|
|
let manifest = createTestManifest("workdir-test", "1.0.0")
|
|
var workdirManifest = manifest
|
|
workdirManifest.startup.workingDir = "/app"
|
|
|
|
let config = createStartupConfig(workdirManifest)
|
|
|
|
check config.workingDir == "/app"
|
|
|
|
test "Startup config with user":
|
|
## Verify user is preserved
|
|
|
|
let manifest = createTestManifest("user-test", "1.0.0")
|
|
var userManifest = manifest
|
|
userManifest.startup.user = some("appuser")
|
|
|
|
let config = createStartupConfig(userManifest)
|
|
|
|
check config.user.isSome
|
|
check config.user.get() == "appuser"
|
|
|
|
test "Startup config with entrypoint":
|
|
## Verify entrypoint is preserved
|
|
|
|
let manifest = createTestManifest("entry-test", "1.0.0")
|
|
var entryManifest = manifest
|
|
entryManifest.startup.entrypoint = some("/app/entrypoint.sh")
|
|
|
|
let config = createStartupConfig(entryManifest)
|
|
|
|
check config.entrypoint.isSome
|
|
check config.entrypoint.get() == "/app/entrypoint.sh"
|
|
|
|
test "Startup config with environment":
|
|
## Verify environment variables are preserved
|
|
|
|
let manifest = createTestManifest("env-test", "1.0.0")
|
|
var envManifest = manifest
|
|
envManifest.environment["APP_ENV"] = "production"
|
|
envManifest.environment["DEBUG"] = "false"
|
|
|
|
let config = createStartupConfig(envManifest)
|
|
|
|
check config.environment["APP_ENV"] == "production"
|
|
check config.environment["DEBUG"] == "false"
|
|
|
|
suite "Container Startup Validation Tests":
|
|
|
|
test "Validate valid startup config":
|
|
## Verify valid config passes validation
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/sh"],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
check validateStartupConfig(config)
|
|
|
|
test "Validate fails for empty command":
|
|
## Verify validation fails for empty command
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @[],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
check not validateStartupConfig(config)
|
|
|
|
test "Validate fails for empty user":
|
|
## Verify validation fails for empty user
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/sh"],
|
|
workingDir: "/",
|
|
user: some(""),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
check not validateStartupConfig(config)
|
|
|
|
test "Validate fails for empty entrypoint":
|
|
## Verify validation fails for empty entrypoint
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/sh"],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: some(""),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
check not validateStartupConfig(config)
|
|
|
|
suite "Container Process Tests":
|
|
|
|
test "Start container with valid config":
|
|
## Verify container can be started
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/echo", "hello"],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
let process = startContainer(config)
|
|
|
|
# Process should be created (or fail gracefully)
|
|
check process.startTime <= now()
|
|
|
|
test "Start container with invalid config":
|
|
## Verify container startup fails gracefully for invalid config
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @[],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
let process = startContainer(config)
|
|
|
|
check process.status == Failed
|
|
check process.pid == -1
|
|
|
|
test "Get container logs":
|
|
## Verify logs can be retrieved
|
|
|
|
let process = ContainerProcess(
|
|
pid: 1234,
|
|
startTime: now(),
|
|
status: Running,
|
|
exitCode: none[int](),
|
|
output: "stdout output",
|
|
error: "stderr output"
|
|
)
|
|
|
|
let logs = getContainerLogs(process)
|
|
|
|
check "stdout output" in logs
|
|
check "stderr output" in logs
|
|
|
|
test "Get container status":
|
|
## Verify status can be retrieved
|
|
|
|
let process = ContainerProcess(
|
|
pid: 1234,
|
|
startTime: now(),
|
|
status: Running,
|
|
exitCode: none[int](),
|
|
output: "",
|
|
error: ""
|
|
)
|
|
|
|
let status = getContainerStatus(process)
|
|
|
|
check status == Running
|
|
|
|
test "Format startup config":
|
|
## Verify startup config formatting
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/sh"],
|
|
workingDir: "/app",
|
|
user: some("appuser"),
|
|
entrypoint: some("/app/start.sh"),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
let formatted = $config
|
|
|
|
check "Container Startup Config" in formatted
|
|
check "/bin/sh" in formatted
|
|
check "/app" in formatted
|
|
check "appuser" in formatted
|
|
|
|
test "Format container process":
|
|
## Verify container process formatting
|
|
|
|
let process = ContainerProcess(
|
|
pid: 1234,
|
|
startTime: now(),
|
|
status: Running,
|
|
exitCode: none[int](),
|
|
output: "",
|
|
error: ""
|
|
)
|
|
|
|
let formatted = $process
|
|
|
|
check "Container Process" in formatted
|
|
check "1234" in formatted
|
|
check "Running" in formatted
|
|
|
|
suite "Container Startup Property Tests":
|
|
|
|
test "Property: Config preserves all manifest startup settings":
|
|
## Verify all startup settings are preserved
|
|
|
|
let manifest = createTestManifest("prop-startup", "1.0.0")
|
|
var testManifest = manifest
|
|
testManifest.startup.command = @["/bin/bash", "-c", "test"]
|
|
testManifest.startup.workingDir = "/test"
|
|
testManifest.startup.user = some("testuser")
|
|
testManifest.startup.entrypoint = some("/test/entry.sh")
|
|
|
|
let config = createStartupConfig(testManifest)
|
|
|
|
check config.command == @["/bin/bash", "-c", "test"]
|
|
check config.workingDir == "/test"
|
|
check config.user.get() == "testuser"
|
|
check config.entrypoint.get() == "/test/entry.sh"
|
|
|
|
test "Property: Process has valid start time":
|
|
## Verify process start time is set correctly
|
|
|
|
let beforeStart = now()
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/echo"],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
let process = startContainer(config)
|
|
let afterStart = now()
|
|
|
|
check process.startTime >= beforeStart
|
|
check process.startTime <= afterStart
|
|
|
|
test "Property: Failed process has negative PID":
|
|
## Verify failed processes have negative PID
|
|
|
|
let config = ContainerStartupConfig(
|
|
command: @[],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
let process = startContainer(config)
|
|
|
|
if process.status == Failed:
|
|
check process.pid < 0
|
|
|
|
test "Property: Valid config always validates":
|
|
## Verify valid configs always pass validation
|
|
|
|
for i in 1..10:
|
|
let config = ContainerStartupConfig(
|
|
command: @["/bin/sh", "-c", "echo test-" & $i],
|
|
workingDir: "/",
|
|
user: none(string),
|
|
entrypoint: none(string),
|
|
environment: initTable[string, string]()
|
|
)
|
|
|
|
check validateStartupConfig(config)
|