feat(tui): add safe client and backup workflows
This commit is contained in:
+603
-12
@@ -11,7 +11,7 @@ source "${PROJECT_DIR}/wg-manager.sh"
|
||||
|
||||
case $# in
|
||||
0) ;;
|
||||
1) case $1 in config|parser|status|list|list-redirected|list-deterministic|transaction|rollback|locking|backups|mutation-preserves-source|secret-modes|add-client|address-allocation|add-collisions|add-rollback|no-secret-logs|add-golden-config|add-invalid-names|show-client|config-export|qr|path-safety|no-secret-default-output|remove-client|remove-cancel|remove-exact-block|remove-rollback|archive-client|archive-path-safety|remove-preserves-neighbors|diagnostics|diagnostics-systemd|diagnostics-firewall|diagnostics-forwarding|diagnostics-runtime-diff|diagnostics-read-only|diagnostics-exit-codes|audit|audit-no-secrets|backup-list|restore|restore-invalid|restore-rollback|backup-path-safety|runtime-file-modes|gum-resolution|tui-routing|tui-cancel|tui-read-only|tui-no-gum|tui-non-tty) ;; *) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;; esac ;;
|
||||
1) case $1 in config|parser|status|list|list-redirected|list-deterministic|transaction|rollback|locking|backups|mutation-preserves-source|secret-modes|add-client|address-allocation|add-collisions|add-rollback|no-secret-logs|add-golden-config|add-invalid-names|show-client|config-export|qr|path-safety|no-secret-default-output|remove-client|remove-cancel|remove-exact-block|remove-rollback|archive-client|archive-path-safety|remove-preserves-neighbors|diagnostics|diagnostics-systemd|diagnostics-firewall|diagnostics-forwarding|diagnostics-runtime-diff|diagnostics-read-only|diagnostics-exit-codes|audit|audit-no-secrets|backup-list|restore|restore-invalid|restore-rollback|backup-path-safety|runtime-file-modes|gum-resolution|tui-routing|tui-cancel|tui-read-only|tui-no-gum|tui-non-tty|tui-add|tui-remove|tui-client-actions|tui-backup|tui-restore|tui-backend-failure|tui-no-secret-args) ;; *) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;; esac ;;
|
||||
*) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;;
|
||||
esac
|
||||
test_group=${1-}
|
||||
@@ -391,6 +391,10 @@ run_gum_tests() {
|
||||
|
||||
prepare_m9_fixture() {
|
||||
local config_file="${TEST_ROOT}/m9.conf" fixture_error
|
||||
# Each focused/full-suite invocation shares TEST_ROOT; own every fixture
|
||||
# directory before seeding it so backup counts and captures are deterministic.
|
||||
rm -rf -- "${TEST_ROOT}/clients" "${TEST_ROOT}/metadata" "${TEST_ROOT}/backups" "${TEST_ROOT}/logs" "${TEST_ROOT}/archive"
|
||||
rm -f -- "${TEST_ROOT}/manager.lock" "${TEST_ROOT}/lock" "${TEST_ROOT}/gum-script" "${TEST_ROOT}/gum-style.txt" "${TEST_ROOT}/gum-pager.txt" "${TEST_ROOT}/gum-choices.txt"
|
||||
mkdir -p -- "${TEST_ROOT}/clients" "${TEST_ROOT}/metadata" "${TEST_ROOT}/backups" "${TEST_ROOT}/logs"
|
||||
cp -- "${PROJECT_DIR}/tests/fixtures/wg0.conf" "${TEST_ROOT}/wg0.conf"
|
||||
write_valid_config "$config_file"
|
||||
@@ -413,8 +417,84 @@ prepare_m9_fixture() {
|
||||
: >"$TEST_MOCK_LOG"
|
||||
}
|
||||
|
||||
prepare_m10_add_fixture() {
|
||||
local config_file="${TEST_ROOT}/m10-add.conf" fixture="${TEST_ROOT}/wg0.conf" runtime_file="${TEST_ROOT}/runtime.dump"
|
||||
rm -rf -- "${TEST_ROOT}/backups" "${TEST_ROOT}/logs" "${TEST_ROOT}/metadata" "${TEST_ROOT}/archive" "${TEST_ROOT}/clients" "${TEST_ROOT}/client-files"
|
||||
rm -f -- "${TEST_ROOT}/lock" "${TEST_ROOT}/manager.lock" "${TEST_ROOT}/gum-script" "${TEST_ROOT}/gum-style.txt" "${TEST_ROOT}/gum-pager.txt" "${TEST_ROOT}/gum-choices.txt"
|
||||
cp -- "${PROJECT_DIR}/tests/fixtures/m3/source.conf" "$fixture"
|
||||
write_m2_config "$config_file" "$fixture"
|
||||
export WG_MANAGER_CONFIG="$config_file" WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/backups" WG_MANAGER_LOG_DIR="${TEST_ROOT}/logs" WG_MANAGER_METADATA_DIR="${TEST_ROOT}/metadata" WG_MANAGER_ARCHIVE_DIR="${TEST_ROOT}/archive" WG_MANAGER_LOCK_FILE="${TEST_ROOT}/lock" WG_MANAGER_GUM="${PROJECT_DIR}/tests/mocks/bin/gum" WG_MANAGER_NOW=1700000100 WG_MANAGER_TEST_MODE=1 WG_MANAGER_TEST_TTY=both WG_MOCK_INTERFACE=wg0 GUM_STYLE_CAPTURE="${TEST_ROOT}/gum-style.txt" GUM_PAGER_CAPTURE="${TEST_ROOT}/gum-pager.txt" GUM_CHOICES_CAPTURE="${TEST_ROOT}/gum-choices.txt"
|
||||
printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\tvpn.example.invalid:51820\t192.0.2.2/32\t0\t0\t0\t0\n' >"$runtime_file"
|
||||
rm -rf -- "${TEST_ROOT}/dump-sequence"
|
||||
mkdir -p -- "${TEST_ROOT}/dump-sequence"
|
||||
cp -- "$runtime_file" "${TEST_ROOT}/dump-sequence/2"
|
||||
cp -- "$runtime_file" "${TEST_ROOT}/dump-sequence/3"
|
||||
cp -- "$runtime_file" "${TEST_ROOT}/dump-sequence/4"
|
||||
cp -- "$runtime_file" "${TEST_ROOT}/dump-sequence/5"
|
||||
cp -- "$runtime_file" "${TEST_ROOT}/dump-sequence/6"
|
||||
printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\tvpn.example.invalid:51820\t192.0.2.2/32\t0\t0\t0\t0\nFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=\t(none)\tvpn.example.invalid:51820\t192.0.2.3/32\t0\t0\t0\t0\n' >"${TEST_ROOT}/dump-sequence/1"
|
||||
export WG_MOCK_DUMP_FILE="$runtime_file" WG_MOCK_DUMP_SEQUENCE_DIR="${TEST_ROOT}/dump-sequence"
|
||||
unset WG_SYNC_FAIL WG_SYNC_FAIL_ONCE_FILE WG_SYNC_FAIL_ON_CALL WG_MOCK_DUMP_FAIL_ON_CALL WG_MOCK_PRIVATE_KEY WG_MOCK_PUBLIC_KEY WG_MOCK_PSK WG_MOCK_EXPECTED_PRIVATE_KEY
|
||||
set_runtime_paths
|
||||
load_config
|
||||
rm -rf -- "$METADATA_DIR" "$CLIENT_DIR"
|
||||
mkdir -p -- "$METADATA_DIR" "$CLIENT_DIR" "${TEST_ROOT}/backups" "${TEST_ROOT}/logs" "${TEST_ROOT}/archive"
|
||||
: >"$TEST_MOCK_LOG"
|
||||
}
|
||||
|
||||
prepare_m10_remove_fixture() {
|
||||
local config_file="${TEST_ROOT}/m10-remove.conf" fixture="${TEST_ROOT}/wg0.conf" runtime_dir="${TEST_ROOT}/remove-dumps"
|
||||
rm -rf -- "${TEST_ROOT}/backups" "${TEST_ROOT}/logs" "${TEST_ROOT}/metadata" "${TEST_ROOT}/archive" "${TEST_ROOT}/clients" "${TEST_ROOT}/client-files" "${runtime_dir}"
|
||||
rm -f -- "${TEST_ROOT}/lock" "${TEST_ROOT}/manager.lock" "${TEST_ROOT}/gum-script" "${TEST_ROOT}/gum-style.txt" "${TEST_ROOT}/gum-pager.txt" "${TEST_ROOT}/gum-choices.txt"
|
||||
cp -- "${PROJECT_DIR}/tests/fixtures/m3/source.conf" "$fixture"
|
||||
chmod 600 -- "$fixture"
|
||||
sed -i 's/wg-manager-client: fixture/wg-manager-client: alice/' "$fixture"
|
||||
sed -i '/\[Peer\]/{N; s/\[Peer\]\n# wg-manager-client: alice/# wg-manager-client: alice\n[Peer]/;}' "$fixture"
|
||||
write_m2_config "$config_file" "$fixture"
|
||||
replace_config_line "$config_file" 'CLIENT_DIR=' "CLIENT_DIR=\"${TEST_ROOT}/client-files\""
|
||||
export WG_MANAGER_CONFIG="$config_file" WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/backups" WG_MANAGER_LOG_DIR="${TEST_ROOT}/logs" WG_MANAGER_METADATA_DIR="${TEST_ROOT}/metadata" WG_MANAGER_ARCHIVE_DIR="${TEST_ROOT}/archive" WG_MANAGER_LOCK_FILE="${TEST_ROOT}/lock" WG_MANAGER_GUM="${PROJECT_DIR}/tests/mocks/bin/gum" WG_MANAGER_NOW=1700000100 WG_MANAGER_TEST_MODE=1 WG_MANAGER_TEST_TTY=both WG_MOCK_INTERFACE=wg0 GUM_STYLE_CAPTURE="${TEST_ROOT}/gum-style.txt" GUM_PAGER_CAPTURE="${TEST_ROOT}/gum-pager.txt" GUM_CHOICES_CAPTURE="${TEST_ROOT}/gum-choices.txt"
|
||||
set_runtime_paths
|
||||
load_config
|
||||
rm -rf -- "$METADATA_DIR" "$CLIENT_DIR" "$ARCHIVE_DIR" "$LOG_DIR" "$runtime_dir" "$BACKUP_DIR"
|
||||
mkdir -p -- "$METADATA_DIR" "$CLIENT_DIR" "$ARCHIVE_DIR" "$LOG_DIR" "$runtime_dir"
|
||||
chmod 700 -- "$METADATA_DIR" "$CLIENT_DIR" "$ARCHIVE_DIR" "$LOG_DIR" "$runtime_dir"
|
||||
write_alice_metadata
|
||||
printf '[Interface]\nPrivateKey = EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=\nAddress = 192.0.2.2/32\n' >"$CLIENT_DIR/alice.conf"
|
||||
printf 'EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=\n' >"$CLIENT_DIR/alice.key"
|
||||
printf 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=\n' >"$CLIENT_DIR/alice.pub"
|
||||
printf 'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG=\n' >"$CLIENT_DIR/alice.psk"
|
||||
chmod 600 -- "$METADATA_DIR/alice.meta" "$CLIENT_DIR"/alice.*
|
||||
printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\tvpn.example.invalid:51820\t192.0.2.2/32\t0\t0\t0\t0\n' >"$runtime_dir/1"
|
||||
cp -- "$runtime_dir/1" "$runtime_dir/2"
|
||||
cp -- "$runtime_dir/1" "$runtime_dir/3"
|
||||
printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\n' >"$runtime_dir/4"
|
||||
cp -- "$runtime_dir/1" "$runtime_dir/5"
|
||||
export WG_MOCK_DUMP_SEQUENCE_DIR="$runtime_dir" WG_MOCK_DUMP_FILE="$runtime_dir/1" TEST_MOCK_LOG="${TEST_ROOT}/mocks.log"
|
||||
unset WG_SYNC_FAIL WG_SYNC_FAIL_ONCE_FILE WG_SYNC_FAIL_ON_CALL M6_ARCHIVE_COPY_FAIL_INDEX M6_ARCHIVE_REMOVE_FAIL_INDEX M6_ARCHIVE_RECOVERY_FAIL_INDEX
|
||||
: >"$TEST_MOCK_LOG"
|
||||
}
|
||||
|
||||
prepare_m10_restore_fixture() {
|
||||
local config_file="${TEST_ROOT}/m10-restore.conf" fixture="${TEST_ROOT}/wg0.conf" runtime_file="${TEST_ROOT}/restore-runtime.dump"
|
||||
rm -rf -- "${TEST_ROOT}/backups" "${TEST_ROOT}/logs" "${TEST_ROOT}/metadata" "${TEST_ROOT}/archive" "${TEST_ROOT}/clients" "${TEST_ROOT}/client-files"
|
||||
rm -f -- "${TEST_ROOT}/lock" "${TEST_ROOT}/manager.lock" "${TEST_ROOT}/gum-script" "${TEST_ROOT}/gum-style.txt" "${TEST_ROOT}/gum-pager.txt" "${TEST_ROOT}/gum-choices.txt"
|
||||
cp -- "${PROJECT_DIR}/tests/fixtures/m3/source.conf" "$fixture"
|
||||
chmod 600 -- "$fixture"
|
||||
write_m2_config "$config_file" "$fixture"
|
||||
export WG_MANAGER_CONFIG="$config_file" WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/backups" WG_MANAGER_LOG_DIR="${TEST_ROOT}/logs" WG_MANAGER_METADATA_DIR="${TEST_ROOT}/metadata" WG_MANAGER_ARCHIVE_DIR="${TEST_ROOT}/archive" WG_MANAGER_LOCK_FILE="${TEST_ROOT}/lock" WG_MANAGER_GUM="${PROJECT_DIR}/tests/mocks/bin/gum" WG_MANAGER_NOW=1700000100 WG_MANAGER_TEST_MODE=1 WG_MANAGER_TEST_TTY=both WG_MOCK_INTERFACE=wg0 GUM_STYLE_CAPTURE="${TEST_ROOT}/gum-style.txt" GUM_PAGER_CAPTURE="${TEST_ROOT}/gum-pager.txt" GUM_CHOICES_CAPTURE="${TEST_ROOT}/gum-choices.txt"
|
||||
set_runtime_paths
|
||||
load_config
|
||||
rm -rf -- "$BACKUP_DIR" "$LOG_DIR" "$METADATA_DIR" "$CLIENT_DIR" "$ARCHIVE_DIR"
|
||||
mkdir -p -- "$BACKUP_DIR" "$LOG_DIR" "$METADATA_DIR" "$CLIENT_DIR" "$ARCHIVE_DIR"
|
||||
chmod 700 -- "$BACKUP_DIR" "$LOG_DIR" "$METADATA_DIR" "$CLIENT_DIR" "$ARCHIVE_DIR"
|
||||
printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\tvpn.example.invalid:51820\t192.0.2.2/32\t0\t0\t0\t0\n' >"$runtime_file"
|
||||
export WG_MOCK_DUMP_FILE="$runtime_file"
|
||||
unset WG_MOCK_DUMP_SEQUENCE_DIR WG_SYNC_FAIL WG_SYNC_FAIL_ONCE_FILE WG_SYNC_FAIL_ON_CALL TRANSACTION_ROLLBACK_REPLACE_FAIL
|
||||
: >"$TEST_MOCK_LOG"
|
||||
}
|
||||
|
||||
run_m9_tests() {
|
||||
local output status script_file tty_mode pager_count snapshot_before lock_status long_name
|
||||
local output status script_file tty_mode pager_count snapshot_before lock_status long_name before_backup secret_stream add_output captured_prompt formatted_prompt long_endpoint
|
||||
case $test_group in
|
||||
gum-resolution)
|
||||
run_gum_tests
|
||||
@@ -463,13 +543,13 @@ run_m9_tests() {
|
||||
printf 'Неизвестно\nНеизвестно\nНеизвестно\n' >"$script_file"
|
||||
set +e; tui_screen >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'three route failures are bounded'
|
||||
printf 'Диагностика\nЖурнал\nРезервные копии\nДобавить\nУдалить\nВыход\n' >"$script_file"
|
||||
printf 'Диагностика\nЖурнал\nРезервные копии\nОтмена\nДобавить\n\nУдалить\n\nВыход\n' >"$script_file"
|
||||
: >"$TEST_MOCK_LOG"
|
||||
set +e; output=$(tui_screen 2>&1); status=$?; set -e
|
||||
assert_equal 0 "$status" 'all read-only and placeholder routes return to menu'
|
||||
assert_contains 'Действие «Добавить»' "$output" 'add placeholder is visible'
|
||||
assert_contains 'Действие «Удалить»' "$output" 'remove placeholder is visible'
|
||||
assert_equal 0 "$(grep -c '^gum pager$' "$TEST_MOCK_LOG" | awk '$1 >= 3 { print 0; next } { print 1 }')" 'diagnose/log/backups reach pager'
|
||||
assert_contains 'gum input' "$(<"$TEST_MOCK_LOG")" 'add route reaches input wrapper'
|
||||
assert_contains 'gum filter' "$(<"$TEST_MOCK_LOG")" 'remove route reaches filter wrapper'
|
||||
assert_equal 0 "$(grep -c '^gum pager$' "$TEST_MOCK_LOG" | awk '$1 >= 2 { print 0; next } { print 1 }')" 'diagnose and log reach pager'
|
||||
printf 'Выход\n' >"$script_file"
|
||||
set +e; main >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'zero-argument main enters TUI'
|
||||
@@ -498,9 +578,9 @@ run_m9_tests() {
|
||||
set +e; tui_is_terminal >/dev/null 2>&1; status=$?; set -e
|
||||
assert_failure "$status" 'unset test TTY is not accepted'
|
||||
;;
|
||||
tui-read-only)
|
||||
tui-read-only)
|
||||
prepare_m9_fixture
|
||||
script_file="${TEST_ROOT}/gum-script"; printf 'Клиенты\nalice — BBBBBBBB\nВыход\n' >"$script_file"; export GUM_SCRIPT_FILE="$script_file"
|
||||
script_file="${TEST_ROOT}/gum-script"; printf 'Клиенты\nalice — BBBBBBBB\nДетали\nВыход\n' >"$script_file"; export GUM_SCRIPT_FILE="$script_file"
|
||||
snapshot_before=$(find "${TEST_ROOT}/backups" "${TEST_ROOT}/logs" "${TEST_ROOT}/archive" "${TEST_ROOT}/clients" "${TEST_ROOT}/metadata" -mindepth 1 -maxdepth 2 -printf '%P %y %m %s\n' | LC_ALL=C sort; sha256sum "$WG_CONF" "$WG_MANAGER_CONFIG" "${TEST_ROOT}/clients/alice.conf" "${TEST_ROOT}/metadata/alice.meta"; stat -c '%a:%u:%g:%h' "$WG_CONF" "$WG_MANAGER_CONFIG" "${TEST_ROOT}/clients/alice.conf" "${TEST_ROOT}/metadata/alice.meta")
|
||||
begin_test 'TUI client selection uses stable identity and read-only backend'
|
||||
set +e; tui_screen >/dev/null 2>&1; status=$?; set -e
|
||||
@@ -511,7 +591,7 @@ run_m9_tests() {
|
||||
assert_equal '' "$(grep -E 'syncconf|wg-quick (up|down|save)|ip (add|del|replace)' "$TEST_MOCK_LOG" || true)" 'read-only screen has no mutation calls'
|
||||
CLIENT_NAMES=(same same) CLIENT_KEYS=(ABCDEFGH111111111111111111111111111111111= ABCDEFGH222222222222222222222222222222222=)
|
||||
CLIENT_ADDRESSES=(192.0.2.2/32 192.0.2.2/32) CLIENT_ENDPOINTS=(- -) CLIENT_HANDSHAKES=(0 0) CLIENT_RX=(0 0) CLIENT_TX=(0 0) CLIENT_STATES=(unmanaged unmanaged) CLIENT_ORIGINS=(unmanaged unmanaged)
|
||||
printf 'same — ABCDEFGH #2\n' >"$script_file"
|
||||
printf 'same — ABCDEFGH #2\nДетали\n' >"$script_file"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'duplicate display names select the offered stable index'
|
||||
assert_contains 'ABCDEFGH222222222222222222222222222222222=' "$(<"$GUM_PAGER_CAPTURE")" 'selected duplicate identity renders the second full key'
|
||||
@@ -519,7 +599,7 @@ run_m9_tests() {
|
||||
long_name=$(printf 'n%.0s' {1..64})
|
||||
CLIENT_NAMES=("$long_name" "$long_name") CLIENT_KEYS=(ABCDEFGH111111111111111111111111111111111= ABCDEFGH222222222222222222222222222222222=)
|
||||
CLIENT_ADDRESSES=(192.0.2.2/32 192.0.2.2/32)
|
||||
printf '%s\n' "${long_name:0:60} — ABCDEFGH #2" >"$script_file"
|
||||
printf '%s\nДетали\n' "${long_name:0:60} — ABCDEFGH #2" >"$script_file"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'long client names remain selectable'
|
||||
assert_equal 0 "$(awk 'length($0) > 80 { bad += 1 } END { print bad + 0 }' "$GUM_CHOICES_CAPTURE")" 'offered client labels fit 80 columns'
|
||||
@@ -531,6 +611,514 @@ run_m9_tests() {
|
||||
assert_equal 0 "$lock_status" 'read-only screens do not create a mutation lock'
|
||||
assert_equal 0 "$(grep -hE 'AAAAAAAAAAAAAAAA|PresharedKey' "$GUM_PAGER_CAPTURE" "$GUM_STYLE_CAPTURE" "$TEST_MOCK_LOG" | wc -l)" 'read-only TUI output has no secrets'
|
||||
;;
|
||||
tui-add)
|
||||
prepare_m10_add_fixture
|
||||
begin_test 'TUI add real full-tunnel success uses the shared backend'
|
||||
printf 'alice\nПолный туннель\ny\ny\nn\n' >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_add_flow 2>"${TEST_ROOT}/add.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'real TUI add succeeds'
|
||||
assert_file_exists "${CLIENT_DIR}/alice.conf" 'real client config exists'
|
||||
assert_file_exists "${METADATA_DIR}/alice.meta" 'real client metadata exists'
|
||||
assert_file_mode 600 "${CLIENT_DIR}/alice.conf" 'real client config mode'
|
||||
assert_file_mode 600 "${METADATA_DIR}/alice.meta" 'real metadata mode'
|
||||
assert_contains 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=' "$(<"$WG_CONF")" 'generated peer is in server config'
|
||||
assert_contains '192.0.2.3/32' "$(<"$WG_CONF")" 'generated address is in server config'
|
||||
assert_contains 'AllowedIPs = 0.0.0.0/0,::/0' "$(<"${CLIENT_DIR}/alice.conf")" 'full-tunnel AllowedIPs are generated'
|
||||
assert_contains 'Endpoint = vpn.example.invalid:51820' "$(<"${CLIENT_DIR}/alice.conf")" 'configured endpoint is generated'
|
||||
assert_contains 'wg syncconf' "$(<"$TEST_MOCK_LOG")" 'runtime syncconf is invoked'
|
||||
assert_contains 'operation=add' "$(<"${LOG_DIR}/wg-manager.log")" 'add audit is recorded'
|
||||
assert_contains "${CLIENT_DIR}/alice.conf" "$output" 'final config path is shown'
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey' "$TEST_MOCK_LOG" "${LOG_DIR}/wg-manager.log" "${TEST_ROOT}/add.err" "$GUM_PAGER_CAPTURE" || true)" 'add outputs and logs are secret-free'
|
||||
|
||||
begin_test 'TUI add preserves QR operation failure status'
|
||||
prepare_m10_add_fixture
|
||||
export QR_MOCK_EXPECTED_FILE="${CLIENT_DIR}/qr-fail.conf" QR_MOCK_FAIL=1
|
||||
printf 'qr-fail\nПолный туннель\ny\ny\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_add_flow >/dev/null 2>"${TEST_ROOT}/qr-fail-add.err"; status=$?; set -e
|
||||
unset QR_MOCK_EXPECTED_FILE QR_MOCK_FAIL
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'TUI add returns QR operation failure'
|
||||
assert_contains 'QR-код недоступен' "$(<"${TEST_ROOT}/qr-fail-add.err")" 'TUI add preserves QR diagnostic'
|
||||
|
||||
begin_test 'TUI add treats missing QR dependency as a normal fallback'
|
||||
prepare_m10_add_fixture
|
||||
export QR_MOCK_EXPECTED_FILE="${CLIENT_DIR}/qr-missing.conf"
|
||||
saved_path=$PATH
|
||||
mkdir -- "${TEST_ROOT}/qr-add-no-command"
|
||||
for utility in bash env wg wg-quick cat stat sort paste date tail mv cp chmod mkdir mktemp flock rm ln; do
|
||||
ln -s -- "$(command -v "$utility")" "${TEST_ROOT}/qr-add-no-command/$utility"
|
||||
done
|
||||
PATH="${TEST_ROOT}/qr-add-no-command"
|
||||
printf 'qr-missing\nПолный туннель\ny\ny\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_add_flow 2>"${TEST_ROOT}/qr-missing-add.err"); status=$?; set -e
|
||||
PATH=$saved_path
|
||||
unset QR_MOCK_EXPECTED_FILE
|
||||
assert_equal 0 "$status" 'missing QR dependency is a normal TUI add fallback'
|
||||
assert_contains 'qr-missing.conf' "$output" 'missing QR fallback retains config path'
|
||||
assert_contains 'QR-код недоступен' "$(<"${TEST_ROOT}/qr-missing-add.err")" 'missing QR fallback is actionable'
|
||||
|
||||
begin_test 'TUI add split tunnel accepts normalized networks and endpoint override'
|
||||
prepare_m10_add_fixture
|
||||
printf 'bob\nВыборочные сети\n10.40.0.0/16, 2001:db8:40::/48\nn\nalt.example.invalid\ny\nn\n' >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_add_flow 2>"${TEST_ROOT}/split.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'split TUI add succeeds'
|
||||
assert_file_exists "${CLIENT_DIR}/bob.conf" 'split client config exists'
|
||||
assert_contains 'AllowedIPs = 10.40.0.0/16,2001:db8:40::/48' "$(<"${CLIENT_DIR}/bob.conf")" 'split networks are normalized'
|
||||
assert_contains 'Endpoint = alt.example.invalid:51820' "$(<"${CLIENT_DIR}/bob.conf")" 'endpoint override is applied'
|
||||
assert_contains 'MODE=split-tunnel' "$(<"${METADATA_DIR}/bob.meta")" 'split mode is recorded'
|
||||
assert_contains 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=' "$(<"$WG_CONF")" 'split peer reaches server config'
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey' "$TEST_MOCK_LOG" "${LOG_DIR}/wg-manager.log" "${TEST_ROOT}/split.err" "$GUM_PAGER_CAPTURE" || true)" 'split output and logs are secret-free'
|
||||
|
||||
begin_test 'TUI add cancellation before create preserves isolated state'
|
||||
prepare_m10_add_fixture
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
printf 'alice\nПолный туннель\ny\nn\n' >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_add_flow >/dev/null 2>"${TEST_ROOT}/cancel.err"; status=$?; set -e
|
||||
assert_equal 0 "$status" 'create cancellation is normal'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'create cancellation preserves server bytes'
|
||||
assert_equal 0 "$(find "$CLIENT_DIR" "$METADATA_DIR" -mindepth 1 -maxdepth 1 -type f | wc -l)" 'create cancellation leaves no artifacts'
|
||||
assert_equal '' "$(grep -E 'syncconf|wg-quick' "$TEST_MOCK_LOG" || true)" 'create cancellation does not mutate runtime'
|
||||
|
||||
begin_test 'TUI add cancellation before name is harmless'
|
||||
prepare_m10_add_fixture
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
: >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_add_flow >/dev/null 2>"${TEST_ROOT}/empty-name.err"; status=$?; set -e
|
||||
assert_equal 0 "$status" 'initial name cancellation is normal'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'initial cancellation preserves server bytes'
|
||||
assert_equal 0 "$(find "$CLIENT_DIR" "$METADATA_DIR" -mindepth 1 -maxdepth 1 -type f | wc -l)" 'initial cancellation leaves no artifacts'
|
||||
|
||||
begin_test 'TUI add corrects invalid name and endpoint'
|
||||
prepare_m10_add_fixture
|
||||
printf 'bad name\ncarol\nПолный туннель\nn\nbad endpoint\ncarol.example.invalid\ny\nn\n' >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_add_flow 2>"${TEST_ROOT}/correction.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'invalid name and endpoint can be corrected'
|
||||
assert_file_exists "${CLIENT_DIR}/carol.conf" 'corrected client artifact exists'
|
||||
assert_contains 'carol.example.invalid:51820' "$(<"${CLIENT_DIR}/carol.conf")" 'corrected endpoint is used'
|
||||
assert_contains 'Некорректное имя клиента' "$(<"${TEST_ROOT}/correction.err")" 'invalid name gives actionable feedback'
|
||||
assert_contains 'Некорректный endpoint' "$(<"${TEST_ROOT}/correction.err")" 'invalid endpoint gives actionable feedback'
|
||||
|
||||
begin_test 'TUI add rejects invalid split networks before mutation'
|
||||
prepare_m10_add_fixture
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
printf 'badnet\nВыборочные сети\nnot-a-cidr\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_add_flow >/dev/null 2>"${TEST_ROOT}/bad-net.err"; status=$?; set -e
|
||||
assert_equal "$EXIT_VALIDATION" "$status" 'invalid split network status'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'invalid split network preserves server bytes'
|
||||
assert_equal 0 "$(find "$CLIENT_DIR" "$METADATA_DIR" -mindepth 1 -maxdepth 1 -type f | wc -l)" 'invalid split network leaves no artifacts'
|
||||
assert_equal '' "$(grep -E 'syncconf|wg-quick' "$TEST_MOCK_LOG" || true)" 'invalid split network does not mutate runtime'
|
||||
|
||||
begin_test 'TUI add apply failure rolls back and removes staged artifacts'
|
||||
prepare_m10_add_fixture
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
export WG_SYNC_FAIL_ONCE_FILE="${TEST_ROOT}/sync-fail"
|
||||
unset WG_MOCK_DUMP_SEQUENCE_DIR
|
||||
printf 'dana\nПолный туннель\ny\ny\nn\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_add_flow >/dev/null 2>"${TEST_ROOT}/apply-fail.err"; status=$?; set -e
|
||||
unset WG_SYNC_FAIL_ONCE_FILE
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'apply failure returns operation status after rollback'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'apply failure restores server bytes'
|
||||
assert_equal 0 "$(find "$CLIENT_DIR" "$METADATA_DIR" -maxdepth 1 -type f -name 'dana.*' | wc -l)" 'apply failure leaves no client artifacts'
|
||||
assert_contains 'syncconf' "$(<"$TEST_MOCK_LOG")" 'apply failure attempted runtime update and rollback'
|
||||
assert_contains 'operation=add' "$(<"${LOG_DIR}/wg-manager.log")" 'apply failure is audited'
|
||||
|
||||
begin_test 'CLI add split-networks contract is validated'
|
||||
prepare_m10_add_fixture
|
||||
set +e; add_client cli --mode split-tunnel --split-networks '10.40.0.0/16,2001:db8:40::/48' >/dev/null 2>"${TEST_ROOT}/cli-split.err"; status=$?; set -e
|
||||
assert_equal 0 "$status" 'CLI split add succeeds'
|
||||
assert_contains 'AllowedIPs = 10.40.0.0/16,2001:db8:40::/48' "$(<"${CLIENT_DIR}/cli.conf")" 'CLI split networks reach config'
|
||||
prepare_m10_add_fixture
|
||||
set +e; add_client missing --mode split-tunnel --split-networks >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'missing split-networks value is usage error'
|
||||
prepare_m10_add_fixture
|
||||
set +e; add_client repeat --mode split-tunnel --split-networks '10.40.0.0/16' --split-networks '10.41.0.0/16' >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'repeated split-networks is usage error'
|
||||
prepare_m10_add_fixture
|
||||
set +e; add_client invalid --mode split-tunnel --split-networks 'not-a-cidr' >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_VALIDATION" "$status" 'invalid split CIDR is validation error'
|
||||
prepare_m10_add_fixture
|
||||
set +e; add_client full --mode full-tunnel --split-networks '10.40.0.0/16' >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_VALIDATION" "$status" 'split-networks is rejected for full tunnel'
|
||||
;;
|
||||
tui-remove)
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
begin_test 'TUI remove real success archives exact client and peer'
|
||||
printf 'alice — 192.0.2.2/32 — BBBBBBBB\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_remove_flow 2>"${TEST_ROOT}/remove.err"); status=$?; set -e
|
||||
output+=$'\n'"$(<"${TEST_ROOT}/remove.err")"
|
||||
assert_equal 0 "$status" 'real TUI remove succeeds'
|
||||
assert_equal 0 "$(grep -c 'PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=' "$WG_CONF" || true)" 'removed peer is absent'
|
||||
assert_file_absent "$CLIENT_DIR/alice.conf" 'client config is archived'
|
||||
assert_file_absent "$METADATA_DIR/alice.meta" 'client metadata is archived'
|
||||
archive_path=$(find "$ARCHIVE_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)
|
||||
if [[ -n $archive_path ]]; then
|
||||
assert_file_exists "$archive_path/alice.conf" 'archive contains config'
|
||||
assert_file_exists "$archive_path/alice.meta" 'archive contains metadata'
|
||||
assert_file_mode 700 "$archive_path" 'archive mode is restrictive'
|
||||
assert_file_mode 600 "$archive_path/alice.conf" 'archived config mode'
|
||||
else
|
||||
assert_equal 1 0 'archive path is returned after successful removal'
|
||||
fi
|
||||
assert_contains 'Архив:' "$output" 'TUI reports actual archive path'
|
||||
assert_contains 'syncconf' "$(<"$TEST_MOCK_LOG")" 'remove applies runtime update'
|
||||
assert_contains 'operation=remove' "$(<"$LOG_DIR/wg-manager.log")" 'remove audit is recorded'
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey' "$TEST_MOCK_LOG" "$LOG_DIR/wg-manager.log" "$TEST_ROOT/remove.err" || true)" 'remove output and logs are secret-free'
|
||||
|
||||
begin_test 'TUI remove cancellation at filter preserves state'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
before_hash=$(sha256sum "$WG_CONF"); before_files=$(find "$CLIENT_DIR" "$METADATA_DIR" "$ARCHIVE_DIR" -type f -printf '%P %s\n' | LC_ALL=C sort)
|
||||
: >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_remove_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'filter cancellation is normal'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'filter cancellation preserves server'
|
||||
assert_equal "$before_files" "$(find "$CLIENT_DIR" "$METADATA_DIR" "$ARCHIVE_DIR" -type f -printf '%P %s\n' | LC_ALL=C sort)" 'filter cancellation preserves artifacts'
|
||||
assert_equal '' "$(grep -E 'syncconf|wg-quick' "$TEST_MOCK_LOG" || true)" 'filter cancellation does not mutate runtime'
|
||||
|
||||
begin_test 'TUI remove cancellation at confirmation preserves state'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
printf 'alice — 192.0.2.2/32 — BBBBBBBB\nn\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_remove_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'confirmation cancellation is normal'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'confirmation cancellation preserves server'
|
||||
assert_equal 0 "$(find "$ARCHIVE_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'confirmation cancellation creates no archive'
|
||||
|
||||
begin_test 'TUI remove excludes unmanaged clients and keeps labels bounded'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
printf '%s\t%s\t-\t192.0.2.9/32\t0\t0\t0\t0\n' EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE= '(none)' >>"${TEST_ROOT}/remove-dumps/1"
|
||||
printf '\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_remove_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'unmanaged-only selection cancellation is normal'
|
||||
assert_equal 0 "$(grep -c EEEEEEEE "$GUM_CHOICES_CAPTURE" || true)" 'unmanaged peer is not offered'
|
||||
long_name=$(printf 'n%.0s' {1..64})
|
||||
CLIENT_NAMES=("$long_name") CLIENT_KEYS=(BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=) CLIENT_ADDRESSES=(192.0.2.2/32) CLIENT_ORIGINS=(managed)
|
||||
printf '\n' >"${TEST_ROOT}/gum-script"
|
||||
set +e; tui_remove_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$(awk 'length($0) > 80 { bad += 1 } END { print bad + 0 }' "$GUM_CHOICES_CAPTURE")" 'remove labels stay within 80 columns'
|
||||
|
||||
begin_test 'destructive TUI previews retain identity while fitting 80 columns'
|
||||
captured_prompt=''
|
||||
gum_confirm() { captured_prompt=$1; return 1; }
|
||||
set +e; tui_remove_flow_for_index "$long_name" 0 >/dev/null 2>&1; status=$?; set -e
|
||||
unset -f gum_confirm
|
||||
gum_confirm() { gum_command confirm "$@"; }
|
||||
assert_equal 0 "$status" 'long destructive preview cancellation is normal'
|
||||
assert_contains "$long_name" "$captured_prompt" 'long name remains in destructive preview'
|
||||
assert_contains '192.0.2.2/32' "$captured_prompt" 'address remains in destructive preview'
|
||||
assert_equal 0 "$(awk 'length($0) > 80 { bad += 1 } END { print bad + 0 }' <<<"$captured_prompt")" 'destructive preview lines fit 80 columns'
|
||||
long_endpoint=$(printf 'e%.0s' {1..253})
|
||||
formatted_prompt=$(tui_wrap_value 'Использовать endpoint:' "$long_endpoint")
|
||||
assert_equal 0 "$(awk 'length($0) > 80 { bad += 1 } END { print bad + 0 }' <<<"$formatted_prompt")" 'endpoint preview lines fit 80 columns'
|
||||
|
||||
begin_test 'TUI remove exact-key mismatch is rejected before mutation'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
set +e; remove_client_exact alice CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC= >/dev/null 2>"${TEST_ROOT}/wrong-key.err"; status=$?; set -e
|
||||
assert_equal "$EXIT_VALIDATION" "$status" 'wrong selected key is rejected'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'wrong selected key preserves server'
|
||||
|
||||
begin_test 'TUI remove apply failure rolls back disk and artifacts'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
cp -- "$TEST_ROOT/remove-dumps/1" "$TEST_ROOT/remove-dumps/2"
|
||||
cp -- "$TEST_ROOT/remove-dumps/1" "$TEST_ROOT/remove-dumps/3"
|
||||
cp -- "$TEST_ROOT/remove-dumps/1" "$TEST_ROOT/remove-dumps/4"
|
||||
cp -- "$TEST_ROOT/remove-dumps/1" "$TEST_ROOT/remove-dumps/5"
|
||||
before_hash=$(sha256sum "$WG_CONF")
|
||||
export WG_SYNC_FAIL_ONCE_FILE="${TEST_ROOT}/remove-sync-fail"
|
||||
printf 'alice — 192.0.2.2/32 — BBBBBBBB\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_remove_flow >/dev/null 2>"${TEST_ROOT}/remove-fail.err"; status=$?; set -e
|
||||
unset WG_SYNC_FAIL_ONCE_FILE
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'remove apply failure returns operation'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'remove apply failure restores server'
|
||||
assert_file_exists "$CLIENT_DIR/alice.conf" 'remove apply failure preserves config'
|
||||
assert_file_exists "$METADATA_DIR/alice.meta" 'remove apply failure preserves metadata'
|
||||
assert_equal 0 "$(find "$ARCHIVE_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'remove apply failure leaves no completed archive'
|
||||
assert_contains 'operation=rollback' "$(<"$LOG_DIR/wg-manager.log")" 'remove rollback is audited'
|
||||
assert_contains 'operation=remove target=alice client=alice key_suffix=BBBBBBB= outcome=failed error=transaction-failed' "$(<"$LOG_DIR/wg-manager.log")" 'remove failure is audited'
|
||||
|
||||
begin_test 'TUI remove artifact recovery failure is critical'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
export M6_ARCHIVE_REMOVE_FAIL_INDEX=1 M6_ARCHIVE_RECOVERY_FAIL_INDEX=1
|
||||
printf 'alice — 192.0.2.2/32 — BBBBBBBB\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_remove_flow 2>&1); status=$?; set -e
|
||||
unset M6_ARCHIVE_REMOVE_FAIL_INDEX M6_ARCHIVE_RECOVERY_FAIL_INDEX
|
||||
assert_equal "$EXIT_ROLLBACK" "$status" 'artifact recovery returns critical status'
|
||||
assert_contains 'КРИТИЧНО' "$output" 'artifact recovery reports critical error'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
export M6_ARCHIVE_REMOVE_FAIL_INDEX=1 M6_ARCHIVE_RECOVERY_FAIL_INDEX=1
|
||||
printf 'Удалить\nalice — 192.0.2.2/32 — BBBBBBBB\ny\nВыход\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_screen >/dev/null 2>"${TEST_ROOT}/screen-critical.err"; status=$?; set -e
|
||||
unset M6_ARCHIVE_REMOVE_FAIL_INDEX M6_ARCHIVE_RECOVERY_FAIL_INDEX
|
||||
assert_equal "$EXIT_ROLLBACK" "$status" 'TUI screen propagates critical remove failure'
|
||||
assert_contains 'КРИТИЧНО' "$(<"${TEST_ROOT}/screen-critical.err")" 'screen critical diagnostic'
|
||||
;;
|
||||
tui-client-actions)
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
begin_test 'managed client details use the selected exact identity'
|
||||
printf 'alice — BBBBBBBB\nДетали\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'managed details succeed'
|
||||
assert_contains 'Публичный ключ: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=' "$(<"$GUM_PAGER_CAPTURE")" 'details show selected key'
|
||||
|
||||
begin_test 'config denial does not read or page client config'
|
||||
printf 'alice — BBBBBBBB\nПоказать конфигурацию\nn\n' >"${TEST_ROOT}/gum-script"; : >"$GUM_PAGER_CAPTURE"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'config denial is normal'
|
||||
assert_equal '' "$(grep -E 'PrivateKey|PresharedKey|EEEE|GGGG|\[Interface\]|\[Peer\]' "$GUM_PAGER_CAPTURE" || true)" 'denied config is not paged'
|
||||
assert_equal '' "$(grep -E 'PrivateKey|PresharedKey|EEEE|GGGG|\[Interface\]' "$TEST_MOCK_LOG" || true)" 'denied config stays out of gum log'
|
||||
|
||||
begin_test 'confirmed config reaches pager only after confirmation'
|
||||
printf 'alice — BBBBBBBB\nПоказать конфигурацию\ny\n' >"${TEST_ROOT}/gum-script"; : >"$GUM_PAGER_CAPTURE"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'confirmed config succeeds'
|
||||
assert_contains 'PrivateKey = EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=' "$(<"$GUM_PAGER_CAPTURE")" 'confirmed config reaches pager stdin'
|
||||
assert_equal '' "$(grep -E 'PrivateKey|PresharedKey|EEEE|GGGG' "$TEST_MOCK_LOG" || true)" 'confirmed config absent from gum argv log'
|
||||
|
||||
begin_test 'QR denial never invokes qrencode'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
printf 'alice — BBBBBBBB\nПоказать QR\nn\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"; : >"$TEST_MOCK_LOG"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'QR denial is normal'
|
||||
assert_equal 0 "$(grep -c '^qrencode ' "$TEST_MOCK_LOG" || true)" 'QR denial does not invoke qrencode'
|
||||
|
||||
begin_test 'confirmed QR uses exact argv and config stdin'
|
||||
export QR_MOCK_EXPECTED_FILE="$CLIENT_DIR/alice.conf"
|
||||
printf 'alice — BBBBBBBB\nПоказать QR\ny\n' >"${TEST_ROOT}/gum-script"; : >"$TEST_MOCK_LOG"
|
||||
set +e; output=$(tui_select_client 2>"${TEST_ROOT}/qr-action.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'confirmed QR succeeds'
|
||||
assert_contains 'MOCK-QR' "$output" 'QR output is returned'
|
||||
assert_equal 1 "$(grep -c '^qrencode -t ansiutf8$' "$TEST_MOCK_LOG" || true)" 'QR argv is exact'
|
||||
assert_equal '' "$(grep -E 'PrivateKey|PresharedKey|EEEE|GGGG' "$TEST_MOCK_LOG" "${TEST_ROOT}/qr-action.err" || true)" 'QR action logs stay secret-free'
|
||||
unset QR_MOCK_EXPECTED_FILE
|
||||
|
||||
begin_test 'missing qrencode is a normal fallback'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
local saved_path=$PATH
|
||||
mkdir -p -- "${TEST_ROOT}/qr-no-command"
|
||||
for utility in bash env wg cat stat sort paste date tail mv; do ln -s -- "$(command -v "$utility")" "${TEST_ROOT}/qr-no-command/$utility"; done
|
||||
PATH="${TEST_ROOT}/qr-no-command"
|
||||
printf 'alice — BBBBBBBB\nПоказать QR\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="$TEST_ROOT/gum-script"
|
||||
set +e; output=$(tui_select_client 2>"${TEST_ROOT}/qr-missing-action.err"); status=$?; set -e
|
||||
PATH=$saved_path
|
||||
assert_equal 0 "$status" 'missing QR dependency returns normally'
|
||||
assert_contains "${CLIENT_DIR}/alice.conf" "$output" 'missing QR fallback gives config path'
|
||||
assert_contains 'qrencode не найден' "$(<"${TEST_ROOT}/qr-missing-action.err")" 'missing QR fallback is actionable'
|
||||
|
||||
begin_test 'qrencode failure preserves backend error status'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
export QR_MOCK_FAIL=1
|
||||
printf 'alice — BBBBBBBB\nПоказать QR\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="$TEST_ROOT/gum-script"
|
||||
set +e; tui_select_client >/dev/null 2>"${TEST_ROOT}/qr-fail-action.err"; status=$?; set -e
|
||||
unset QR_MOCK_FAIL
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'QR failure is nonzero'
|
||||
assert_contains 'QR-код недоступен' "$(<"${TEST_ROOT}/qr-fail-action.err")" 'QR failure diagnostic is preserved'
|
||||
|
||||
begin_test 'unmanaged client has details and back actions only'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
CLIENT_NAMES+=(unmanaged); CLIENT_KEYS+=(EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=); CLIENT_ADDRESSES+=(192.0.2.9/32); CLIENT_ENDPOINTS+=(-); CLIENT_HANDSHAKES+=(0); CLIENT_RX+=(0); CLIENT_TX+=(0); CLIENT_STATES+=(unmanaged); CLIENT_ORIGINS+=(unmanaged)
|
||||
printf 'Детали\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="$TEST_ROOT/gum-script"; : >"$GUM_CHOICES_CAPTURE"
|
||||
set +e; tui_client_action_flow unmanaged 1 >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'unmanaged details remain readable'
|
||||
assert_contains 'Детали' "$(<"$GUM_CHOICES_CAPTURE")" 'unmanaged action list includes details'
|
||||
assert_contains 'Назад' "$(<"$GUM_CHOICES_CAPTURE")" 'unmanaged action list includes back'
|
||||
assert_equal '' "$(grep -E 'Показать QR|Удалить|Показать конфигурацию' "$GUM_CHOICES_CAPTURE" || true)" 'unmanaged actions exclude config/QR/remove'
|
||||
|
||||
begin_test 'managed remove action uses exact shared backend'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
cp -- "$TEST_ROOT/remove-dumps/4" "$TEST_ROOT/remove-dumps/3"
|
||||
printf 'alice — BBBBBBBB\nУдалить\ny\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="$TEST_ROOT/gum-script"
|
||||
set +e; output=$(tui_select_client 2>"${TEST_ROOT}/action-remove.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'managed remove action succeeds'
|
||||
assert_equal 0 "$(grep -c 'PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=' "$WG_CONF" || true)" 'managed remove action removes selected peer'
|
||||
assert_contains 'Клиент удалён' "$output" 'managed remove action reports outcome'
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey' "$TEST_MOCK_LOG" "${TEST_ROOT}/action-remove.err" || true)" 'managed action remains secret-free'
|
||||
|
||||
begin_test 'client action cancellation is harmless'
|
||||
prepare_m10_remove_fixture
|
||||
load_client_model >/dev/null
|
||||
printf '\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="$TEST_ROOT/gum-script"
|
||||
set +e; tui_select_client >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'client action cancellation returns normally'
|
||||
;;
|
||||
tui-backup)
|
||||
prepare_m9_fixture
|
||||
begin_test 'TUI backup action creates a real validated backup and reports its path'
|
||||
printf 'Создать резервную копию\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_backup_flow 2>&1); status=$?; set -e
|
||||
assert_equal 0 "$status" 'real backup action success'
|
||||
assert_contains 'Резервная копия создана:' "$output" 'real backup path is displayed'
|
||||
assert_equal 2 "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'real backup directory exists'
|
||||
begin_test 'TUI backup cancellation and validation failure preserve state'
|
||||
before_backup=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | sort)
|
||||
printf 'Отмена\n' >"${TEST_ROOT}/gum-script"
|
||||
set +e; tui_backup_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'backup cancellation'
|
||||
assert_equal "$before_backup" "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | sort)" 'cancel leaves backups unchanged'
|
||||
mv -- "$BACKUP_DIR" "${TEST_ROOT}/backups-real"
|
||||
ln -s -- "${TEST_ROOT}/backups-real" "$BACKUP_DIR"
|
||||
printf 'Создать резервную копию\n' >"${TEST_ROOT}/gum-script"
|
||||
set +e; tui_backup_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_failure "$status" 'unsafe backup root failure is preserved'
|
||||
rm -- "$BACKUP_DIR"
|
||||
mv -- "${TEST_ROOT}/backups-real" "$BACKUP_DIR"
|
||||
;;
|
||||
tui-restore)
|
||||
prepare_m10_restore_fixture
|
||||
begin_test 'TUI restore real success applies validated backup and audits it'
|
||||
backup_path=$(create_backup "$WG_CONF" manual "$WG_INTERFACE"); restore_id=${backup_path##*/}
|
||||
printf '\n# changed before restore\n' >>"$WG_CONF"
|
||||
printf 'Восстановить резервную копию\n%s\n%s\n' "$restore_id" "$restore_id" >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_backup_flow 2>"${TEST_ROOT}/restore.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'real restore succeeds'
|
||||
assert_equal "$(sha256sum "$backup_path/wg.conf" | awk '{print $1}')" "$(sha256sum "$WG_CONF" | awk '{print $1}')" 'restore applies exact backup bytes'
|
||||
assert_contains "$restore_id" "$(<"$GUM_PAGER_CAPTURE")" 'restore preview shows selected id'
|
||||
assert_contains 'manual' "$(<"$GUM_PAGER_CAPTURE")" 'restore preview shows operation details'
|
||||
assert_equal 2 "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'restore keeps current-state and selected backups'
|
||||
assert_contains 'syncconf' "$(<"$TEST_MOCK_LOG")" 'restore applies runtime configuration'
|
||||
assert_contains 'operation=restore' "$(<"$LOG_DIR/wg-manager.log")" 'restore audit is recorded'
|
||||
assert_equal '' "$(grep -E 'PrivateKey|PresharedKey|AAAAAAAA|BBBBBBBB' "$TEST_MOCK_LOG" "$LOG_DIR/wg-manager.log" "$TEST_ROOT/restore.err" || true)" 'restore output and logs are secret-free'
|
||||
|
||||
begin_test 'TUI restore cancellation and typed mismatch preserve state'
|
||||
prepare_m10_restore_fixture
|
||||
backup_path=$(create_backup "$WG_CONF" manual "$WG_INTERFACE"); restore_id=${backup_path##*/}
|
||||
before_hash=$(sha256sum "$WG_CONF"); before_count=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||
printf 'Восстановить резервную копию\n%s\nwrong-id\n' "$restore_id" >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_backup_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal 0 "$status" 'typed restore mismatch is harmless'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'typed mismatch preserves config'
|
||||
assert_equal "$before_count" "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'typed mismatch creates no backup'
|
||||
|
||||
begin_test 'TUI restore rejects an unoffered selection without mutation'
|
||||
prepare_m10_restore_fixture
|
||||
backup_path=$(create_backup "$WG_CONF" manual "$WG_INTERFACE"); restore_id=${backup_path##*/}; before_hash=$(sha256sum "$WG_CONF")
|
||||
printf 'Восстановить резервную копию\nnot-a-backup\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_backup_flow >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_VALIDATION" "$status" 'unoffered restore selection is rejected'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'unoffered restore leaves config unchanged'
|
||||
|
||||
begin_test 'TUI restore apply failure rolls back and retains backup'
|
||||
prepare_m10_restore_fixture
|
||||
backup_path=$(create_backup "$WG_CONF" manual "$WG_INTERFACE"); restore_id=${backup_path##*/}; printf '\n# changed before failed restore\n' >>"$WG_CONF"; before_hash=$(sha256sum "$WG_CONF")
|
||||
export WG_SYNC_FAIL_ONCE_FILE="${TEST_ROOT}/restore-sync-fail"
|
||||
printf 'Восстановить резервную копию\n%s\n%s\n' "$restore_id" "$restore_id" >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_backup_flow >/dev/null 2>"${TEST_ROOT}/restore-fail.err"; status=$?; set -e
|
||||
unset WG_SYNC_FAIL_ONCE_FILE
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'restore apply failure returns operation'
|
||||
assert_equal "$before_hash" "$(sha256sum "$WG_CONF")" 'restore apply failure restores attempted state'
|
||||
assert_contains 'operation=rollback' "$(<"$LOG_DIR/wg-manager.log")" 'restore rollback is audited'
|
||||
assert_contains 'operation=restore target='"$restore_id"' client='"$restore_id"' key_suffix= outcome=failed error=transaction-failed' "$(<"$LOG_DIR/wg-manager.log")" 'restore failure is audited'
|
||||
assert_equal 2 "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'restore failure retains backup copies'
|
||||
|
||||
begin_test 'TUI screen propagates critical restore rollback'
|
||||
prepare_m10_restore_fixture
|
||||
backup_path=$(create_backup "$WG_CONF" manual "$WG_INTERFACE"); restore_id=${backup_path##*/}; printf '\n# changed before critical restore\n' >>"$WG_CONF"
|
||||
export WG_SYNC_FAIL_ONCE_FILE="${TEST_ROOT}/restore-critical-sync" TRANSACTION_ROLLBACK_REPLACE_FAIL=1
|
||||
printf 'Резервные копии\nВосстановить резервную копию\n%s\n%s\nВыход\n' "$restore_id" "$restore_id" >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_screen >/dev/null 2>"${TEST_ROOT}/restore-critical.err"; status=$?; set -e
|
||||
unset WG_SYNC_FAIL_ONCE_FILE TRANSACTION_ROLLBACK_REPLACE_FAIL
|
||||
assert_equal "$EXIT_ROLLBACK" "$status" 'critical restore rollback exits nonzero'
|
||||
assert_contains 'КРИТИЧНО' "$(<"${TEST_ROOT}/restore-critical.err")" 'critical restore diagnostic is preserved'
|
||||
;;
|
||||
tui-backend-failure)
|
||||
prepare_m9_fixture
|
||||
begin_test 'TUI ordinary backup failure remains readable and bounded'
|
||||
rm -rf -- "$BACKUP_DIR"
|
||||
printf 'not a directory\n' >"$BACKUP_DIR"
|
||||
before_backup=0
|
||||
printf 'Резервные копии\nСоздать резервную копию\nВыход\n' >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; output=$(tui_screen 2>"${TEST_ROOT}/backend-failure.err"); status=$?; set -e
|
||||
rm -- "$BACKUP_DIR"
|
||||
mkdir -- "$BACKUP_DIR"
|
||||
chmod 700 -- "$BACKUP_DIR"
|
||||
assert_equal 0 "$status" 'ordinary backend failure returns to menu'
|
||||
assert_contains 'Предупреждение' "$(<"${TEST_ROOT}/backend-failure.err")" 'ordinary failure has context'
|
||||
assert_equal "$before_backup" "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'failed backup creates no entry'
|
||||
|
||||
begin_test 'TUI repeated real backend failures are bounded'
|
||||
prepare_m9_fixture
|
||||
rm -rf -- "$BACKUP_DIR"
|
||||
printf 'not a directory\n' >"$BACKUP_DIR"
|
||||
before_backup=0
|
||||
printf 'Резервные копии\nСоздать резервную копию\nРезервные копии\nСоздать резервную копию\nРезервные копии\nСоздать резервную копию\nВыход\n' >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_screen >"${TEST_ROOT}/bounded.out" 2>"${TEST_ROOT}/bounded.err"; status=$?; set -e
|
||||
rm -- "$BACKUP_DIR"
|
||||
mkdir -- "$BACKUP_DIR"
|
||||
chmod 700 -- "$BACKUP_DIR"
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'three real failures terminate with operation status'
|
||||
assert_contains 'TUI завершён' "$(<"${TEST_ROOT}/bounded.err")" 'bounded failure diagnostic'
|
||||
assert_equal "$before_backup" "$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" 'repeated failures create no backups'
|
||||
|
||||
begin_test 'TUI critical restore rollback propagates without stubs'
|
||||
prepare_m10_restore_fixture
|
||||
local critical_backup critical_id
|
||||
critical_backup=$(create_backup "$WG_CONF" manual "$WG_INTERFACE")
|
||||
critical_id=${critical_backup##*/}
|
||||
printf '\n# changed before critical TUI restore\n' >>"$WG_CONF"
|
||||
export WG_SYNC_FAIL_ONCE_FILE="${TEST_ROOT}/critical-sync" TRANSACTION_ROLLBACK_REPLACE_FAIL=1
|
||||
printf 'Резервные копии\nВосстановить резервную копию\n%s\n%s\nВыход\n' "$critical_id" "$critical_id" >"${TEST_ROOT}/gum-script"
|
||||
export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_screen >"${TEST_ROOT}/critical.out" 2>"${TEST_ROOT}/critical.err"; status=$?; set -e
|
||||
unset WG_SYNC_FAIL_ONCE_FILE TRANSACTION_ROLLBACK_REPLACE_FAIL
|
||||
assert_equal "$EXIT_ROLLBACK" "$status" 'critical rollback exits TUI immediately'
|
||||
assert_contains 'КРИТИЧНО' "$(<"${TEST_ROOT}/critical.err")" 'critical rollback diagnostic is preserved'
|
||||
assert_equal '' "${TUI_GUM_PATH:-}" 'critical route clears gum state'
|
||||
;;
|
||||
tui-no-secret-args)
|
||||
prepare_m10_add_fixture
|
||||
begin_test 'successful add keeps secrets out of TUI arguments and audit'
|
||||
printf 'secretcheck\nПолный туннель\ny\ny\nn\n' >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; add_output=$(tui_add_flow 2>"${TEST_ROOT}/secret-add.err"); status=$?; set -e
|
||||
assert_equal 0 "$status" 'real add for secret boundary succeeds'
|
||||
assert_equal 600 "$(stat -c '%a' "$CLIENT_DIR/secretcheck.conf")" 'generated config is restrictive'
|
||||
assert_contains 'secretcheck.conf' "$add_output" 'successful add reports validated path'
|
||||
secret_stream=$(cat "$TEST_MOCK_LOG" "$LOG_DIR/wg-manager.log" "${TEST_ROOT}/secret-add.err"; printf '%s\n' "$add_output")
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey|\[Interface\]|\[Peer\]' <<<"$secret_stream" || true)" 'successful add outputs and logs are secret-free'
|
||||
|
||||
unset WG_MOCK_DUMP_SEQUENCE_DIR
|
||||
export WG_MOCK_DUMP_FILE="${TEST_ROOT}/dump-sequence/1"
|
||||
load_client_model >/dev/null
|
||||
local secret_label
|
||||
secret_label='secretcheck — FFFFFFFF'
|
||||
begin_test 'confirmed config is sent only to pager stdin'
|
||||
: >"$GUM_PAGER_CAPTURE"; : >"$TEST_MOCK_LOG"
|
||||
printf '%s\nПоказать конфигурацию\ny\n' "$secret_label" >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_select_client >"${TEST_ROOT}/secret-config.out" 2>"${TEST_ROOT}/secret-config.err"; status=$?; set -e
|
||||
assert_equal 0 "$status" 'confirmed config action succeeds'
|
||||
assert_contains 'PrivateKey' "$(<"$GUM_PAGER_CAPTURE")" 'confirmed config reaches pager stdin'
|
||||
secret_stream=$(cat "$TEST_MOCK_LOG" "$LOG_DIR/wg-manager.log" "${TEST_ROOT}/secret-config.out" "${TEST_ROOT}/secret-config.err")
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey|\[Interface\]|\[Peer\]' <<<"$secret_stream" || true)" 'config secrets absent from args/logs/output'
|
||||
|
||||
begin_test 'confirmed QR receives config on stdin without leaking it to gum'
|
||||
export QR_MOCK_EXPECTED_FILE="$CLIENT_DIR/secretcheck.conf"
|
||||
: >"$TEST_MOCK_LOG"
|
||||
printf '%s\nПоказать QR\ny\n' "$secret_label" >"${TEST_ROOT}/gum-script"; export GUM_SCRIPT_FILE="${TEST_ROOT}/gum-script"
|
||||
set +e; tui_select_client >"${TEST_ROOT}/secret-qr.out" 2>"${TEST_ROOT}/secret-qr.err"; status=$?; set -e
|
||||
unset QR_MOCK_EXPECTED_FILE
|
||||
assert_equal 0 "$status" 'confirmed QR action succeeds'
|
||||
assert_contains 'MOCK-QR' "$(<"${TEST_ROOT}/secret-qr.out")" 'QR output is returned'
|
||||
assert_contains 'qrencode -t ansiutf8' "$(<"$TEST_MOCK_LOG")" 'QR argv is exact and nonsecret'
|
||||
secret_stream=$(cat "$TEST_MOCK_LOG" "$LOG_DIR/wg-manager.log" "${TEST_ROOT}/secret-qr.out" "${TEST_ROOT}/secret-qr.err")
|
||||
assert_equal '' "$(grep -E 'EEEE|GGGG|PrivateKey|PresharedKey|\[Interface\]|\[Peer\]' <<<"$secret_stream" || true)" 'QR secrets absent from args/logs/output'
|
||||
;;
|
||||
tui-no-gum)
|
||||
prepare_m9_fixture
|
||||
export WG_MANAGER_GUM="${TEST_ROOT}/missing-gum"
|
||||
@@ -2587,6 +3175,9 @@ EOF
|
||||
set +e; output=$(remove_client alice extra 2>&1); status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'лишний аргумент remove отклонён'
|
||||
assert_equal "$remove_before" "$(sha256sum -- "$fixture")" 'лишний аргумент не меняет конфиг'
|
||||
set +e; output=$(remove_client alice --yes --expected-key BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= 2>&1); status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'публичный remove не принимает ключ в аргументах'
|
||||
assert_equal "$remove_before" "$(sha256sum -- "$fixture")" 'запрещённый ключ не меняет конфиг'
|
||||
set +e; output=$(/bin/bash "$PROJECT_DIR/wg-manager.sh" remove 2>&1); status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'отсутствующее имя remove отклонено'
|
||||
set +e; output=$(remove_client absent --yes 2>&1); status=$?; set -e
|
||||
@@ -3502,7 +4093,7 @@ elif [[ $test_group == add-client || $test_group == address-allocation || $test_
|
||||
run_m4_tests
|
||||
elif [[ $test_group == remove-client || $test_group == remove-cancel || $test_group == remove-exact-block || $test_group == remove-rollback || $test_group == archive-client || $test_group == archive-path-safety || $test_group == remove-preserves-neighbors ]]; then
|
||||
run_m6_tests
|
||||
elif [[ $test_group == gum-resolution || $test_group == tui-routing || $test_group == tui-cancel || $test_group == tui-read-only || $test_group == tui-no-gum || $test_group == tui-non-tty ]]; then
|
||||
elif [[ $test_group == gum-resolution || $test_group == tui-routing || $test_group == tui-cancel || $test_group == tui-read-only || $test_group == tui-no-gum || $test_group == tui-non-tty || $test_group == tui-add || $test_group == tui-remove || $test_group == tui-client-actions || $test_group == tui-backup || $test_group == tui-restore || $test_group == tui-backend-failure || $test_group == tui-no-secret-args ]]; then
|
||||
run_m9_tests
|
||||
elif [[ $test_group == audit || $test_group == audit-no-secrets || $test_group == backup-list || $test_group == restore || $test_group == restore-invalid || $test_group == restore-rollback || $test_group == backup-path-safety || $test_group == runtime-file-modes ]]; then
|
||||
run_m8_tests
|
||||
@@ -3530,7 +4121,7 @@ else
|
||||
run_m8_tests
|
||||
done
|
||||
unset m8_group
|
||||
for m9_group in gum-resolution tui-routing tui-cancel tui-read-only tui-no-gum tui-non-tty; do
|
||||
for m9_group in gum-resolution tui-routing tui-cancel tui-read-only tui-no-gum tui-non-tty tui-add tui-remove tui-client-actions tui-backup tui-restore tui-backend-failure tui-no-secret-args; do
|
||||
test_group=$m9_group
|
||||
run_m9_tests
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user