integration.sh
· 8.9 KiB · Bash
Исходник
#!/usr/bin/env bash
# test/integration.sh — integration test sections
# Tests run against the live wgctl binary.
# Sourced by test.command.sh — do not execute directly.
WGCTL_BINARY="$(command -v wgctl)"
# ============================================
# Helpers
# ============================================
function cmd::test::_strip_ansi() {
sed 's/\x1b\[[0-9;]*m//g'
}
function cmd::test::run_cmd() {
local desc="$1" expected="${2:-}"
shift 2
local tmp exit_code
tmp=$(mktemp)
set +e
timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
exit_code=$?
set -e
# Reset terminal color in case command output left ANSI state dirty
printf "\033[0m" >&2
if [[ $exit_code -eq 124 ]]; then
test::warn "${desc} (timed out after 30s)"
rm -f "$tmp"
return 1
fi
local clean
clean=$(cmd::test::_strip_ansi < "$tmp")
if [[ $exit_code -ne 0 ]]; then
local msg="${desc}"
[[ -n "$expected" ]] && msg="${desc} (expected '${expected}', command failed)"
test::fail "$msg"
if [[ "${WGCTL_TEST_VERBOSE:-false}" == "true" ]]; then
printf " Output: %s\n" "$(echo "$clean" | head -3 | tr '\n' ' ')"
fi
rm -f "$tmp"
return 1
fi
if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then
local actual
actual=$(echo "$clean" | head -3 | 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_any() {
local desc="$1" expected="${2:-}"
shift 2
local tmp
tmp=$(mktemp)
set +e
timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
set -e
printf "\033[0m" >&2
local clean
clean=$(cmd::test::_strip_ansi < "$tmp")
if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then
local actual
actual=$(echo "$clean" | head -3 | 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
local tmp exit_code
tmp=$(mktemp)
set +e
timeout 10 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
exit_code=$?
set -e
printf "\033[0m" >&2
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"
}
# ============================================
# Sections
# ============================================
function cmd::test::run_all_integration_sections() {
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
cmd::test::section_net
cmd::test::section_subnet
cmd::test::section_identity
}
function cmd::test::section_list() {
test::section "List"
cmd::test::run_cmd "list" "rule:" 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" "phone" list --type phone
cmd::test::run_cmd "list --detailed" "rule:" 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" 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" "user" 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_any "audit" "passed" audit
cmd::test::run_cmd_any "audit --peer phone-nuno" "passed" audit --peer phone-nuno
cmd::test::run_cmd_any "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 --fw" "Firewall Drops" logs --fw
cmd::test::run_cmd "logs --wg" "WireGuard Events" 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_subnet() {
test::section "Subnet"
"$WGCTL_BINARY" subnet rm --name test-subnet-2 > /dev/null 2>&1 || true
"$WGCTL_BINARY" subnet rm --name test-subnet > /dev/null 2>&1 || true
cmd::test::run_cmd "subnet list" "desktop" subnet list
cmd::test::run_cmd "subnet show desktop" "tunnel:" subnet show --name desktop
cmd::test::run_cmd "subnet show guests group" "guests" subnet show --name guests
cmd::test::run_cmd_fails "subnet show nonexistent" subnet show --name nonexistent
cmd::test::run_cmd "subnet add" "added" \
subnet add --name test-subnet --subnet 10.1.250.0/24 --type iot --desc "Test"
cmd::test::run_cmd "subnet list shows new" "test-subnet" \
subnet list
cmd::test::run_cmd_fails "subnet rename in-use (desktop)" \
subnet rename --name desktop --new-name workstation
cmd::test::run_cmd "subnet rename unused" "renamed" \
subnet rename --name test-subnet --new-name test-subnet-2
cmd::test::run_cmd "subnet rm" "removed" \
subnet rm --name test-subnet-2
cmd::test::run_cmd_fails "subnet rm nonexistent" \
subnet rm --name nonexistent-subnet
}
function cmd::test::section_identity() {
test::section "Identity"
cmd::test::run_cmd "identity list" "" identity list
cmd::test::run_cmd "identity migrate --dry-run" "Dry run" identity migrate --dry-run
cmd::test::run_cmd "identity show nuno" "nuno" identity show --name nuno
cmd::test::run_cmd_fails "identity show nonexistent" identity show --name nonexistent
}
| 1 | #!/usr/bin/env bash |
| 2 | # test/integration.sh — integration test sections |
| 3 | # Tests run against the live wgctl binary. |
| 4 | # Sourced by test.command.sh — do not execute directly. |
| 5 | |
| 6 | WGCTL_BINARY="$(command -v wgctl)" |
| 7 | |
| 8 | # ============================================ |
| 9 | # Helpers |
| 10 | # ============================================ |
| 11 | |
| 12 | function cmd::test::_strip_ansi() { |
| 13 | sed 's/\x1b\[[0-9;]*m//g' |
| 14 | } |
| 15 | |
| 16 | function cmd::test::run_cmd() { |
| 17 | local desc="$1" expected="${2:-}" |
| 18 | shift 2 |
| 19 | |
| 20 | local tmp exit_code |
| 21 | tmp=$(mktemp) |
| 22 | |
| 23 | set +e |
| 24 | timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1 |
| 25 | exit_code=$? |
| 26 | set -e |
| 27 | |
| 28 | # Reset terminal color in case command output left ANSI state dirty |
| 29 | printf "\033[0m" >&2 |
| 30 | |
| 31 | if [[ $exit_code -eq 124 ]]; then |
| 32 | test::warn "${desc} (timed out after 30s)" |
| 33 | rm -f "$tmp" |
| 34 | return 1 |
| 35 | fi |
| 36 | |
| 37 | local clean |
| 38 | clean=$(cmd::test::_strip_ansi < "$tmp") |
| 39 | |
| 40 | if [[ $exit_code -ne 0 ]]; then |
| 41 | local msg="${desc}" |
| 42 | [[ -n "$expected" ]] && msg="${desc} (expected '${expected}', command failed)" |
| 43 | test::fail "$msg" |
| 44 | if [[ "${WGCTL_TEST_VERBOSE:-false}" == "true" ]]; then |
| 45 | printf " Output: %s\n" "$(echo "$clean" | head -3 | tr '\n' ' ')" |
| 46 | fi |
| 47 | rm -f "$tmp" |
| 48 | return 1 |
| 49 | fi |
| 50 | |
| 51 | if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then |
| 52 | local actual |
| 53 | actual=$(echo "$clean" | head -3 | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-100) |
| 54 | test::fail "${desc} (expected '${expected}', got: '${actual}')" |
| 55 | rm -f "$tmp" |
| 56 | return 1 |
| 57 | fi |
| 58 | |
| 59 | test::pass "$desc" |
| 60 | rm -f "$tmp" |
| 61 | } |
| 62 | |
| 63 | function cmd::test::run_cmd_any() { |
| 64 | local desc="$1" expected="${2:-}" |
| 65 | shift 2 |
| 66 | |
| 67 | local tmp |
| 68 | tmp=$(mktemp) |
| 69 | |
| 70 | set +e |
| 71 | timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1 |
| 72 | set -e |
| 73 | |
| 74 | printf "\033[0m" >&2 |
| 75 | |
| 76 | local clean |
| 77 | clean=$(cmd::test::_strip_ansi < "$tmp") |
| 78 | |
| 79 | if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then |
| 80 | local actual |
| 81 | actual=$(echo "$clean" | head -3 | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-100) |
| 82 | test::fail "${desc} (expected '${expected}', got: '${actual}')" |
| 83 | rm -f "$tmp" |
| 84 | return 1 |
| 85 | fi |
| 86 | |
| 87 | test::pass "$desc" |
| 88 | rm -f "$tmp" |
| 89 | } |
| 90 | |
| 91 | function cmd::test::run_cmd_fails() { |
| 92 | local desc="$1" |
| 93 | shift |
| 94 | |
| 95 | local tmp exit_code |
| 96 | tmp=$(mktemp) |
| 97 | |
| 98 | set +e |
| 99 | timeout 10 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1 |
| 100 | exit_code=$? |
| 101 | set -e |
| 102 | |
| 103 | printf "\033[0m" >&2 |
| 104 | rm -f "$tmp" |
| 105 | |
| 106 | if [[ $exit_code -eq 124 ]]; then |
| 107 | test::warn "${desc} (timed out)" |
| 108 | return 1 |
| 109 | fi |
| 110 | |
| 111 | if [[ $exit_code -eq 0 ]]; then |
| 112 | test::fail "${desc} (expected failure but succeeded)" |
| 113 | return 1 |
| 114 | fi |
| 115 | |
| 116 | test::pass "$desc" |
| 117 | } |
| 118 | |
| 119 | # ============================================ |
| 120 | # Sections |
| 121 | # ============================================ |
| 122 | |
| 123 | function cmd::test::run_all_integration_sections() { |
| 124 | cmd::test::section_list |
| 125 | cmd::test::section_inspect |
| 126 | cmd::test::section_config |
| 127 | cmd::test::section_rules |
| 128 | cmd::test::section_groups |
| 129 | cmd::test::section_audit |
| 130 | cmd::test::section_logs |
| 131 | cmd::test::section_fw |
| 132 | cmd::test::section_net |
| 133 | cmd::test::section_subnet |
| 134 | cmd::test::section_identity |
| 135 | } |
| 136 | |
| 137 | function cmd::test::section_list() { |
| 138 | test::section "List" |
| 139 | cmd::test::run_cmd "list" "rule:" list |
| 140 | cmd::test::run_cmd "list --online" "" list --online |
| 141 | cmd::test::run_cmd "list --offline" "" list --offline |
| 142 | cmd::test::run_cmd "list --blocked" "" list --blocked |
| 143 | cmd::test::run_cmd "list --type phone" "phone" list --type phone |
| 144 | cmd::test::run_cmd "list --detailed" "rule:" list --detailed |
| 145 | cmd::test::run_cmd "list --name phone-nuno" "phone-nuno" list --name phone-nuno |
| 146 | } |
| 147 | |
| 148 | function cmd::test::section_inspect() { |
| 149 | test::section "Inspect" |
| 150 | cmd::test::run_cmd "inspect --name phone-nuno" "IP:" inspect --name phone-nuno |
| 151 | cmd::test::run_cmd "inspect --name nuno --type phone" "IP:" inspect --name nuno --type phone |
| 152 | cmd::test::run_cmd "inspect --name phone-nuno --config" "PrivateKey" inspect --name phone-nuno --config |
| 153 | cmd::test::run_cmd_fails "inspect nonexistent" inspect --name nonexistent-peer |
| 154 | } |
| 155 | |
| 156 | function cmd::test::section_config() { |
| 157 | test::section "Config & QR" |
| 158 | cmd::test::run_cmd "config --name phone-nuno" "PrivateKey" config --name phone-nuno |
| 159 | cmd::test::run_cmd "config --name nuno --type phone" "PrivateKey" config --name nuno --type phone |
| 160 | cmd::test::run_cmd "qr --name phone-nuno" "" qr --name phone-nuno |
| 161 | } |
| 162 | |
| 163 | function cmd::test::section_rules() { |
| 164 | test::section "Rules" |
| 165 | cmd::test::run_cmd "rule list" "user" rule list |
| 166 | cmd::test::run_cmd "rule show --name guest" "Description" rule show --name guest |
| 167 | cmd::test::run_cmd "rule show --name user" "Description" rule show --name user |
| 168 | cmd::test::run_cmd "rule show --name admin" "Description" rule show --name admin |
| 169 | cmd::test::run_cmd_fails "rule show nonexistent" rule show --name nonexistent |
| 170 | } |
| 171 | |
| 172 | function cmd::test::section_groups() { |
| 173 | test::section "Groups" |
| 174 | cmd::test::run_cmd "group list" "Groups" group list |
| 175 | cmd::test::run_cmd "group show --name family" "Peers:" group show --name family |
| 176 | cmd::test::run_cmd_fails "group show nonexistent" group show --name nonexistent |
| 177 | } |
| 178 | |
| 179 | function cmd::test::section_audit() { |
| 180 | test::section "Audit" |
| 181 | cmd::test::run_cmd_any "audit" "passed" audit |
| 182 | cmd::test::run_cmd_any "audit --peer phone-nuno" "passed" audit --peer phone-nuno |
| 183 | cmd::test::run_cmd_any "audit --type phone" "passed" audit --type phone |
| 184 | } |
| 185 | |
| 186 | function cmd::test::section_logs() { |
| 187 | test::section "Logs" |
| 188 | cmd::test::run_cmd "logs" "Activity" logs |
| 189 | cmd::test::run_cmd "logs --name phone-nuno" "Activity" logs --name phone-nuno |
| 190 | cmd::test::run_cmd "logs --fw" "Firewall Drops" logs --fw |
| 191 | cmd::test::run_cmd "logs --wg" "WireGuard Events" logs --wg |
| 192 | } |
| 193 | |
| 194 | function cmd::test::section_fw() { |
| 195 | test::section "Firewall" |
| 196 | cmd::test::run_cmd "fw list" "FORWARD" fw list |
| 197 | cmd::test::run_cmd "fw list --peer phone-nuno" "" fw list --peer phone-nuno |
| 198 | cmd::test::run_cmd "fw list --no-nflog" "" fw list --no-nflog |
| 199 | cmd::test::run_cmd "fw list --no-accept" "" fw list --no-accept |
| 200 | cmd::test::run_cmd "fw list --no-drop" "" fw list --no-drop |
| 201 | cmd::test::run_cmd "fw nat" "PREROUTING" fw nat |
| 202 | cmd::test::run_cmd "fw count" "TOTAL" fw count |
| 203 | } |
| 204 | |
| 205 | function cmd::test::section_net() { |
| 206 | test::section "Net" |
| 207 | "$WGCTL_BINARY" net rm --name test-svc --force > /dev/null 2>&1 || true |
| 208 | |
| 209 | cmd::test::run_cmd "net add service" "added" net add --name test-svc --ip 10.0.0.99 --desc "Test service" |
| 210 | cmd::test::run_cmd "net add port" "Added" net add --name test-svc:web --port 9999:tcp |
| 211 | cmd::test::run_cmd "net list" "test-svc" net list |
| 212 | cmd::test::run_cmd "net list --detailed" "web" net list --detailed |
| 213 | cmd::test::run_cmd "net show" "9999" net show --name test-svc |
| 214 | cmd::test::run_cmd "net rm port" "Removed" net rm --name test-svc:web --force |
| 215 | cmd::test::run_cmd "net add port again" "Added" net add --name test-svc:web --port 9999:tcp |
| 216 | cmd::test::run_cmd "net rm all ports" "Removed" net rm --name test-svc:ports --force |
| 217 | cmd::test::run_cmd "net rm service" "Removed" net rm --name test-svc --force |
| 218 | cmd::test::run_cmd_fails "net show nonexistent" net show --name nonexistent-svc |
| 219 | cmd::test::run_cmd_fails "net add port no service" net add --name nonexistent:web --port 80:tcp |
| 220 | } |
| 221 | |
| 222 | function cmd::test::section_subnet() { |
| 223 | test::section "Subnet" |
| 224 | "$WGCTL_BINARY" subnet rm --name test-subnet-2 > /dev/null 2>&1 || true |
| 225 | "$WGCTL_BINARY" subnet rm --name test-subnet > /dev/null 2>&1 || true |
| 226 | |
| 227 | cmd::test::run_cmd "subnet list" "desktop" subnet list |
| 228 | cmd::test::run_cmd "subnet show desktop" "tunnel:" subnet show --name desktop |
| 229 | cmd::test::run_cmd "subnet show guests group" "guests" subnet show --name guests |
| 230 | cmd::test::run_cmd_fails "subnet show nonexistent" subnet show --name nonexistent |
| 231 | |
| 232 | cmd::test::run_cmd "subnet add" "added" \ |
| 233 | subnet add --name test-subnet --subnet 10.1.250.0/24 --type iot --desc "Test" |
| 234 | cmd::test::run_cmd "subnet list shows new" "test-subnet" \ |
| 235 | subnet list |
| 236 | cmd::test::run_cmd_fails "subnet rename in-use (desktop)" \ |
| 237 | subnet rename --name desktop --new-name workstation |
| 238 | cmd::test::run_cmd "subnet rename unused" "renamed" \ |
| 239 | subnet rename --name test-subnet --new-name test-subnet-2 |
| 240 | cmd::test::run_cmd "subnet rm" "removed" \ |
| 241 | subnet rm --name test-subnet-2 |
| 242 | cmd::test::run_cmd_fails "subnet rm nonexistent" \ |
| 243 | subnet rm --name nonexistent-subnet |
| 244 | } |
| 245 | |
| 246 | function cmd::test::section_identity() { |
| 247 | test::section "Identity" |
| 248 | cmd::test::run_cmd "identity list" "" identity list |
| 249 | cmd::test::run_cmd "identity migrate --dry-run" "Dry run" identity migrate --dry-run |
| 250 | cmd::test::run_cmd "identity show nuno" "nuno" identity show --name nuno |
| 251 | cmd::test::run_cmd_fails "identity show nonexistent" identity show --name nonexistent |
| 252 | } |