test: add tests for all new features, fix bugs found by tests
- integration: logs query flags, hosts command, peer command sections - unit: fmt::bytes, config::dns_string, parse_since, ui::group::status - destructive: duplicate rule validation, peer update-dns/tunnel - fix: config::allowed_ips_for used $2 instead of $1 - fix: identity rule assign exit_code unbound variable - fix: ctx::identity → ctx::identities in peers::get_identity - fix: peers::get_identity restored (needed for rule assign duplicate check) - rule assign: blocks if rule already in peer's identity via peers::get_identity - identity rule assign: --migrate removes conflicting direct peer rules
This commit is contained in:
parent
794e75bc9b
commit
cf71e9f51a
9 changed files with 256 additions and 7 deletions
|
|
@ -397,7 +397,7 @@ function cmd::identity::_rule_assign() {
|
|||
done
|
||||
fi
|
||||
|
||||
local exit_code
|
||||
local exit_code=0
|
||||
identity::add_rule "$name" "$rule" || exit_code=$?
|
||||
|
||||
if [[ $exit_code -eq 2 ]]; then
|
||||
|
|
|
|||
|
|
@ -187,7 +187,8 @@ function cmd::logs::show() {
|
|||
"$filter_ip" "$name" "$type" "$limit" \
|
||||
"$collapse" "$since" "$filter_event")
|
||||
|
||||
if [[ -z "${fw_output// /}" && -z "${wg_output// /}" ]]; then
|
||||
if [[ -z "$(echo "$fw_output" | tr -d '[:space:]')" && \
|
||||
-z "$(echo "$wg_output" | tr -d '[:space:]')" ]]; then
|
||||
log::wg_warning "No logs found"
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -554,7 +554,7 @@ function cmd::rule::assign() {
|
|||
# Identity rule check
|
||||
local peer_identity
|
||||
|
||||
peer_identity=$(peers::get_meta "$peer" "identity")
|
||||
peer_identity=$(peers::get_identity "$peer")
|
||||
if [[ -n "$peer_identity" ]]; then
|
||||
local identity_rules
|
||||
identity_rules=$(identity::rules "$peer_identity" 2>/dev/null)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ function cmd::test::section_destructive() {
|
|||
cmd::test::_destructive_groups
|
||||
cmd::test::_destructive_identity
|
||||
cmd::test::_destructive_cleanup
|
||||
cmd::test::_destructive_rule_duplicate
|
||||
cmd::test::_destructive_peer_dns
|
||||
}
|
||||
|
||||
function cmd::test::_destructive_peer() {
|
||||
|
|
@ -121,6 +123,67 @@ function cmd::test::_destructive_identity() {
|
|||
identity show --name testunit2b
|
||||
}
|
||||
|
||||
function cmd::test::_destructive_rule_duplicate() {
|
||||
# Cleanup from any previous failed run
|
||||
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" identity rule unassign --name testunit --all > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
|
||||
|
||||
# Assign admin to identity
|
||||
"$WGCTL_BINARY" identity rule assign --name testunit --rule admin > /dev/null 2>&1 || true
|
||||
|
||||
# Try to assign admin directly — should fail (identity has it)
|
||||
cmd::test::run_cmd_fails "rule assign blocked by identity rule" \
|
||||
rule assign --name admin --peer phone-testunit
|
||||
|
||||
# Remove admin from identity first so we can assign user directly to peer
|
||||
"$WGCTL_BINARY" identity rule unassign --name testunit --rule admin > /dev/null 2>&1 || true
|
||||
|
||||
# Assign user directly to peer
|
||||
"$WGCTL_BINARY" rule assign --name user --peer phone-testunit > /dev/null 2>&1 || true
|
||||
|
||||
# Now assign user to identity with --migrate — should remove from peer
|
||||
cmd::test::run_cmd "identity rule assign --migrate" "Migrated" \
|
||||
identity rule assign --name testunit --rule user --migrate
|
||||
|
||||
# Cleanup
|
||||
"$WGCTL_BINARY" identity rule unassign --name testunit --all > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
function cmd::test::_destructive_peer_dns() {
|
||||
test::section "Destructive: peer update-dns"
|
||||
|
||||
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
|
||||
|
||||
# Update DNS and verify it's in the conf
|
||||
cmd::test::run_cmd "peer update-dns single peer" "Updated DNS" \
|
||||
peer update-dns --name phone-testunit --fallback-dns "9.9.9.9"
|
||||
|
||||
local conf_dns
|
||||
conf_dns=$(grep "^DNS" /etc/wireguard/clients/phone-testunit.conf 2>/dev/null)
|
||||
[[ "$conf_dns" == *"9.9.9.9"* ]] && \
|
||||
test::pass "DNS line contains fallback" || \
|
||||
test::fail "DNS line missing fallback (got: $conf_dns)"
|
||||
|
||||
# Update tunnel mode
|
||||
cmd::test::run_cmd "peer update-tunnel to full" "Updated" \
|
||||
peer update-tunnel --name phone-testunit --mode full
|
||||
|
||||
local conf_allowed
|
||||
conf_allowed=$(grep "^AllowedIPs" /etc/wireguard/clients/phone-testunit.conf 2>/dev/null)
|
||||
[[ "$conf_allowed" == *"0.0.0.0/0"* ]] && \
|
||||
test::pass "AllowedIPs set to full tunnel" || \
|
||||
test::fail "AllowedIPs not full tunnel (got: $conf_allowed)"
|
||||
|
||||
cmd::test::run_cmd "peer update-tunnel to split" "Updated" \
|
||||
peer update-tunnel --name phone-testunit --mode split
|
||||
|
||||
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
|
||||
function cmd::test::_destructive_cleanup() {
|
||||
cmd::test::run_cmd "remove phone peer" "removed" \
|
||||
remove --name phone-testunit --force
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ function cmd::test::run_all_integration_sections() {
|
|||
cmd::test::section_net
|
||||
cmd::test::section_subnet
|
||||
cmd::test::section_identity
|
||||
cmd::test::section_hosts
|
||||
cmd::test::section_peer_cmd
|
||||
}
|
||||
|
||||
function cmd::test::section_list() {
|
||||
|
|
@ -189,6 +191,11 @@ function cmd::test::section_logs() {
|
|||
cmd::test::run_cmd "logs --name phone-nuno" "Activity" logs --name phone-nuno
|
||||
cmd::test::run_cmd "logs --fw" "Firewall Drops" logs --fw
|
||||
cmd::test::run_cmd "logs --wg" "WireGuard Events" logs --wg
|
||||
cmd::test::run_cmd "logs --since 2099-01-01" "No logs" logs --since "2099-01-01"
|
||||
cmd::test::run_cmd "logs --wg --since 2099-01-01" "No logs" logs --wg --since "2099-01-01"
|
||||
cmd::test::run_cmd "logs --fw --since 2099-01-01" "No logs" logs --fw --since "2099-01-01"
|
||||
cmd::test::run_cmd "logs --wg --event attempt" "" logs --wg --event attempt
|
||||
cmd::test::run_cmd "logs --detailed" "" logs --detailed
|
||||
}
|
||||
|
||||
function cmd::test::section_fw() {
|
||||
|
|
@ -250,3 +257,46 @@ function cmd::test::section_identity() {
|
|||
cmd::test::run_cmd "identity show nuno" "nuno" identity show --name nuno
|
||||
cmd::test::run_cmd_fails "identity show nonexistent" identity show --name nonexistent
|
||||
}
|
||||
|
||||
function cmd::test::section_hosts() {
|
||||
test::section "Hosts"
|
||||
|
||||
# Cleanup
|
||||
"$WGCTL_BINARY" hosts rm --ip 192.0.2.1 --force > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" hosts rm --port 9999 --force > /dev/null 2>&1 || true
|
||||
|
||||
cmd::test::run_cmd "hosts list" "Hosts" hosts list
|
||||
cmd::test::run_cmd "hosts add --ip" "Added" hosts add --ip 192.0.2.1 --name test-host --desc "Test" --tags test,unit
|
||||
cmd::test::run_cmd "hosts list shows new" "test-host" hosts list
|
||||
cmd::test::run_cmd "hosts show --ip" "Name" hosts show --ip 192.0.2.1
|
||||
cmd::test::run_cmd "hosts add --port" "Added" hosts add --port 9999 --name test-port
|
||||
cmd::test::run_cmd "hosts list shows port" "test-port" hosts list
|
||||
cmd::test::run_cmd "hosts rm --ip" "Removed" hosts rm --ip 192.0.2.1 --force
|
||||
cmd::test::run_cmd "hosts rm --port" "Removed" hosts rm --port 9999 --force
|
||||
cmd::test::run_cmd_fails "hosts show nonexistent" hosts show --ip 192.0.2.99
|
||||
cmd::test::run_cmd_fails "hosts add missing --name" hosts add --ip 192.0.2.1
|
||||
}
|
||||
|
||||
function cmd::test::section_peer_cmd() {
|
||||
test::section "Peer command"
|
||||
|
||||
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
|
||||
|
||||
# update-dns
|
||||
cmd::test::run_cmd "peer update-dns --name" "Updated DNS" peer update-dns --name phone-testunit
|
||||
cmd::test::run_cmd "peer update-dns applies" "10.0.0.103" config --name phone-testunit
|
||||
cmd::test::run_cmd "peer update-dns --all" "peer(s)" peer update-dns --all
|
||||
|
||||
# update-tunnel
|
||||
cmd::test::run_cmd "peer update-tunnel split" "split" peer update-tunnel --name phone-testunit --mode split
|
||||
cmd::test::run_cmd "peer update-tunnel full" "Updated" peer update-tunnel --name phone-testunit --mode full
|
||||
cmd::test::run_cmd_fails "peer update-tunnel bad mode" peer update-tunnel --name phone-testunit --mode invalid
|
||||
cmd::test::run_cmd_fails "peer update-tunnel missing --mode" peer update-tunnel --name phone-testunit
|
||||
cmd::test::run_cmd_fails "peer update-tunnel missing --name" peer update-tunnel --mode split
|
||||
|
||||
# Restore split tunnel
|
||||
"$WGCTL_BINARY" peer update-tunnel --name phone-testunit --mode split > /dev/null 2>&1 || true
|
||||
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +44,10 @@ function cmd::test::run_all_unit_sections() {
|
|||
cmd::test::unit_subnet
|
||||
cmd::test::unit_ip
|
||||
cmd::test::unit_identity
|
||||
cmd::test::unit_fmt
|
||||
cmd::test::unit_config
|
||||
cmd::test::unit_parse_since
|
||||
cmd::test::unit_group_status
|
||||
}
|
||||
|
||||
function cmd::test::unit_subnet() {
|
||||
|
|
@ -103,3 +107,116 @@ function cmd::test::unit_identity() {
|
|||
cmd::test::assert "infer no convention" "$(identity::infer 'roboclean')" ""
|
||||
cmd::test::assert "infer guest-zephyr" "$(identity::infer 'guest-zephyr')" ""
|
||||
}
|
||||
|
||||
function cmd::test::unit_fmt() {
|
||||
test::section "Unit: fmt::bytes"
|
||||
|
||||
cmd::test::assert "fmt::bytes 0" "$(fmt::bytes 0)" "—"
|
||||
cmd::test::assert "fmt::bytes bytes" "$(fmt::bytes 512)" "512B"
|
||||
cmd::test::assert "fmt::bytes KB" "$(fmt::bytes 2048)" "2KB"
|
||||
cmd::test::assert "fmt::bytes MB" "$(fmt::bytes 2097152)" "2MB"
|
||||
cmd::test::assert "fmt::bytes GB" "$(fmt::bytes 2147483648)" "2GB"
|
||||
cmd::test::assert "fmt::bytes 1023" "$(fmt::bytes 1023)" "1023B"
|
||||
cmd::test::assert "fmt::bytes 1024" "$(fmt::bytes 1024)" "1KB"
|
||||
}
|
||||
|
||||
function cmd::test::unit_config() {
|
||||
test::section "Unit: config dns"
|
||||
|
||||
# config::dns_string with no fallback
|
||||
local orig_fallback="${_WG_DNS_FALLBACK:-}"
|
||||
_WG_DNS_FALLBACK=""
|
||||
cmd::test::assert "dns_string no fallback" \
|
||||
"$(config::dns_string)" "$(config::dns)"
|
||||
|
||||
# config::dns_string with fallback
|
||||
_WG_DNS_FALLBACK="9.9.9.9"
|
||||
cmd::test::assert "dns_string with fallback" \
|
||||
"$(config::dns_string)" "$(config::dns), 9.9.9.9"
|
||||
|
||||
# config::dns_string with multiple fallbacks
|
||||
_WG_DNS_FALLBACK="9.9.9.9,1.1.1.1"
|
||||
cmd::test::assert "dns_string multi fallback" \
|
||||
"$(config::dns_string)" "$(config::dns), 9.9.9.9,1.1.1.1"
|
||||
|
||||
# Restore
|
||||
_WG_DNS_FALLBACK="$orig_fallback"
|
||||
}
|
||||
|
||||
function cmd::test::unit_parse_since() {
|
||||
test::section "Unit: parse_since (Python)"
|
||||
|
||||
# Test via Python directly
|
||||
local py_result
|
||||
|
||||
# Relative formats
|
||||
py_result=$(python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '/etc/wireguard/wgctl/core/lib')
|
||||
from util import parse_since
|
||||
from datetime import datetime, timezone, timedelta
|
||||
dt = parse_since('2h')
|
||||
now = datetime.now(timezone.utc)
|
||||
diff = abs((now - dt).total_seconds() - 7200)
|
||||
print('ok' if diff < 5 else f'fail: {diff}')
|
||||
")
|
||||
cmd::test::assert "parse_since 2h" "$py_result" "ok"
|
||||
|
||||
py_result=$(python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '/etc/wireguard/wgctl/core/lib')
|
||||
from util import parse_since
|
||||
dt = parse_since('7d')
|
||||
print('ok' if dt is not None else 'fail')
|
||||
")
|
||||
cmd::test::assert "parse_since 7d" "$py_result" "ok"
|
||||
|
||||
# EU date format
|
||||
py_result=$(python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '/etc/wireguard/wgctl/core/lib')
|
||||
from util import parse_since
|
||||
from datetime import datetime
|
||||
dt = parse_since('23/05')
|
||||
print('ok' if dt is not None and dt.day == 23 and dt.month == 5 else f'fail: {dt}')
|
||||
")
|
||||
cmd::test::assert "parse_since 23/05" "$py_result" "ok"
|
||||
|
||||
# ISO format
|
||||
py_result=$(python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '/etc/wireguard/wgctl/core/lib')
|
||||
from util import parse_since
|
||||
dt = parse_since('2026-05-23')
|
||||
print('ok' if dt is not None and dt.year == 2026 and dt.month == 5 and dt.day == 23 else f'fail: {dt}')
|
||||
")
|
||||
cmd::test::assert "parse_since ISO" "$py_result" "ok"
|
||||
|
||||
# Invalid
|
||||
py_result=$(python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '/etc/wireguard/wgctl/core/lib')
|
||||
from util import parse_since
|
||||
dt = parse_since('not-a-date')
|
||||
print('ok' if dt is None else f'fail: {dt}')
|
||||
")
|
||||
cmd::test::assert "parse_since invalid" "$py_result" "ok"
|
||||
}
|
||||
|
||||
function cmd::test::unit_group_status() {
|
||||
test::section "Unit: ui::group::status"
|
||||
load_module ui
|
||||
|
||||
local color str
|
||||
IFS='|' read -r color str <<< "$(ui::group::status 0 0)"
|
||||
cmd::test::assert "group status inactive str" "$str" "inactive"
|
||||
|
||||
IFS='|' read -r color str <<< "$(ui::group::status 4 0)"
|
||||
cmd::test::assert "group status active str" "$str" "active"
|
||||
|
||||
IFS='|' read -r color str <<< "$(ui::group::status 4 4)"
|
||||
cmd::test::assert "group status blocked str" "$str" "blocked"
|
||||
|
||||
IFS='|' read -r color str <<< "$(ui::group::status 4 2)"
|
||||
cmd::test::assert "group status partial str" "$str" "partial (2/4)"
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"phone-fred": "176.223.61.130",
|
||||
"phone-helena": "148.69.46.73",
|
||||
"phone-nuno": "94.63.0.129",
|
||||
"phone-nuno": "148.69.50.62",
|
||||
"tablet-nuno": "148.69.202.5",
|
||||
"guest-zephyr": "86.120.152.74",
|
||||
"guest-zephyr-test": "94.63.0.129",
|
||||
"desktop-roboclean": "46.189.215.231",
|
||||
"laptop-nuno": "94.63.0.129",
|
||||
"phone-luis": "176.223.61.15",
|
||||
"phone-helena-2": "148.69.202.127",
|
||||
"phone-helena-2": "148.69.203.225",
|
||||
"desktop-zephyr": "86.120.152.74"
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ function config::activity_current_high() { echo "$_ACTIVITY_TOTAL_HIGH_BYTES";
|
|||
function config::server_public_key() { cat "$_WG_SERVER_PUBLIC_KEY_FILE"; }
|
||||
|
||||
function config::allowed_ips_for() {
|
||||
local tunnel="${2:-split}"
|
||||
local tunnel="${1:-split}"
|
||||
case "$tunnel" in
|
||||
full) echo "$_WG_TUNNEL_FULL" ;;
|
||||
split) echo "$_WG_TUNNEL_SPLIT" ;;
|
||||
|
|
|
|||
|
|
@ -538,6 +538,24 @@ function peers::format_activity_current() {
|
|||
echo "${level} (↓${rx_hr}B/s ↑${tx_hr}B/s)"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# dentity
|
||||
# ============================================
|
||||
|
||||
function peers::get_identity() {
|
||||
local peer_name="${1:-}"
|
||||
local id_dir
|
||||
id_dir="$(ctx::identities)"
|
||||
for id_file in "${id_dir}"/*.identity; do
|
||||
[[ -f "$id_file" ]] || continue
|
||||
if json::get "$id_file" "peers" 2>/dev/null | grep -qx "$peer_name"; then
|
||||
basename "$id_file" .identity
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Helpers - Meta File
|
||||
# ============================================
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue