wgctl/commands/test/integration.sh
Nuno Duque Nunes 3378ec3e5e feat: logs query flags, json_helper module split, handshake logging
- wgctl logs --since: relative (2h/7d) and EU/ISO date formats
- wgctl logs --service: filter by service name, IP, or IP:port
- wgctl logs --event: filter wg events by type
- wgctl logs: no header when no logs found
- core/lib/util.py: shared utilities, parse_since, reverse_lookup
- core/lib/events.py: fw_events, wg_events with query params
- core/lib/peers.py: peer_data, peer_transfer
- core/lib/activity.py: activity_aggregate
- wgctl-monitor.py: handshake session poller thread with cache
2026-05-25 00:21:16 +00:00

252 lines
No EOL
8.9 KiB
Bash

#!/usr/bin/env bash
# test/integration.sh — integration test sections
# Tests run against the live wgctl binary.
# Sourced by test.command.sh — do not execute directly.
WGCTL_BINARY="$(command -v wgctl)"
# ============================================
# Helpers
# ============================================
function cmd::test::_strip_ansi() {
sed 's/\x1b\[[0-9;]*m//g'
}
function cmd::test::run_cmd() {
local desc="$1" expected="${2:-}"
shift 2
local tmp exit_code
tmp=$(mktemp)
set +e
timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
exit_code=$?
set -e
# Reset terminal color in case command output left ANSI state dirty
printf "\033[0m" >&2
if [[ $exit_code -eq 124 ]]; then
test::warn "${desc} (timed out after 30s)"
rm -f "$tmp"
return 1
fi
local clean
clean=$(cmd::test::_strip_ansi < "$tmp")
if [[ $exit_code -ne 0 ]]; then
local msg="${desc}"
[[ -n "$expected" ]] && msg="${desc} (expected '${expected}', command failed)"
test::fail "$msg"
if [[ "${WGCTL_TEST_VERBOSE:-false}" == "true" ]]; then
printf " Output: %s\n" "$(echo "$clean" | head -3 | tr '\n' ' ')"
fi
rm -f "$tmp"
return 1
fi
if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then
local actual
actual=$(echo "$clean" | head -3 | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-100)
test::fail "${desc} (expected '${expected}', got: '${actual}')"
rm -f "$tmp"
return 1
fi
test::pass "$desc"
rm -f "$tmp"
}
function cmd::test::run_cmd_any() {
local desc="$1" expected="${2:-}"
shift 2
local tmp
tmp=$(mktemp)
set +e
timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
set -e
printf "\033[0m" >&2
local clean
clean=$(cmd::test::_strip_ansi < "$tmp")
if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then
local actual
actual=$(echo "$clean" | head -3 | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-100)
test::fail "${desc} (expected '${expected}', got: '${actual}')"
rm -f "$tmp"
return 1
fi
test::pass "$desc"
rm -f "$tmp"
}
function cmd::test::run_cmd_fails() {
local desc="$1"
shift
local tmp exit_code
tmp=$(mktemp)
set +e
timeout 10 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
exit_code=$?
set -e
printf "\033[0m" >&2
rm -f "$tmp"
if [[ $exit_code -eq 124 ]]; then
test::warn "${desc} (timed out)"
return 1
fi
if [[ $exit_code -eq 0 ]]; then
test::fail "${desc} (expected failure but succeeded)"
return 1
fi
test::pass "$desc"
}
# ============================================
# Sections
# ============================================
function cmd::test::run_all_integration_sections() {
cmd::test::section_list
cmd::test::section_inspect
cmd::test::section_config
cmd::test::section_rules
cmd::test::section_groups
cmd::test::section_audit
cmd::test::section_logs
cmd::test::section_fw
cmd::test::section_net
cmd::test::section_subnet
cmd::test::section_identity
}
function cmd::test::section_list() {
test::section "List"
cmd::test::run_cmd "list" "rule:" list
cmd::test::run_cmd "list --online" "" list --online
cmd::test::run_cmd "list --offline" "" list --offline
cmd::test::run_cmd "list --blocked" "" list --blocked
cmd::test::run_cmd "list --type phone" "phone" list --type phone
cmd::test::run_cmd "list --detailed" "rule:" list --detailed
cmd::test::run_cmd "list --name phone-nuno" "phone-nuno" list --name phone-nuno
}
function cmd::test::section_inspect() {
test::section "Inspect"
cmd::test::run_cmd "inspect --name phone-nuno" "IP:" inspect --name phone-nuno
cmd::test::run_cmd "inspect --name nuno --type phone" "IP:" inspect --name nuno --type phone
cmd::test::run_cmd "inspect --name phone-nuno --config" "PrivateKey" inspect --name phone-nuno --config
cmd::test::run_cmd_fails "inspect nonexistent" inspect --name nonexistent-peer
}
function cmd::test::section_config() {
test::section "Config & QR"
cmd::test::run_cmd "config --name phone-nuno" "PrivateKey" config --name phone-nuno
cmd::test::run_cmd "config --name nuno --type phone" "PrivateKey" config --name nuno --type phone
cmd::test::run_cmd "qr --name phone-nuno" "" qr --name phone-nuno
}
function cmd::test::section_rules() {
test::section "Rules"
cmd::test::run_cmd "rule list" "user" rule list
cmd::test::run_cmd "rule show --name guest" "Description" rule show --name guest
cmd::test::run_cmd "rule show --name user" "Description" rule show --name user
cmd::test::run_cmd "rule show --name admin" "Description" rule show --name admin
cmd::test::run_cmd_fails "rule show nonexistent" rule show --name nonexistent
}
function cmd::test::section_groups() {
test::section "Groups"
cmd::test::run_cmd "group list" "Groups" group list
cmd::test::run_cmd "group show --name family" "Peers:" group show --name family
cmd::test::run_cmd_fails "group show nonexistent" group show --name nonexistent
}
function cmd::test::section_audit() {
test::section "Audit"
cmd::test::run_cmd_any "audit" "passed" audit
cmd::test::run_cmd_any "audit --peer phone-nuno" "passed" audit --peer phone-nuno
cmd::test::run_cmd_any "audit --type phone" "passed" audit --type phone
}
function cmd::test::section_logs() {
test::section "Logs"
cmd::test::run_cmd "logs" "Activity" logs
cmd::test::run_cmd "logs --name phone-nuno" "Activity" logs --name phone-nuno
cmd::test::run_cmd "logs --fw" "Firewall Drops" logs --fw
cmd::test::run_cmd "logs --wg" "WireGuard Events" logs --wg
}
function cmd::test::section_fw() {
test::section "Firewall"
cmd::test::run_cmd "fw list" "FORWARD" fw list
cmd::test::run_cmd "fw list --peer phone-nuno" "" fw list --peer phone-nuno
cmd::test::run_cmd "fw list --no-nflog" "" fw list --no-nflog
cmd::test::run_cmd "fw list --no-accept" "" fw list --no-accept
cmd::test::run_cmd "fw list --no-drop" "" fw list --no-drop
cmd::test::run_cmd "fw nat" "PREROUTING" fw nat
cmd::test::run_cmd "fw count" "TOTAL" fw count
}
function cmd::test::section_net() {
test::section "Net"
"$WGCTL_BINARY" net rm --name test-svc --force > /dev/null 2>&1 || true
cmd::test::run_cmd "net add service" "added" net add --name test-svc --ip 10.0.0.99 --desc "Test service"
cmd::test::run_cmd "net add port" "Added" net add --name test-svc:web --port 9999:tcp
cmd::test::run_cmd "net list" "test-svc" net list
cmd::test::run_cmd "net list --detailed" "web" net list --detailed
cmd::test::run_cmd "net show" "9999" net show --name test-svc
cmd::test::run_cmd "net rm port" "Removed" net rm --name test-svc:web --force
cmd::test::run_cmd "net add port again" "Added" net add --name test-svc:web --port 9999:tcp
cmd::test::run_cmd "net rm all ports" "Removed" net rm --name test-svc:ports --force
cmd::test::run_cmd "net rm service" "Removed" net rm --name test-svc --force
cmd::test::run_cmd_fails "net show nonexistent" net show --name nonexistent-svc
cmd::test::run_cmd_fails "net add port no service" net add --name nonexistent:web --port 80:tcp
}
function cmd::test::section_subnet() {
test::section "Subnet"
"$WGCTL_BINARY" subnet rm --name test-subnet-2 > /dev/null 2>&1 || true
"$WGCTL_BINARY" subnet rm --name test-subnet > /dev/null 2>&1 || true
cmd::test::run_cmd "subnet list" "desktop" subnet list
cmd::test::run_cmd "subnet show desktop" "tunnel:" subnet show --name desktop
cmd::test::run_cmd "subnet show guests group" "guests" subnet show --name guests
cmd::test::run_cmd_fails "subnet show nonexistent" subnet show --name nonexistent
cmd::test::run_cmd "subnet add" "added" \
subnet add --name test-subnet --subnet 10.1.250.0/24 --type iot --desc "Test"
cmd::test::run_cmd "subnet list shows new" "test-subnet" \
subnet list
cmd::test::run_cmd_fails "subnet rename in-use (desktop)" \
subnet rename --name desktop --new-name workstation
cmd::test::run_cmd "subnet rename unused" "renamed" \
subnet rename --name test-subnet --new-name test-subnet-2
cmd::test::run_cmd "subnet rm" "removed" \
subnet rm --name test-subnet-2
cmd::test::run_cmd_fails "subnet rm nonexistent" \
subnet rm --name nonexistent-subnet
}
function cmd::test::section_identity() {
test::section "Identity"
cmd::test::run_cmd "identity list" "" identity list
cmd::test::run_cmd "identity migrate --dry-run" "Dry run" identity migrate --dry-run
cmd::test::run_cmd "identity show nuno" "nuno" identity show --name nuno
cmd::test::run_cmd_fails "identity show nonexistent" identity show --name nonexistent
}