#!/usr/bin/env bash set -euo pipefail ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) # shellcheck source=tests/lib/assert.sh source "${ROOT_DIR}/tests/lib/assert.sh" maybe_enter_namespace() { if (( EUID == 0 )); then return 0 fi if [[ ${IPF_IN_NAMESPACE:-0} == 1 ]]; then return 0 fi if command -v unshare >/dev/null 2>&1 && unshare -Urn true >/dev/null 2>&1; then exec unshare -Urn env IPF_IN_NAMESPACE=1 bash "$0" fi printf 'SKIP: 交互测试需要 root 或可用的 unshare。\n' exit 0 } maybe_enter_namespace TMP_DIR=$(mktemp -d) trap 'rm -rf "${TMP_DIR}"' EXIT export IPF_STORAGE_DIR="${TMP_DIR}/storage" export IPF_STORAGE_DB="${IPF_STORAGE_DIR}/rules.db" export IPF_LOCK_FILE="${IPF_STORAGE_DIR}/.lock" export IPTABLES_MOCK_DIR="${TMP_DIR}/iptables-mock" export IPTABLES_MOCK_LOG="${TMP_DIR}/iptables.log" export IPTABLES_BIN="${ROOT_DIR}/tests/mocks/iptables" export IP6TABLES_BIN="${ROOT_DIR}/tests/mocks/ip6tables" export IPTABLES_SAVE_BIN="${ROOT_DIR}/tests/mocks/iptables-save" export IP6TABLES_SAVE_BIN="${ROOT_DIR}/tests/mocks/ip6tables-save" export NETFILTER_PERSISTENT_BIN="${ROOT_DIR}/tests/mocks/persist-mock.sh" export PERSIST_MOCK_LOG="${TMP_DIR}/persist.log" export IPF_SKIP_ENV_CHECK=1 export IPF_FORCE_PLAIN_UI=1 export IPF_TEST_INPUTS=$'4\n2\n1\n8080\n1\n127.0.0.1\n80\ninteractive web\ny\n5\n1\n3\n1\ny\n0\n' output=$("${ROOT_DIR}/iptables-forward.sh" 2>&1) assert_contains "${output}" '== 环境状态 ==' 'interactive flow should open environment status view' assert_contains "${output}" '规则总数: 0' 'environment status should show empty rule count before adding' assert_contains "${output}" '== 添加新的转发规则 ==' 'interactive flow should enter add screen' assert_contains "${output}" '描述: interactive web' 'interactive add summary should echo description' assert_contains "${output}" '规则已保存到磁盘。' 'interactive flow should exercise menu save action' assert_contains "${output}" 'interactive web' 'interactive list/delete flow should display the created description' assert_contains "${output}" '规则已添加,UUID=' 'interactive flow should add one managed rule' assert_contains "${output}" '已退出。' 'interactive flow should return cleanly from main menu' assert_eq '0' "$(wc -l < "${IPF_STORAGE_DB}")" 'interactive add/delete flow should leave storage empty' assert_eq '0' "$(wc -l < "${IPTABLES_MOCK_DIR}/state.v4")" 'interactive add/delete flow should leave runtime mock state empty' assert_eq '3' "$(grep -Ec ' -A ' "${IPTABLES_MOCK_LOG}")" 'interactive add flow should emit three IPv4 add commands' assert_eq '3' "$(grep -Ec ' -D ' "${IPTABLES_MOCK_LOG}")" 'interactive delete should emit three delete commands' assert_eq '3' "$(grep -Ec 'persist-mock\.sh save' "${PERSIST_MOCK_LOG}")" 'interactive add/menu-save/delete flow should persist three times' pass 'test_interactive.sh'