gistfile1.txt
· 18 KiB · Text
Originalformat
#!/usr/bin/env bash
WGCTL_BINARY="$(command -v wgctl)"
# ============================================
# Lifecycle
# ============================================
function cmd::test::on_load() {
flag::register --destructive
flag::register --section
flag::register --fn
flag::register --function
}
function cmd::test::help() {
cat <<EOF
Usage: wgctl test [options]
Run the wgctl test suite.
Options:
--destructive Include tests that modify state (add/remove/block)
--section <name> Run only a specific section (list, rules, groups, audit, logs, fw)
Examples:
wgctl test
wgctl test --section rules
wgctl test --destructive
EOF
}
# ============================================
# Test helpers
# ============================================
function cmd::test::run_cmd() {
local desc="$1"
local expected="${2:-}"
shift 2
local tmp exit_code
tmp=$(mktemp)
set +e # disable exit on error (return 1)
timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1 &
local pid=$!
wait $pid
exit_code=$?
set -e # re-enable exit on error
if [[ $exit_code -eq 124 ]]; then
test::warn "${desc} (timed out after 30s)"
rm -f "$tmp"
return 1
fi
if [[ $exit_code -ne 0 ]]; then
test::fail "${desc}"
if [[ "${WGCTL_TEST_VERBOSE:-false}" == "true" ]]; then
printf " Output: %s\n" "$(cat "$tmp")"
fi
rm -f "$tmp"
return 1
fi
if [[ -n "$expected" ]] && ! grep -qF "$expected" "$tmp"; then
local actual
actual=$(head -3 "$tmp" | 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
set +e # disable exit on error (return 1)
local tmp exit_code
tmp=$(mktemp)
timeout 10 setsid "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
exit_code=$?
set -e # re-enable exit on error
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"
}
function cmd::test::run_function() {
local fn="$1"
local namespace
namespace=$(echo "$fn" | cut -d':' -f3)
load_command "$namespace" 2>/dev/null || true
test::reset
log::section "Function Test: ${fn}"
case "$fn" in
cmd::block::run) cmd::test::fn_block ;;
cmd::unblock::run) cmd::test::fn_unblock ;;
cmd::remove::run) cmd::test::fn_remove ;;
cmd::rule::assign) cmd::test::fn_rule_assign ;;
cmd::rename::run) cmd::test::fn_rename ;;
cmd::remove::run) cmd::test::fn_remove ;;
cmd::unblock::run) cmd::test::fn_unblock ;;
*)
log::error "No function test defined for: ${fn}"
return 1
;;
esac
test::summary
}
# ============================================
# Test sections
# ============================================
function cmd::test::section_list() {
test::section "List"
cmd::test::run_cmd "list" "WireGuard Clients" 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" "" list --type phone
cmd::test::run_cmd "list --type guest" "" list --type guest
cmd::test::run_cmd "list --detailed" "Client:" 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 peer" 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" "guest" 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 "audit" "passed" audit
cmd::test::run_cmd "audit --peer phone-nuno" "passed" audit --peer phone-nuno
cmd::test::run_cmd "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 --type guest" "Activity" logs --type guest
cmd::test::run_cmd "logs --fw" "Activity" logs --fw
cmd::test::run_cmd "logs --wg" "Activity" 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_destructive() {
test::section "Destructive (modifying state)"
# ── Cleanup from any previous failed run ──
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" group remove --name testgroup --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" group remove --name testgroup2 --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" net rm --name test-block-svc --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true
# ── Add test peer ──────────────────────────
cmd::test::run_cmd "add phone peer" "added successfully" \
add --name testunit --type phone
# ── Direct block/unblock ───────────────────
cmd::test::run_cmd "block peer" "blocked" block --name phone-testunit
cmd::test::run_cmd "list shows blocked" "blocked" list --blocked
cmd::test::run_cmd "unblock peer" "unblocked" unblock --name phone-testunit
# ── Specific IP block/unblock ──────────────
cmd::test::run_cmd "block peer --ip" "blocked for" \
block --name phone-testunit --ip 10.0.0.99
cmd::test::run_cmd "list shows restricted" "restricted" \
list --name phone-testunit
cmd::test::run_cmd "unblock peer --ip" "unblocked" \
unblock --name phone-testunit --ip 10.0.0.99
# ── Service block/unblock ──────────────────
"$WGCTL_BINARY" net add --name test-block-svc \
--ip 10.0.0.99 > /dev/null 2>&1
"$WGCTL_BINARY" net add --name test-block-svc:web \
--port 9999:tcp > /dev/null 2>&1
cmd::test::run_cmd "block peer --service (ip)" "blocked" \
block --name phone-testunit --service test-block-svc
cmd::test::run_cmd "block already blocked service" "already" \
block --name phone-testunit --service test-block-svc
cmd::test::run_cmd "unblock peer --service (ip)" "unblocked" \
unblock --name phone-testunit --service test-block-svc
cmd::test::run_cmd "unblock not blocked service" "not blocked" \
unblock --name phone-testunit --service test-block-svc
cmd::test::run_cmd "block peer --service (port)" "blocked" \
block --name phone-testunit --service test-block-svc:web
cmd::test::run_cmd "unblock peer --service (port)" "unblocked" \
unblock --name phone-testunit --service test-block-svc:web
"$WGCTL_BINARY" net rm --name test-block-svc --force > /dev/null 2>&1 || true
# ── Rule assign/unassign ───────────────────
cmd::test::run_cmd "rule assign" "Assigned" \
rule assign --name user --peer phone-testunit
cmd::test::run_cmd "rule unassign" "Unassigned" \
rule unassign --peer phone-testunit
"$WGCTL_BINARY" rule assign --name user --peer phone-testunit \
> /dev/null 2>&1 || true
# ── Group basic operations ─────────────────
cmd::test::run_cmd "group add" "created" \
group add --name testgroup --desc "Test group"
cmd::test::run_cmd "group peer add" "Added" \
group peer add --name testgroup --peer phone-testunit
cmd::test::run_cmd "group block" "blocked" \
group block --name testgroup
cmd::test::run_cmd "group unblock" "unblocked" \
group unblock --name testgroup
# ── M:N group block tracking ───────────────
"$WGCTL_BINARY" group add --name testgroup2 \
--desc "Test group 2" > /dev/null 2>&1
"$WGCTL_BINARY" group peer add --name testgroup2 \
--peer phone-testunit > /dev/null 2>&1
cmd::test::run_cmd "group block first group" "blocked" \
group block --name testgroup
cmd::test::run_cmd "group block second group" "blocked" \
group block --name testgroup2
"$WGCTL_BINARY" group unblock --name testgroup > /dev/null 2>&1
cmd::test::run_cmd "peer stays blocked after partial unblock" "blocked" \
list --blocked
"$WGCTL_BINARY" group unblock --name testgroup2 > /dev/null 2>&1
cmd::test::run_cmd "peer unblocked after all groups unblock" "phone-testunit" \
list --allowed
# ── Direct block overrides group block ─────
"$WGCTL_BINARY" group block --name testgroup > /dev/null 2>&1
cmd::test::run_cmd "direct unblock overrides group block" "unblocked" \
unblock --name phone-testunit
# ── Cleanup ────────────────────────────────
cmd::test::run_cmd "group remove" "removed" \
group remove --name testgroup --force
"$WGCTL_BINARY" group remove --name testgroup2 --force > /dev/null 2>&1 || true
cmd::test::run_cmd "remove phone peer" "removed" \
remove --name phone-testunit --force
}
# ============================================
# Function Blocks
# ============================================
function cmd::test::fn_block() {
test::section "cmd::block::run"
# Setup
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
# Tests
cmd::test::run_cmd "block peer" "blocked" block --name phone-testunit
cmd::test::run_cmd "block already blocked" "already" block --name phone-testunit
"$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true
cmd::test::run_cmd "block with --type" "blocked" block --name testunit --type phone
cmd::test::run_cmd_fails "block nonexistent" block --name truly-nonexistent-xyz
# Cleanup
"$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
}
function cmd::test::fn_remove() {
test::section "cmd::remove::run"
# Setup
"$WGCTL_BINARY" remove --name phone-testunit --force \
> /dev/null 2>&1 || true
"$WGCTL_BINARY" add --name testunit --type phone \
> /dev/null 2>&1
# Tests
# Skip the interactive prompt test — it hangs waiting for input
# cmd::test::run_cmd_fails "remove without --force" \
# remove --name phone-testunit
cmd::test::run_cmd "remove with --force" "removed" \
remove --name phone-testunit --force
cmd::test::run_cmd_fails "remove nonexistent" \
remove --name nonexistent-peer --force
cmd::test::run_cmd_fails "remove missing --name" \
remove --force
# Cleanup already done by tests
}
function cmd::test::fn_rename() {
test::section "cmd::rename::run"
# Setup
"$WGCTL_BINARY" remove --name phone-testunit --force \
> /dev/null 2>&1 || true
"$WGCTL_BINARY" remove --name phone-testunit2 --force \
> /dev/null 2>&1 || true
"$WGCTL_BINARY" add --name testunit --type phone \
> /dev/null 2>&1
# Tests
cmd::test::run_cmd "rename peer" "renamed" \
rename --name phone-testunit --new-name phone-testunit2
cmd::test::run_cmd_fails "rename to existing" \
rename --name phone-testunit2 --new-name phone-nuno
cmd::test::run_cmd_fails "rename nonexistent" \
rename --name phone-nonexistent --new-name phone-testunit
cmd::test::run_cmd_fails "rename missing --new-name" \
rename --name phone-testunit2
# Cleanup
"$WGCTL_BINARY" remove --name phone-testunit2 --force \
> /dev/null 2>&1 || true
}
function cmd::test::fn_unblock() {
test::section "cmd::unblock::run"
# Setup — add and block a peer
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
"$WGCTL_BINARY" block --name phone-testunit > /dev/null 2>&1
# Tests
cmd::test::run_cmd "unblock peer" "unblocked" unblock --name phone-testunit
cmd::test::run_cmd "unblock not blocked" "not blocked" unblock --name phone-testunit
cmd::test::run_cmd_fails "unblock nonexistent" unblock --name nonexistent-peer
cmd::test::run_cmd_fails "unblock missing --name" unblock
# Cleanup
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
}
function cmd::test::fn_rule_assign() {
test::section "cmd::rule::assign"
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
cmd::test::run_cmd "rule assign" "Assigned" \
rule assign --name admin --peer phone-testunit
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
}
# ============================================
# Run
# ============================================
function cmd::test::run() {
local destructive=false section=""
local fn=""
while [[ $# -gt 0 ]]; do
case "$1" in
--destructive) destructive=true; shift ;;
--section)
util::require_flag "--section" "${2:-}" || return 1
section="$2"; shift 2
;;
--fn|--function) fn="$2"; shift 2 ;;
--verbose|-v) WGCTL_TEST_VERBOSE=true; shift ;;
--help) cmd::test::help; return ;;
*)
log::error "Unknown flag: $1"
return 1
;;
esac
done
# After flag parsing:
if [[ -n "$fn" ]]; then
cmd::test::run_function "$fn"
return
fi
test::reset
log::section "wgctl Test Suite"
if [[ -n "$section" ]]; then
case "$section" in
list) cmd::test::section_list ;;
inspect) cmd::test::section_inspect ;;
config) cmd::test::section_config ;;
rules) cmd::test::section_rules ;;
groups) cmd::test::section_groups ;;
audit) cmd::test::section_audit ;;
logs) cmd::test::section_logs ;;
fw) cmd::test::section_fw ;;
net) cmd::test::section_net ;;
destructive) cmd::test::section_destructive ;;
*)
log::error "Unknown section: $section"
return 1
;;
esac
else
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
fi
if $destructive; then
cmd::test::section_destructive
fi
test::summary
}
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | WGCTL_BINARY="$(command -v wgctl)" |
| 4 | |
| 5 | # ============================================ |
| 6 | # Lifecycle |
| 7 | # ============================================ |
| 8 | |
| 9 | function cmd::test::on_load() { |
| 10 | flag::register --destructive |
| 11 | flag::register --section |
| 12 | flag::register --fn |
| 13 | flag::register --function |
| 14 | } |
| 15 | |
| 16 | function cmd::test::help() { |
| 17 | cat <<EOF |
| 18 | Usage: wgctl test [options] |
| 19 | |
| 20 | Run the wgctl test suite. |
| 21 | |
| 22 | Options: |
| 23 | --destructive Include tests that modify state (add/remove/block) |
| 24 | --section <name> Run only a specific section (list, rules, groups, audit, logs, fw) |
| 25 | |
| 26 | Examples: |
| 27 | wgctl test |
| 28 | wgctl test --section rules |
| 29 | wgctl test --destructive |
| 30 | EOF |
| 31 | } |
| 32 | |
| 33 | # ============================================ |
| 34 | # Test helpers |
| 35 | # ============================================ |
| 36 | |
| 37 | function cmd::test::run_cmd() { |
| 38 | local desc="$1" |
| 39 | local expected="${2:-}" |
| 40 | shift 2 |
| 41 | |
| 42 | local tmp exit_code |
| 43 | tmp=$(mktemp) |
| 44 | |
| 45 | set +e # disable exit on error (return 1) |
| 46 | |
| 47 | timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1 & |
| 48 | local pid=$! |
| 49 | wait $pid |
| 50 | exit_code=$? |
| 51 | |
| 52 | set -e # re-enable exit on error |
| 53 | |
| 54 | if [[ $exit_code -eq 124 ]]; then |
| 55 | test::warn "${desc} (timed out after 30s)" |
| 56 | rm -f "$tmp" |
| 57 | return 1 |
| 58 | fi |
| 59 | |
| 60 | if [[ $exit_code -ne 0 ]]; then |
| 61 | test::fail "${desc}" |
| 62 | if [[ "${WGCTL_TEST_VERBOSE:-false}" == "true" ]]; then |
| 63 | printf " Output: %s\n" "$(cat "$tmp")" |
| 64 | fi |
| 65 | rm -f "$tmp" |
| 66 | return 1 |
| 67 | fi |
| 68 | |
| 69 | if [[ -n "$expected" ]] && ! grep -qF "$expected" "$tmp"; then |
| 70 | local actual |
| 71 | actual=$(head -3 "$tmp" | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-100) |
| 72 | test::fail "${desc} (expected '${expected}', got: '${actual}')" |
| 73 | rm -f "$tmp" |
| 74 | return 1 |
| 75 | fi |
| 76 | |
| 77 | test::pass "$desc" |
| 78 | rm -f "$tmp" |
| 79 | } |
| 80 | |
| 81 | function cmd::test::run_cmd_fails() { |
| 82 | local desc="$1" |
| 83 | shift |
| 84 | |
| 85 | set +e # disable exit on error (return 1) |
| 86 | |
| 87 | local tmp exit_code |
| 88 | tmp=$(mktemp) |
| 89 | timeout 10 setsid "$WGCTL_BINARY" "$@" > "$tmp" 2>&1 |
| 90 | exit_code=$? |
| 91 | |
| 92 | set -e # re-enable exit on error |
| 93 | |
| 94 | rm -f "$tmp" |
| 95 | |
| 96 | if [[ $exit_code -eq 124 ]]; then |
| 97 | test::warn "${desc} (timed out)" |
| 98 | return 1 |
| 99 | fi |
| 100 | |
| 101 | if [[ $exit_code -eq 0 ]]; then |
| 102 | test::fail "${desc} (expected failure but succeeded)" |
| 103 | return 1 |
| 104 | fi |
| 105 | |
| 106 | test::pass "$desc" |
| 107 | } |
| 108 | |
| 109 | function cmd::test::run_function() { |
| 110 | local fn="$1" |
| 111 | |
| 112 | local namespace |
| 113 | namespace=$(echo "$fn" | cut -d':' -f3) |
| 114 | load_command "$namespace" 2>/dev/null || true |
| 115 | |
| 116 | test::reset |
| 117 | log::section "Function Test: ${fn}" |
| 118 | |
| 119 | case "$fn" in |
| 120 | cmd::block::run) cmd::test::fn_block ;; |
| 121 | cmd::unblock::run) cmd::test::fn_unblock ;; |
| 122 | cmd::remove::run) cmd::test::fn_remove ;; |
| 123 | cmd::rule::assign) cmd::test::fn_rule_assign ;; |
| 124 | cmd::rename::run) cmd::test::fn_rename ;; |
| 125 | cmd::remove::run) cmd::test::fn_remove ;; |
| 126 | cmd::unblock::run) cmd::test::fn_unblock ;; |
| 127 | *) |
| 128 | log::error "No function test defined for: ${fn}" |
| 129 | return 1 |
| 130 | ;; |
| 131 | esac |
| 132 | |
| 133 | test::summary |
| 134 | } |
| 135 | |
| 136 | # ============================================ |
| 137 | # Test sections |
| 138 | # ============================================ |
| 139 | |
| 140 | function cmd::test::section_list() { |
| 141 | test::section "List" |
| 142 | cmd::test::run_cmd "list" "WireGuard Clients" list |
| 143 | cmd::test::run_cmd "list --online" "" list --online |
| 144 | cmd::test::run_cmd "list --offline" "" list --offline |
| 145 | cmd::test::run_cmd "list --blocked" "" list --blocked |
| 146 | cmd::test::run_cmd "list --type phone" "" list --type phone |
| 147 | cmd::test::run_cmd "list --type guest" "" list --type guest |
| 148 | cmd::test::run_cmd "list --detailed" "Client:" list --detailed |
| 149 | cmd::test::run_cmd "list --name phone-nuno" "phone-nuno" list --name phone-nuno |
| 150 | } |
| 151 | |
| 152 | function cmd::test::section_inspect() { |
| 153 | test::section "Inspect" |
| 154 | cmd::test::run_cmd "inspect --name phone-nuno" "IP:" inspect --name phone-nuno |
| 155 | cmd::test::run_cmd "inspect --name nuno --type phone" "IP:" inspect --name nuno --type phone |
| 156 | cmd::test::run_cmd "inspect --name phone-nuno --config" "PrivateKey" inspect --name phone-nuno --config |
| 157 | cmd::test::run_cmd_fails "inspect nonexistent peer" inspect --name nonexistent-peer |
| 158 | } |
| 159 | |
| 160 | function cmd::test::section_config() { |
| 161 | test::section "Config & QR" |
| 162 | cmd::test::run_cmd "config --name phone-nuno" "PrivateKey" config --name phone-nuno |
| 163 | cmd::test::run_cmd "config --name nuno --type phone" "PrivateKey" config --name nuno --type phone |
| 164 | cmd::test::run_cmd "qr --name phone-nuno" "" qr --name phone-nuno |
| 165 | } |
| 166 | |
| 167 | function cmd::test::section_rules() { |
| 168 | test::section "Rules" |
| 169 | cmd::test::run_cmd "rule list" "guest" rule list |
| 170 | cmd::test::run_cmd "rule show --name guest" "Description" rule show --name guest |
| 171 | cmd::test::run_cmd "rule show --name user" "Description" rule show --name user |
| 172 | cmd::test::run_cmd "rule show --name admin" "Description" rule show --name admin |
| 173 | cmd::test::run_cmd_fails "rule show nonexistent" rule show --name nonexistent |
| 174 | } |
| 175 | |
| 176 | function cmd::test::section_groups() { |
| 177 | test::section "Groups" |
| 178 | cmd::test::run_cmd "group list" "Groups" group list |
| 179 | cmd::test::run_cmd "group show --name family" "Peers:" group show --name family |
| 180 | cmd::test::run_cmd_fails "group show nonexistent" group show --name nonexistent |
| 181 | } |
| 182 | |
| 183 | function cmd::test::section_audit() { |
| 184 | test::section "Audit" |
| 185 | cmd::test::run_cmd "audit" "passed" audit |
| 186 | cmd::test::run_cmd "audit --peer phone-nuno" "passed" audit --peer phone-nuno |
| 187 | cmd::test::run_cmd "audit --type phone" "passed" audit --type phone |
| 188 | } |
| 189 | |
| 190 | function cmd::test::section_logs() { |
| 191 | test::section "Logs" |
| 192 | cmd::test::run_cmd "logs" "Activity" logs |
| 193 | cmd::test::run_cmd "logs --name phone-nuno" "Activity" logs --name phone-nuno |
| 194 | cmd::test::run_cmd "logs --type guest" "Activity" logs --type guest |
| 195 | cmd::test::run_cmd "logs --fw" "Activity" logs --fw |
| 196 | cmd::test::run_cmd "logs --wg" "Activity" logs --wg |
| 197 | } |
| 198 | |
| 199 | function cmd::test::section_fw() { |
| 200 | test::section "Firewall" |
| 201 | cmd::test::run_cmd "fw list" "FORWARD" fw list |
| 202 | cmd::test::run_cmd "fw list --peer phone-nuno" "" fw list --peer phone-nuno |
| 203 | cmd::test::run_cmd "fw list --no-nflog" "" fw list --no-nflog |
| 204 | cmd::test::run_cmd "fw list --no-accept" "" fw list --no-accept |
| 205 | cmd::test::run_cmd "fw list --no-drop" "" fw list --no-drop |
| 206 | cmd::test::run_cmd "fw nat" "PREROUTING" fw nat |
| 207 | cmd::test::run_cmd "fw count" "TOTAL" fw count |
| 208 | } |
| 209 | function cmd::test::section_net() { |
| 210 | test::section "Net" |
| 211 | |
| 212 | "$WGCTL_BINARY" net rm --name test-svc --force > /dev/null 2>&1 || true |
| 213 | |
| 214 | cmd::test::run_cmd "net add service" "added" \ |
| 215 | net add --name test-svc --ip 10.0.0.99 --desc "Test service" |
| 216 | cmd::test::run_cmd "net add port" "Added" \ |
| 217 | net add --name test-svc:web --port 9999:tcp |
| 218 | cmd::test::run_cmd "net list" "test-svc" \ |
| 219 | net list |
| 220 | cmd::test::run_cmd "net list --detailed" "web" \ |
| 221 | net list --detailed |
| 222 | cmd::test::run_cmd "net show" "9999" \ |
| 223 | net show --name test-svc |
| 224 | cmd::test::run_cmd "net rm port" "Removed" \ |
| 225 | net rm --name test-svc:web --force |
| 226 | cmd::test::run_cmd "net add port again" "Added" \ |
| 227 | net add --name test-svc:web --port 9999:tcp |
| 228 | cmd::test::run_cmd "net rm all ports" "Removed" \ |
| 229 | net rm --name test-svc:ports --force |
| 230 | cmd::test::run_cmd "net rm service" "Removed" \ |
| 231 | net rm --name test-svc --force |
| 232 | cmd::test::run_cmd_fails "net show nonexistent" \ |
| 233 | net show --name nonexistent-svc |
| 234 | cmd::test::run_cmd_fails "net add port no service" \ |
| 235 | net add --name nonexistent:web --port 80:tcp |
| 236 | } |
| 237 | |
| 238 | function cmd::test::section_destructive() { |
| 239 | test::section "Destructive (modifying state)" |
| 240 | |
| 241 | # ── Cleanup from any previous failed run ── |
| 242 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 243 | "$WGCTL_BINARY" group remove --name testgroup --force > /dev/null 2>&1 || true |
| 244 | "$WGCTL_BINARY" group remove --name testgroup2 --force > /dev/null 2>&1 || true |
| 245 | "$WGCTL_BINARY" net rm --name test-block-svc --force > /dev/null 2>&1 || true |
| 246 | "$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true |
| 247 | |
| 248 | # ── Add test peer ────────────────────────── |
| 249 | cmd::test::run_cmd "add phone peer" "added successfully" \ |
| 250 | add --name testunit --type phone |
| 251 | |
| 252 | # ── Direct block/unblock ─────────────────── |
| 253 | cmd::test::run_cmd "block peer" "blocked" block --name phone-testunit |
| 254 | cmd::test::run_cmd "list shows blocked" "blocked" list --blocked |
| 255 | cmd::test::run_cmd "unblock peer" "unblocked" unblock --name phone-testunit |
| 256 | |
| 257 | # ── Specific IP block/unblock ────────────── |
| 258 | cmd::test::run_cmd "block peer --ip" "blocked for" \ |
| 259 | block --name phone-testunit --ip 10.0.0.99 |
| 260 | cmd::test::run_cmd "list shows restricted" "restricted" \ |
| 261 | list --name phone-testunit |
| 262 | cmd::test::run_cmd "unblock peer --ip" "unblocked" \ |
| 263 | unblock --name phone-testunit --ip 10.0.0.99 |
| 264 | |
| 265 | # ── Service block/unblock ────────────────── |
| 266 | "$WGCTL_BINARY" net add --name test-block-svc \ |
| 267 | --ip 10.0.0.99 > /dev/null 2>&1 |
| 268 | "$WGCTL_BINARY" net add --name test-block-svc:web \ |
| 269 | --port 9999:tcp > /dev/null 2>&1 |
| 270 | cmd::test::run_cmd "block peer --service (ip)" "blocked" \ |
| 271 | block --name phone-testunit --service test-block-svc |
| 272 | cmd::test::run_cmd "block already blocked service" "already" \ |
| 273 | block --name phone-testunit --service test-block-svc |
| 274 | cmd::test::run_cmd "unblock peer --service (ip)" "unblocked" \ |
| 275 | unblock --name phone-testunit --service test-block-svc |
| 276 | cmd::test::run_cmd "unblock not blocked service" "not blocked" \ |
| 277 | unblock --name phone-testunit --service test-block-svc |
| 278 | cmd::test::run_cmd "block peer --service (port)" "blocked" \ |
| 279 | block --name phone-testunit --service test-block-svc:web |
| 280 | cmd::test::run_cmd "unblock peer --service (port)" "unblocked" \ |
| 281 | unblock --name phone-testunit --service test-block-svc:web |
| 282 | "$WGCTL_BINARY" net rm --name test-block-svc --force > /dev/null 2>&1 || true |
| 283 | |
| 284 | # ── Rule assign/unassign ─────────────────── |
| 285 | cmd::test::run_cmd "rule assign" "Assigned" \ |
| 286 | rule assign --name user --peer phone-testunit |
| 287 | cmd::test::run_cmd "rule unassign" "Unassigned" \ |
| 288 | rule unassign --peer phone-testunit |
| 289 | "$WGCTL_BINARY" rule assign --name user --peer phone-testunit \ |
| 290 | > /dev/null 2>&1 || true |
| 291 | |
| 292 | # ── Group basic operations ───────────────── |
| 293 | cmd::test::run_cmd "group add" "created" \ |
| 294 | group add --name testgroup --desc "Test group" |
| 295 | cmd::test::run_cmd "group peer add" "Added" \ |
| 296 | group peer add --name testgroup --peer phone-testunit |
| 297 | cmd::test::run_cmd "group block" "blocked" \ |
| 298 | group block --name testgroup |
| 299 | cmd::test::run_cmd "group unblock" "unblocked" \ |
| 300 | group unblock --name testgroup |
| 301 | |
| 302 | # ── M:N group block tracking ─────────────── |
| 303 | "$WGCTL_BINARY" group add --name testgroup2 \ |
| 304 | --desc "Test group 2" > /dev/null 2>&1 |
| 305 | "$WGCTL_BINARY" group peer add --name testgroup2 \ |
| 306 | --peer phone-testunit > /dev/null 2>&1 |
| 307 | |
| 308 | cmd::test::run_cmd "group block first group" "blocked" \ |
| 309 | group block --name testgroup |
| 310 | cmd::test::run_cmd "group block second group" "blocked" \ |
| 311 | group block --name testgroup2 |
| 312 | |
| 313 | "$WGCTL_BINARY" group unblock --name testgroup > /dev/null 2>&1 |
| 314 | cmd::test::run_cmd "peer stays blocked after partial unblock" "blocked" \ |
| 315 | list --blocked |
| 316 | |
| 317 | "$WGCTL_BINARY" group unblock --name testgroup2 > /dev/null 2>&1 |
| 318 | cmd::test::run_cmd "peer unblocked after all groups unblock" "phone-testunit" \ |
| 319 | list --allowed |
| 320 | |
| 321 | # ── Direct block overrides group block ───── |
| 322 | "$WGCTL_BINARY" group block --name testgroup > /dev/null 2>&1 |
| 323 | cmd::test::run_cmd "direct unblock overrides group block" "unblocked" \ |
| 324 | unblock --name phone-testunit |
| 325 | |
| 326 | # ── Cleanup ──────────────────────────────── |
| 327 | cmd::test::run_cmd "group remove" "removed" \ |
| 328 | group remove --name testgroup --force |
| 329 | "$WGCTL_BINARY" group remove --name testgroup2 --force > /dev/null 2>&1 || true |
| 330 | |
| 331 | cmd::test::run_cmd "remove phone peer" "removed" \ |
| 332 | remove --name phone-testunit --force |
| 333 | } |
| 334 | |
| 335 | # ============================================ |
| 336 | # Function Blocks |
| 337 | # ============================================ |
| 338 | |
| 339 | function cmd::test::fn_block() { |
| 340 | test::section "cmd::block::run" |
| 341 | |
| 342 | # Setup |
| 343 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 344 | "$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1 |
| 345 | |
| 346 | # Tests |
| 347 | cmd::test::run_cmd "block peer" "blocked" block --name phone-testunit |
| 348 | cmd::test::run_cmd "block already blocked" "already" block --name phone-testunit |
| 349 | |
| 350 | "$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true |
| 351 | cmd::test::run_cmd "block with --type" "blocked" block --name testunit --type phone |
| 352 | |
| 353 | cmd::test::run_cmd_fails "block nonexistent" block --name truly-nonexistent-xyz |
| 354 | |
| 355 | # Cleanup |
| 356 | "$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true |
| 357 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 358 | } |
| 359 | |
| 360 | function cmd::test::fn_remove() { |
| 361 | test::section "cmd::remove::run" |
| 362 | |
| 363 | # Setup |
| 364 | "$WGCTL_BINARY" remove --name phone-testunit --force \ |
| 365 | > /dev/null 2>&1 || true |
| 366 | "$WGCTL_BINARY" add --name testunit --type phone \ |
| 367 | > /dev/null 2>&1 |
| 368 | |
| 369 | # Tests |
| 370 | # Skip the interactive prompt test — it hangs waiting for input |
| 371 | # cmd::test::run_cmd_fails "remove without --force" \ |
| 372 | # remove --name phone-testunit |
| 373 | cmd::test::run_cmd "remove with --force" "removed" \ |
| 374 | remove --name phone-testunit --force |
| 375 | cmd::test::run_cmd_fails "remove nonexistent" \ |
| 376 | remove --name nonexistent-peer --force |
| 377 | cmd::test::run_cmd_fails "remove missing --name" \ |
| 378 | remove --force |
| 379 | |
| 380 | # Cleanup already done by tests |
| 381 | } |
| 382 | |
| 383 | function cmd::test::fn_rename() { |
| 384 | test::section "cmd::rename::run" |
| 385 | |
| 386 | # Setup |
| 387 | "$WGCTL_BINARY" remove --name phone-testunit --force \ |
| 388 | > /dev/null 2>&1 || true |
| 389 | "$WGCTL_BINARY" remove --name phone-testunit2 --force \ |
| 390 | > /dev/null 2>&1 || true |
| 391 | "$WGCTL_BINARY" add --name testunit --type phone \ |
| 392 | > /dev/null 2>&1 |
| 393 | |
| 394 | # Tests |
| 395 | cmd::test::run_cmd "rename peer" "renamed" \ |
| 396 | rename --name phone-testunit --new-name phone-testunit2 |
| 397 | cmd::test::run_cmd_fails "rename to existing" \ |
| 398 | rename --name phone-testunit2 --new-name phone-nuno |
| 399 | cmd::test::run_cmd_fails "rename nonexistent" \ |
| 400 | rename --name phone-nonexistent --new-name phone-testunit |
| 401 | cmd::test::run_cmd_fails "rename missing --new-name" \ |
| 402 | rename --name phone-testunit2 |
| 403 | |
| 404 | # Cleanup |
| 405 | "$WGCTL_BINARY" remove --name phone-testunit2 --force \ |
| 406 | > /dev/null 2>&1 || true |
| 407 | } |
| 408 | |
| 409 | function cmd::test::fn_unblock() { |
| 410 | test::section "cmd::unblock::run" |
| 411 | |
| 412 | # Setup — add and block a peer |
| 413 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 414 | "$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1 |
| 415 | "$WGCTL_BINARY" block --name phone-testunit > /dev/null 2>&1 |
| 416 | |
| 417 | # Tests |
| 418 | cmd::test::run_cmd "unblock peer" "unblocked" unblock --name phone-testunit |
| 419 | cmd::test::run_cmd "unblock not blocked" "not blocked" unblock --name phone-testunit |
| 420 | cmd::test::run_cmd_fails "unblock nonexistent" unblock --name nonexistent-peer |
| 421 | cmd::test::run_cmd_fails "unblock missing --name" unblock |
| 422 | |
| 423 | # Cleanup |
| 424 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 425 | } |
| 426 | |
| 427 | function cmd::test::fn_rule_assign() { |
| 428 | test::section "cmd::rule::assign" |
| 429 | |
| 430 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 431 | "$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1 |
| 432 | |
| 433 | cmd::test::run_cmd "rule assign" "Assigned" \ |
| 434 | rule assign --name admin --peer phone-testunit |
| 435 | |
| 436 | "$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true |
| 437 | } |
| 438 | |
| 439 | # ============================================ |
| 440 | # Run |
| 441 | # ============================================ |
| 442 | |
| 443 | function cmd::test::run() { |
| 444 | local destructive=false section="" |
| 445 | local fn="" |
| 446 | |
| 447 | while [[ $# -gt 0 ]]; do |
| 448 | case "$1" in |
| 449 | --destructive) destructive=true; shift ;; |
| 450 | --section) |
| 451 | util::require_flag "--section" "${2:-}" || return 1 |
| 452 | section="$2"; shift 2 |
| 453 | ;; |
| 454 | --fn|--function) fn="$2"; shift 2 ;; |
| 455 | --verbose|-v) WGCTL_TEST_VERBOSE=true; shift ;; |
| 456 | --help) cmd::test::help; return ;; |
| 457 | *) |
| 458 | log::error "Unknown flag: $1" |
| 459 | return 1 |
| 460 | ;; |
| 461 | esac |
| 462 | done |
| 463 | |
| 464 | # After flag parsing: |
| 465 | if [[ -n "$fn" ]]; then |
| 466 | cmd::test::run_function "$fn" |
| 467 | return |
| 468 | fi |
| 469 | |
| 470 | test::reset |
| 471 | log::section "wgctl Test Suite" |
| 472 | |
| 473 | if [[ -n "$section" ]]; then |
| 474 | case "$section" in |
| 475 | list) cmd::test::section_list ;; |
| 476 | inspect) cmd::test::section_inspect ;; |
| 477 | config) cmd::test::section_config ;; |
| 478 | rules) cmd::test::section_rules ;; |
| 479 | groups) cmd::test::section_groups ;; |
| 480 | audit) cmd::test::section_audit ;; |
| 481 | logs) cmd::test::section_logs ;; |
| 482 | fw) cmd::test::section_fw ;; |
| 483 | net) cmd::test::section_net ;; |
| 484 | destructive) cmd::test::section_destructive ;; |
| 485 | *) |
| 486 | log::error "Unknown section: $section" |
| 487 | return 1 |
| 488 | ;; |
| 489 | esac |
| 490 | else |
| 491 | cmd::test::section_list |
| 492 | cmd::test::section_inspect |
| 493 | cmd::test::section_config |
| 494 | cmd::test::section_rules |
| 495 | cmd::test::section_groups |
| 496 | cmd::test::section_audit |
| 497 | cmd::test::section_logs |
| 498 | cmd::test::section_fw |
| 499 | fi |
| 500 | |
| 501 | if $destructive; then |
| 502 | cmd::test::section_destructive |
| 503 | fi |
| 504 | |
| 505 | test::summary |
| 506 | } |