warpgate/tests/harness/mock-nas.sh
grabbit a2d49137f9 Add comprehensive test suite: 63 integration tests + 110 Rust unit tests
Integration tests (tests/):
- 9 categories covering config, lifecycle, signals, supervision,
  cache, writeback, network faults, crash recovery, and CLI
- Shell-based harness with mock NAS (network namespace + SFTP),
  fault injection (tc netem), and power loss simulation
- TAP format runner (run-all.sh) with proper SKIP detection

Rust unit tests (warpgate/src/):
- 110 tests across 14 modules, all passing in 0.01s
- Config parsing, defaults validation, RestartTracker logic,
  RC API response parsing, rclone arg generation, service
  config generation, CLI output formatting, warmup path logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:21:35 +08:00

167 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Warpgate Integration Test — Mock NAS (SFTP server in network namespace)
#
# Creates a Linux network namespace with a veth pair and runs an SFTP-only
# SSH daemon. This lets tests control "network" behavior (down/up/latency)
# without affecting the host.
#
# Topology:
# Host namespace nas-sim namespace
# veth-wg (10.99.0.1/24) <---> veth-nas (10.99.0.2/24)
# └─ sshd (SFTP on :22)
# └─ NAS root: $NAS_ROOT
set -euo pipefail
MOCK_NAS_NS="${MOCK_NAS_NS:-nas-sim}"
MOCK_NAS_IP="${MOCK_NAS_IP:-10.99.0.2}"
HOST_IP="${HOST_IP:-10.99.0.1}"
MOCK_NAS_SSHD_PID=""
start_mock_nas() {
require_root
local sshd_config="$TEST_DIR/sshd_config"
local host_key="$TEST_DIR/ssh_host_key"
local auth_keys="$TEST_DIR/authorized_keys"
# Generate host key for sshd
ssh-keygen -t ed25519 -f "$host_key" -N "" -q
# Set up authorized_keys from the test key
cp "$TEST_SSH_PUBKEY" "$auth_keys"
chmod 600 "$auth_keys"
# Create network namespace
ip netns add "$MOCK_NAS_NS" 2>/dev/null || true
# Create veth pair
ip link add veth-wg type veth peer name veth-nas 2>/dev/null || true
# Move one end into the namespace
ip link set veth-nas netns "$MOCK_NAS_NS"
# Configure host side
ip addr add "$HOST_IP/24" dev veth-wg 2>/dev/null || true
ip link set veth-wg up
# Configure namespace side
ip netns exec "$MOCK_NAS_NS" ip addr add "$MOCK_NAS_IP/24" dev veth-nas 2>/dev/null || true
ip netns exec "$MOCK_NAS_NS" ip link set veth-nas up
ip netns exec "$MOCK_NAS_NS" ip link set lo up
# Write sshd config (SFTP-only, no password auth, restricted to NAS_ROOT)
cat > "$sshd_config" <<SSHD_EOF
Port 22
ListenAddress $MOCK_NAS_IP
HostKey $host_key
PidFile $TEST_DIR/sshd.pid
AuthorizedKeysFile $auth_keys
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM no
Subsystem sftp internal-sftp
ForceCommand internal-sftp -d /
ChrootDirectory $NAS_ROOT
StrictModes no
LogLevel ERROR
SSHD_EOF
# Ensure NAS root is owned by root (sshd ChrootDirectory requirement)
chown root:root "$NAS_ROOT"
chmod 755 "$NAS_ROOT"
# Start sshd inside the namespace
ip netns exec "$MOCK_NAS_NS" /usr/sbin/sshd -f "$sshd_config" -E "$TEST_DIR/sshd.log"
# Read PID
if [[ -f "$TEST_DIR/sshd.pid" ]]; then
MOCK_NAS_SSHD_PID=$(cat "$TEST_DIR/sshd.pid")
fi
# Wait for sshd to be ready
local deadline=$((SECONDS + 10))
while [[ $SECONDS -lt $deadline ]]; do
if ip netns exec "$MOCK_NAS_NS" bash -c "echo > /dev/tcp/$MOCK_NAS_IP/22" 2>/dev/null; then
break
fi
sleep 0.5
done
export MOCK_NAS_SSHD_PID
}
stop_mock_nas() {
# Kill sshd
if [[ -n "${MOCK_NAS_SSHD_PID:-}" ]] && kill -0 "$MOCK_NAS_SSHD_PID" 2>/dev/null; then
kill "$MOCK_NAS_SSHD_PID" 2>/dev/null || true
wait "$MOCK_NAS_SSHD_PID" 2>/dev/null || true
fi
# Also kill by PID file
if [[ -f "${TEST_DIR:-}/sshd.pid" ]]; then
local pid
pid=$(cat "$TEST_DIR/sshd.pid" 2>/dev/null || true)
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
fi
fi
# Clean up veth pair (deleting one end removes both)
ip link del veth-wg 2>/dev/null || true
# Delete network namespace
ip netns del "$MOCK_NAS_NS" 2>/dev/null || true
MOCK_NAS_SSHD_PID=""
}
# Verify mock NAS is reachable via SFTP
verify_mock_nas() {
sftp -i "$TEST_SSH_KEY" \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ConnectTimeout=5 \
-P 22 \
"root@$MOCK_NAS_IP" <<< "ls" > /dev/null 2>&1
}
# Create a file directly on the mock NAS filesystem
nas_create_file() {
local path="$1"
local size_kb="${2:-1}"
local full_path="$NAS_ROOT/$path"
mkdir -p "$(dirname "$full_path")"
dd if=/dev/urandom of="$full_path" bs=1K count="$size_kb" 2>/dev/null
}
# Create a file with specific content on the mock NAS
nas_create_file_content() {
local path="$1"
local content="$2"
local full_path="$NAS_ROOT/$path"
mkdir -p "$(dirname "$full_path")"
echo -n "$content" > "$full_path"
}
# Read a file from the mock NAS
nas_read_file() {
local path="$1"
cat "$NAS_ROOT/$path"
}
# Check if a file exists on the mock NAS
nas_file_exists() {
local path="$1"
[[ -f "$NAS_ROOT/$path" ]]
}
# Get file checksum on the mock NAS
nas_file_checksum() {
local path="$1"
md5sum "$NAS_ROOT/$path" | awk '{print $1}'
}