diff --git a/commands/inspect.command.sh b/commands/inspect.command.sh index 22186eb..7f8cacf 100644 --- a/commands/inspect.command.sh +++ b/commands/inspect.command.sh @@ -5,7 +5,8 @@ function cmd::inspect::on_load() { flag::register --type flag::register --config flag::register --qr - flag::register --json + + command::mixin json_output } function cmd::inspect::help() { @@ -313,7 +314,6 @@ function cmd::inspect::run() { --type) type="$2"; shift 2 ;; --config) show_config=true; shift ;; --qr) show_qr=true; shift ;; - --json) json_output=true; shift ;; --help) cmd::inspect::help; return ;; *) log::error "Unknown flag: $1" @@ -331,7 +331,7 @@ function cmd::inspect::run() { name=$(peers::resolve_and_require "$name" "$type") || return 1 - if $json_output; then + if command::json; then cmd::inspect::_output_json "$name" return 0 fi @@ -372,7 +372,7 @@ function cmd::inspect::_output_json() { type=$(peers::get_type "$name") rule=$(peers::get_meta "$name" "rule") allowed_ips=$(grep "^AllowedIPs" "$(ctx::clients)/${name}.conf" 2>/dev/null | \ - awk '{print $3}') + awk '{print $3}' | tr -d ',') public_key=$(keys::public "$name" 2>/dev/null || echo "") peers::is_blocked "$name" && is_blocked="true" || is_blocked="false" diff --git a/commands/list.command.sh b/commands/list.command.sh index 65fc2fe..e4779f4 100644 --- a/commands/list.command.sh +++ b/commands/list.command.sh @@ -685,7 +685,7 @@ function cmd::list::_output_json() { local peer_json peer_json=$(printf '{"name":"%s","ip":"%s","type":"%s","rule":"%s","group":"%s","status":"%s","last_seen":"%s","is_blocked":%s,"is_restricted":%s}' \ "$name" "$ip" "$type" \ - "${rule//-/}" "${group//-/}" \ + "${rule}" "${group}" \ "$status" "$last_seen" \ "$is_blocked" "$is_restricted") peers+=("$peer_json") diff --git a/commands/test/integration.sh b/commands/test/integration.sh index de3a636..9aac2cb 100644 --- a/commands/test/integration.sh +++ b/commands/test/integration.sh @@ -134,6 +134,8 @@ function cmd::test::run_all_integration_sections() { cmd::test::section_identity cmd::test::section_hosts cmd::test::section_peer_cmd + cmd::test::section_group_purge + cmd::test::section_logs_clean } function cmd::test::section_list() { @@ -145,6 +147,12 @@ function cmd::test::section_list() { 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 + cmd::test::run_cmd "list --json" '"ok":true' list --json + cmd::test::run_cmd "list --json has peers" '"peers":' list --json + cmd::test::run_cmd "list --json has meta" '"meta":' list --json + cmd::test::run_cmd "list --json peer name" '"name":' list --json + cmd::test::run_cmd "list --json peer ip" '"ip":' list --json + cmd::test::run_cmd "list --json peer status" '"status":' list --json } function cmd::test::section_inspect() { @@ -153,6 +161,10 @@ function cmd::test::section_inspect() { 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 + cmd::test::run_cmd "inspect --json" '"ok":true' inspect --name phone-nuno --json + cmd::test::run_cmd "inspect --json rule" '"rule":' inspect --name phone-nuno --json + cmd::test::run_cmd "inspect --json identity" '"identity":' inspect --name phone-nuno --json + cmd::test::run_cmd "inspect --json groups" '"groups":' inspect --name phone-nuno --json } function cmd::test::section_config() { @@ -196,6 +208,10 @@ function cmd::test::section_logs() { 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 + cmd::test::run_cmd "logs --resolved" "" logs --resolved + cmd::test::run_cmd "logs --ascending" "" logs --ascending + cmd::test::run_cmd "logs --descending" "" logs --descending + cmd::test::run_cmd "logs --wg --ascending" "" logs --wg --ascending } function cmd::test::section_fw() { @@ -299,4 +315,25 @@ function cmd::test::section_peer_cmd() { "$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 } - \ No newline at end of file + + function cmd::test::section_group_purge() { + test::section "Group: purge-stale" + + # dry-run should not modify anything + cmd::test::run_cmd "purge-stale --all --dry-run" \ + "[dry-run]" \ + group purge-stale --all --dry-run --force + + # single group dry-run + cmd::test::run_cmd "purge-stale --name family --dry-run" \ + "[dry-run]" \ + group purge-stale --name family --dry-run --force +} + +function cmd::test::section_logs_clean() { + test::section "Logs: clean" + + cmd::test::run_cmd "logs clean --force" \ + "keepalive" \ + logs clean --force +} \ No newline at end of file diff --git a/commands/test/unit.sh b/commands/test/unit.sh index e242b43..e53463c 100644 --- a/commands/test/unit.sh +++ b/commands/test/unit.sh @@ -48,6 +48,7 @@ function cmd::test::run_all_unit_sections() { cmd::test::unit_config cmd::test::unit_parse_since cmd::test::unit_group_status + cmd::test::unit_json_output } function cmd::test::unit_subnet() { @@ -219,4 +220,30 @@ function cmd::test::unit_group_status() { IFS='|' read -r color str <<< "$(ui::group::status 4 2)" cmd::test::assert "group status partial str" "$str" "partial (2/4)" +} + +function cmd::test::unit_json_output() { + test::section "Unit: JSON output" + + # json::envelope produces valid structure + local result + result=$(echo '{"peers":[]}' | json::envelope "list" "0") + cmd::test::assert "envelope ok field" "$(echo "$result" | grep -o '"ok":true')" '"ok":true' + cmd::test::assert "envelope command field" "$(echo "$result" | grep -o '"command":"list"')" '"command":"list"' + cmd::test::assert "envelope meta field" "$(echo "$result" | grep -o '"meta":')" '"meta":' + cmd::test::assert "envelope count field" "$(echo "$result" | grep -o '"count":0')" '"count":0' + + # json::error_envelope + local err_result + err_result=$(json::error_envelope "inspect" "Peer not found") + cmd::test::assert "error envelope ok false" \ + "$(echo "$err_result" | grep -o '"ok":false')" '"ok":false' + cmd::test::assert "error envelope error field" \ + "$(echo "$err_result" | grep -o '"error":')" '"error":' + + # command::json mixin accessor + _COMMAND_JSON=true + cmd::test::assert_true "command::json true" "command::json" + _COMMAND_JSON=false + cmd::test::assert_false "command::json false" "command::json" } \ No newline at end of file diff --git a/core/command.sh b/core/command.sh index d359560..6957d53 100644 --- a/core/command.sh +++ b/core/command.sh @@ -47,7 +47,7 @@ function command::run() { local fn fn=$(command::fn "$cmd" run) - core::call_function "$fn" "${args[@]}" + core::call_function "$fn" ${args[@]+"${args[@]}"} } diff --git a/core/command_mixins.sh b/core/command_mixins.sh index ddda8b6..fdb5582 100644 --- a/core/command_mixins.sh +++ b/core/command_mixins.sh @@ -85,9 +85,12 @@ function command::_preprocess_flags() { local -n _args_ref="$1" local -a _filtered=() - for _arg in "${_args_ref[@]:-}"; do + if [[ ${#_args_ref[@]} -eq 0 ]]; then + return 0 # nothing to process + fi + + for _arg in "${_args_ref[@]}"; do local _consumed=false - local _mixin for _mixin in "${_ACTIVE_MIXINS[@]:-}"; do local _process_fn="command::mixin::${_mixin}::process" if declare -f "$_process_fn" >/dev/null 2>&1; then