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")
|
ip=$(peers::get_ip "$peer")
|
||||||
[[ -z "$ip" ]] && log::error "Peer not found: $peer" && return 1
|
[[ -z "$ip" ]] && log::error "Peer not found: $peer" && return 1
|
||||||
iptables -L FORWARD -n -v | grep -F "$ip" \
|
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
|
elif [[ -n "$type" ]]; then
|
||||||
local subnet
|
local subnet
|
||||||
subnet=$(config::subnet_for "$type")
|
subnet=$(config::subnet_for "$type")
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,18 @@ function cmd::list::_is_attempting() {
|
||||||
local now attempt_ts diff
|
local now attempt_ts diff
|
||||||
now=$(date +%s)
|
now=$(date +%s)
|
||||||
attempt_ts=$(json::iso_to_ts "$last_ts")
|
attempt_ts=$(json::iso_to_ts "$last_ts")
|
||||||
|
[[ -z "$attempt_ts" || "$attempt_ts" == "0" ]] && return 1
|
||||||
diff=$(( now - attempt_ts ))
|
diff=$(( now - attempt_ts ))
|
||||||
(( diff < 180 ))
|
(( diff < 180 ))
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmd::list::_format_last_seen() {
|
function cmd::list::_format_last_seen() {
|
||||||
local name="$1" pubkey="$2" is_blocked="$3"
|
local name="$1"
|
||||||
local last_ts="$4" last_evt="$5" handshake_ts="$6"
|
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 [[ "$is_blocked" == "true" ]]; then
|
||||||
if [[ -n "$last_ts" ]]; then
|
if [[ -n "$last_ts" ]]; then
|
||||||
|
|
@ -133,10 +138,12 @@ function cmd::list::_format_last_seen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmd::list::_format_status() {
|
function cmd::list::_format_status() {
|
||||||
local name="$1" pubkey="$2"
|
local name="$1"
|
||||||
local is_blocked="$3" is_restricted="$4"
|
local pubkey="$2"
|
||||||
local handshake_ts="$5" last_ts="$6"
|
local is_blocked="${3:-false}"
|
||||||
|
local is_restricted="${4:-false}"
|
||||||
|
local handshake_ts="${5:-0}"
|
||||||
|
local last_ts="${6:-}"
|
||||||
local connected=false modifier="" color
|
local connected=false modifier="" color
|
||||||
|
|
||||||
if [[ "$is_blocked" == "true" ]]; then
|
if [[ "$is_blocked" == "true" ]]; then
|
||||||
|
|
@ -177,7 +184,9 @@ function cmd::list::_get_type() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmd::list::display_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
|
if config::is_guest_type "$type" && [[ -n "$subtype" ]]; then
|
||||||
echo "guest/${subtype}"
|
echo "guest/${subtype}"
|
||||||
elif config::is_guest_type "$type"; then
|
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