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