Files
2026-03-15 18:31:53 +01:00

481 lines
13 KiB
Bash

#!/usr/bin/env bash
# Assertion library for nix-infra-machine tests
# Provides reusable assertion functions with consistent output formatting
#
# All assertions follow this pattern:
# assert_* "label" [args...]
# Returns 0 on pass, 1 on fail
# Prints colored pass/fail message
#
# Requires: Colors (GREEN, RED, YELLOW, NC) from shared.sh
# Requires: cmd, cmd_value, cmd_clean functions from shared.sh
# ============================================================================
# Service Assertions
# ============================================================================
# Check if a systemd service is active
# Usage: assert_service_active "$node" "service-name" ["optional label"]
assert_service_active() {
local node="$1"
local service="$2"
local label="${3:-$service}"
local status
status=$(cmd_value "$node" "systemctl is-active $service")
if [[ "$status" == "active" ]]; then
echo -e " ${GREEN}${NC} $label: active [pass]"
return 0
else
echo -e " ${RED}${NC} $label: $status [fail]"
return 1
fi
}
# Check if a oneshot service completed successfully
# Usage: assert_service_completed "$node" "service-name" ["optional label"]
assert_service_completed() {
local node="$1"
local service="$2"
local label="${3:-$service}"
local status
status=$(cmd_value "$node" "systemctl is-active $service 2>/dev/null || echo 'inactive'")
if [[ "$status" == "inactive" ]]; then
local exit_status
exit_status=$(cmd_value "$node" "systemctl show -p ExecMainStatus $service | cut -d= -f2")
if [[ "$exit_status" == "0" ]]; then
echo -e " ${GREEN}${NC} $label: completed successfully [pass]"
return 0
else
echo -e " ${RED}${NC} $label: failed (exit status: $exit_status) [fail]"
return 1
fi
else
echo -e " ${GREEN}${NC} $label: $status [pass]"
return 0
fi
}
# Check if service SubState is running
# Usage: assert_service_running "$node" "service-name" ["optional label"]
assert_service_running() {
local node="$1"
local service="$2"
local label="${3:-$service}"
local state
state=$(cmd_value "$node" "systemctl show -p SubState $service --value")
if [[ "$state" == "running" ]]; then
echo -e " ${GREEN}${NC} $label: running [pass]"
return 0
else
echo -e " ${RED}${NC} $label: $state [fail]"
return 1
fi
}
# ============================================================================
# Process Assertions
# ============================================================================
# Check if a process is running (using pgrep pattern)
# Usage: assert_process_running "$node" "pattern" "label"
assert_process_running() {
local node="$1"
local pattern="$2"
local label="$3"
local result
result=$(cmd_clean "$node" "pgrep -a $pattern || echo ''")
if [[ -n "$result" ]]; then
echo -e " ${GREEN}${NC} $label process running [pass]"
return 0
else
echo -e " ${RED}${NC} $label process not running [fail]"
return 1
fi
}
# Check process count meets minimum
# Usage: assert_process_count "$node" "pattern" "min_count" "label"
assert_process_count() {
local node="$1"
local pattern="$2"
local min_count="$3"
local label="$4"
local count
count=$(cmd_value "$node" "pgrep -c $pattern || echo 0")
if [[ "$count" -ge "$min_count" ]]; then
echo -e " ${GREEN}${NC} $count $label processes running [pass]"
return 0
else
echo -e " ${RED}${NC} Expected $min_count $label processes, found $count [fail]"
return 1
fi
}
# ============================================================================
# Port Assertions
# ============================================================================
# Check if a port is listening
# Usage: assert_port_listening "$node" "port" ["label"]
assert_port_listening() {
local node="$1"
local port="$2"
local label="${3:-Port $port}"
local result
result=$(cmd "$node" "ss -tlnp | grep :$port")
if [[ "$result" == *":$port"* ]]; then
echo -e " ${GREEN}${NC} $label is listening [pass]"
return 0
else
echo -e " ${RED}${NC} $label is not listening [fail]"
return 1
fi
}
# ============================================================================
# HTTP Assertions
# ============================================================================
# Check HTTP status code
# Usage: assert_http_status "$node" "url" "expected_codes" ["label"]
# expected_codes can be space-separated: "200 302 303"
assert_http_status() {
local node="$1"
local url="$2"
local expected="$3"
local label="${4:-HTTP $url}"
local code
code=$(cmd_value "$node" "curl -s -o /dev/null -w '%{http_code}' '$url' 2>/dev/null || echo '000'")
for exp in $expected; do
if [[ "$code" == "$exp" ]]; then
echo -e " ${GREEN}${NC} $label: HTTP $code [pass]"
return 0
fi
done
echo -e " ${RED}${NC} $label: HTTP $code (expected: $expected) [fail]"
return 1
}
# Check HTTP response contains string
# Usage: assert_http_contains "$node" "url" "expected_string" ["label"]
assert_http_contains() {
local node="$1"
local url="$2"
local expected="$3"
local label="${4:-HTTP $url}"
local response
response=$(cmd_clean "$node" "curl -s '$url' 2>/dev/null")
if [[ "$response" == *"$expected"* ]]; then
echo -e " ${GREEN}${NC} $label contains '$expected' [pass]"
return 0
else
echo -e " ${RED}${NC} $label missing '$expected' [fail]"
return 1
fi
}
# Check HTTP response contains multiple strings (all must match)
# Usage: assert_http_contains_all "$node" "url" "string1" "string2" ... ["--label" "label"]
assert_http_contains_all() {
local node="$1"
local url="$2"
shift 2
local label="HTTP $url"
local patterns=()
# Parse arguments - check for --label flag
while [[ $# -gt 0 ]]; do
if [[ "$1" == "--label" ]]; then
label="$2"
shift 2
else
patterns+=("$1")
shift
fi
done
local response
response=$(cmd_clean "$node" "curl -s '$url' 2>/dev/null")
for pattern in "${patterns[@]}"; do
if [[ "$response" != *"$pattern"* ]]; then
echo -e " ${RED}${NC} $label missing '$pattern' [fail]"
return 1
fi
done
echo -e " ${GREEN}${NC} $label [pass]"
return 0
}
# ============================================================================
# String Assertions
# ============================================================================
# Check if value equals expected
# Usage: assert_equals "actual" "expected" "label"
assert_equals() {
local actual="$1"
local expected="$2"
local label="$3"
if [[ "$actual" == "$expected" ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
echo -e " ${RED}${NC} $label: got '$actual', expected '$expected' [fail]"
return 1
fi
}
# Check if value contains substring
# Usage: assert_contains "haystack" "needle" "label"
assert_contains() {
local haystack="$1"
local needle="$2"
local label="$3"
if [[ "$haystack" == *"$needle"* ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
echo -e " ${RED}${NC} $label: missing '$needle' [fail]"
return 1
fi
}
# Check if value contains all substrings
# Usage: assert_contains_all "haystack" "label" "needle1" "needle2" ...
assert_contains_all() {
local haystack="$1"
local label="$2"
shift 2
for needle in "$@"; do
if [[ "$haystack" != *"$needle"* ]]; then
echo -e " ${RED}${NC} $label: missing '$needle' [fail]"
return 1
fi
done
echo -e " ${GREEN}${NC} $label [pass]"
return 0
}
# Check if value is not empty
# Usage: assert_not_empty "value" "label"
assert_not_empty() {
local value="$1"
local label="$2"
if [[ -n "$value" ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
echo -e " ${RED}${NC} $label: empty [fail]"
return 1
fi
}
# Check if value is empty or nil (for Redis-style responses)
# Usage: assert_empty_or_nil "value" "label"
assert_empty_or_nil() {
local value="$1"
local label="$2"
if [[ -z "$value" ]] || [[ "$value" == "nil" ]] || [[ "$value" == "(nil)" ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
echo -e " ${RED}${NC} $label: got '$value', expected empty/nil [fail]"
return 1
fi
}
# Check if value does NOT contain error indicators
# Usage: assert_no_error "value" "label"
assert_no_error() {
local value="$1"
local label="$2"
if [[ "$value" != *"ERROR"* ]] && [[ "$value" != *"error"* ]] && [[ "$value" != *"Error"* ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
echo -e " ${RED}${NC} $label: error found [fail]"
return 1
fi
}
# ============================================================================
# File System Assertions
# ============================================================================
# Check if file exists on remote node
# Usage: assert_file_exists "$node" "/path/to/file" ["label"]
assert_file_exists() {
local node="$1"
local path="$2"
local label="${3:-File $path}"
local result
result=$(cmd_value "$node" "test -f '$path' && echo 'exists' || echo 'missing'")
if [[ "$result" == "exists" ]]; then
echo -e " ${GREEN}${NC} $label exists [pass]"
return 0
else
echo -e " ${RED}${NC} $label not found [fail]"
return 1
fi
}
# Check if directory exists on remote node
# Usage: assert_dir_exists "$node" "/path/to/dir" ["label"]
assert_dir_exists() {
local node="$1"
local path="$2"
local label="${3:-Directory $path}"
local result
result=$(cmd_value "$node" "test -d '$path' && echo 'exists' || echo 'missing'")
if [[ "$result" == "exists" ]]; then
echo -e " ${GREEN}${NC} $label exists [pass]"
return 0
else
echo -e " ${RED}${NC} $label not found [fail]"
return 1
fi
}
# ============================================================================
# Container Assertions
# ============================================================================
# Check if a podman container is running
# Usage: assert_container_running "$node" "container-name" ["label"]
assert_container_running() {
local node="$1"
local container="$2"
local label="${3:-Container $container}"
local status
status=$(cmd_clean "$node" "podman ps --filter name=$container --format '{{.Names}} {{.Status}}'")
if [[ "$status" == *"$container"* ]]; then
echo -e " ${GREEN}${NC} $label running [pass]"
return 0
else
echo -e " ${RED}${NC} $label not running [fail]"
return 1
fi
}
# ============================================================================
# Comparison Assertions
# ============================================================================
# Check if numeric value is greater than or equal to expected
# Usage: assert_gte "actual" "expected" "label"
assert_gte() {
local actual="$1"
local expected="$2"
local label="$3"
if [[ "$actual" -ge "$expected" ]]; then
echo -e " ${GREEN}${NC} $label ($actual >= $expected) [pass]"
return 0
else
echo -e " ${RED}${NC} $label: $actual < $expected [fail]"
return 1
fi
}
# Check if numeric value is less than expected (for ordering/timestamps)
# Usage: assert_lt "actual" "expected" "label"
assert_lt() {
local actual="$1"
local expected="$2"
local label="$3"
if [[ -n "$actual" ]] && [[ -n "$expected" ]] && [[ "$actual" -lt "$expected" ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
echo -e " ${YELLOW}!${NC} $label: could not verify [warn]"
return 1
fi
}
# ============================================================================
# Soft Assertions (warnings instead of failures)
# ============================================================================
# Soft assertion that shows warning instead of failure
# Usage: assert_warn "condition_result" "label" "warn_message"
# condition_result should be "true" or "false"
assert_warn() {
local condition="$1"
local label="$2"
local warn_msg="${3:-}"
if [[ "$condition" == "true" ]]; then
echo -e " ${GREEN}${NC} $label [pass]"
return 0
else
if [[ -n "$warn_msg" ]]; then
echo -e " ${YELLOW}!${NC} $label ($warn_msg) [warn]"
else
echo -e " ${YELLOW}!${NC} $label [warn]"
fi
return 0 # Return success for soft assertions
fi
}
# ============================================================================
# Utility: Show logs on failure
# ============================================================================
# Show service logs (call after a failed assertion)
# Usage: show_service_logs "$node" "service-name" [lines]
show_service_logs() {
local node="$1"
local service="$2"
local lines="${3:-50}"
echo ""
echo "Service logs for $service:"
cmd "$node" "journalctl -n $lines -u $service"
}
# Show container logs (call after a failed assertion)
# Usage: show_container_logs "$node" "container-name" [lines]
show_container_logs() {
local node="$1"
local container="$2"
local lines="${3:-50}"
echo ""
echo "Container logs for $container:"
cmd "$node" "podman logs --tail $lines $container"
}