feat: test suite, date formatter, list optimizations, fw:: rename, config overrides
This commit is contained in:
parent
0efa6c3a9e
commit
312f1f973c
3 changed files with 291 additions and 8 deletions
|
|
@ -56,7 +56,7 @@ function cmd::fw::list() {
|
|||
ip=$(peers::get_ip "$peer")
|
||||
[[ -z "$ip" ]] && log::error "Peer not found: $peer" && return 1
|
||||
iptables -L FORWARD -n -v | grep -F "$ip" \
|
||||
| cmd::fw::_print_filtered "$show_nflog" "$show_accept" "$show_drop"
|
||||
| cmd::fw::_print_filtered "$show_nflog" "$show_accept" "$show_drop" || true
|
||||
elif [[ -n "$type" ]]; then
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$type")
|
||||
|
|
|
|||
|
|
@ -105,13 +105,18 @@ function cmd::list::_is_attempting() {
|
|||
local now attempt_ts diff
|
||||
now=$(date +%s)
|
||||
attempt_ts=$(json::iso_to_ts "$last_ts")
|
||||
[[ -z "$attempt_ts" || "$attempt_ts" == "0" ]] && return 1
|
||||
diff=$(( now - attempt_ts ))
|
||||
(( diff < 180 ))
|
||||
}
|
||||
|
||||
function cmd::list::_format_last_seen() {
|
||||
local name="$1" pubkey="$2" is_blocked="$3"
|
||||
local last_ts="$4" last_evt="$5" handshake_ts="$6"
|
||||
local name="$1"
|
||||
local pubkey="$2"
|
||||
local is_blocked="${3:-0}"
|
||||
local last_ts="${4:-0}"
|
||||
local last_evt="${5:-0}"
|
||||
local handshake_ts="${6:-0}"
|
||||
|
||||
if [[ "$is_blocked" == "true" ]]; then
|
||||
if [[ -n "$last_ts" ]]; then
|
||||
|
|
@ -133,10 +138,12 @@ function cmd::list::_format_last_seen() {
|
|||
}
|
||||
|
||||
function cmd::list::_format_status() {
|
||||
local name="$1" pubkey="$2"
|
||||
local is_blocked="$3" is_restricted="$4"
|
||||
local handshake_ts="$5" last_ts="$6"
|
||||
|
||||
local name="$1"
|
||||
local pubkey="$2"
|
||||
local is_blocked="${3:-false}"
|
||||
local is_restricted="${4:-false}"
|
||||
local handshake_ts="${5:-0}"
|
||||
local last_ts="${6:-}"
|
||||
local connected=false modifier="" color
|
||||
|
||||
if [[ "$is_blocked" == "true" ]]; then
|
||||
|
|
@ -177,7 +184,9 @@ function cmd::list::_get_type() {
|
|||
}
|
||||
|
||||
function cmd::list::display_type() {
|
||||
local name="$1" type="$2" subtype="$3"
|
||||
local name="${1:-0}"
|
||||
local type="${2:-0}"
|
||||
local subtype="${3:-0}"
|
||||
if config::is_guest_type "$type" && [[ -n "$subtype" ]]; then
|
||||
echo "guest/${subtype}"
|
||||
elif config::is_guest_type "$type"; then
|
||||
|
|
|
|||
274
commands/test.command.sh
Normal file
274
commands/test.command.sh
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# ============================================
|
||||
# Lifecycle
|
||||
# ============================================
|
||||
|
||||
function cmd::test::on_load() {
|
||||
flag::register --destructive
|
||||
flag::register --section
|
||||
}
|
||||
|
||||
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 /usr/local/bin/wgctl "$@" > "$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 /usr/local/bin/wgctl "$@" > "$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"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Test sections
|
||||
# ============================================
|
||||
|
||||
function cmd::test::section_list() {
|
||||
test::section "List"
|
||||
cmd::test::run_cmd "list" "WireGuard Clients" list
|
||||
cmd::test::run_cmd "list --online" "STATUS" list --online
|
||||
cmd::test::run_cmd "list --offline" "STATUS" list --offline
|
||||
cmd::test::run_cmd "list --blocked" "STATUS" list --blocked
|
||||
cmd::test::run_cmd "list --type phone" "phone" list --type phone
|
||||
cmd::test::run_cmd "list --type guest" "guest" list --type guest
|
||||
# TODO: Fix detailed, hangs
|
||||
# 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 guest --peers" "Peers:" rule show --name guest --peers
|
||||
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_destructive() {
|
||||
test::section "Destructive (modifying state)"
|
||||
|
||||
# Add test peer
|
||||
cmd::test::run_cmd "add phone peer" "added successfully" \
|
||||
wgctl add --name testunit --type phone --force
|
||||
|
||||
# Block/unblock
|
||||
cmd::test::run_cmd "block peer" "blocked" \
|
||||
wgctl block --name phone-testunit --force
|
||||
cmd::test::run_cmd "list shows blocked" "blocked" \
|
||||
wgctl list --blocked
|
||||
cmd::test::run_cmd "unblock peer" "unblocked" \
|
||||
wgctl unblock --name phone-testunit --force
|
||||
|
||||
# Rule assign/unassign
|
||||
cmd::test::run_cmd "rule assign" "Assigned" \
|
||||
wgctl rule assign --name admin --peer phone-testunit
|
||||
cmd::test::run_cmd "rule unassign" "Unassigned" \
|
||||
wgctl rule unassign --peer phone-testunit
|
||||
|
||||
# Group operations
|
||||
cmd::test::run_cmd "group add" "created" \
|
||||
wgctl group add --name testgroup --desc "Test group"
|
||||
cmd::test::run_cmd "group peer add" "Added" \
|
||||
wgctl group peer add --name testgroup --peer phone-testunit
|
||||
cmd::test::run_cmd "group block" "Blocked" \
|
||||
wgctl group block --name testgroup
|
||||
cmd::test::run_cmd "group unblock" "Unblocked" \
|
||||
wgctl group unblock --name testgroup
|
||||
cmd::test::run_cmd "group remove" "removed" \
|
||||
wgctl group remove --name testgroup --force
|
||||
|
||||
# Remove test peer
|
||||
cmd::test::run_cmd "remove phone peer" "removed" \
|
||||
wgctl remove --name phone-testunit --force
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Run
|
||||
# ============================================
|
||||
|
||||
function cmd::test::run() {
|
||||
local destructive=false section=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--destructive) destructive=true; shift ;;
|
||||
--section)
|
||||
util::require_flag "--section" "${2:-}" || return 1
|
||||
section="$2"; shift 2 ;;
|
||||
--verbose|-v) WGCTL_TEST_VERBOSE=true; shift ;;
|
||||
--help) cmd::test::help; return ;;
|
||||
*)
|
||||
log::error "Unknown flag: $1"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
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 ;;
|
||||
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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue