Skip to content

Integration Tests

Integration Tests #234

---
name: Integration Tests
'on':
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'main.yml'
- 'roles/**'
- 'playbooks/**'
- 'library/**'
workflow_dispatch:
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 2 AM
permissions:
contents: read
jobs:
localhost-deployment:
name: Localhost VPN Deployment Test
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
vpn_type: ['wireguard', 'ipsec', 'both']
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1
with:
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: '3.11'
# Note: No pip cache - we use uv for dependency management
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
wireguard \
wireguard-tools \
strongswan \
libstrongswan-standard-plugins \
dnsmasq \
qrencode \
openssl \
linux-headers-$(uname -r) \
libxml2-utils \
dnsutils
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install Python dependencies
run: uv sync
- name: Create test configuration
run: |
cat > integration-test.cfg << EOF
users:
- alice
- bob
cloud_providers:
local:
server: localhost
endpoint: 127.0.0.1
wireguard_enabled: ${{ matrix.vpn_type == 'wireguard' || matrix.vpn_type == 'both' }}
ipsec_enabled: ${{ matrix.vpn_type == 'ipsec' || matrix.vpn_type == 'both' }}
dns_adblocking: true
ssh_tunneling: false
store_pki: true
algo_provider: local
algo_server_name: github-ci-test
server: localhost
algo_ssh_port: 22
CA_password: "test-ca-password-${{ github.run_id }}"
p12_export_password: "test-p12-password-${{ github.run_id }}"
tests: true
no_log: false
ansible_connection: local
dns_encryption: true
algo_dns_adblocking: true
algo_ssh_tunneling: false
BetweenClients_DROP: true
block_smb: true
block_netbios: true
pki_in_tmpfs: true
endpoint: 127.0.0.1
ssh_port: 4160
local_service_ip: 172.16.0.1
local_service_ipv6: "fd00::1"
EOF
- name: Run Algo deployment
run: |
# Run ansible-playbook via uv - become: true in playbook handles root
# GitHub runners have passwordless sudo for become escalation
uv run ansible-playbook main.yml \
-i "localhost," \
-c local \
-e @integration-test.cfg \
-e "provider=local" \
-vv
- name: Verify services are running
run: |
# Check WireGuard
if [[ "${{ matrix.vpn_type }}" == "wireguard" || "${{ matrix.vpn_type }}" == "both" ]]; then
echo "Checking WireGuard..."
sudo wg show
if ! sudo systemctl is-active --quiet wg-quick@wg0; then
echo "✗ WireGuard service not running"
exit 1
fi
echo "✓ WireGuard is running"
fi
# Check StrongSwan (service name is strongswan-starter on Ubuntu 20.04+)
if [[ "${{ matrix.vpn_type }}" == "ipsec" || "${{ matrix.vpn_type }}" == "both" ]]; then
echo "Checking StrongSwan..."
sudo ipsec statusall
if ! sudo systemctl is-active --quiet strongswan-starter; then
echo "✗ StrongSwan service not running"
exit 1
fi
echo "✓ StrongSwan is running"
fi
# Check dnsmasq
if ! sudo systemctl is-active --quiet dnsmasq; then
echo "⚠️ dnsmasq not running (may be expected)"
else
echo "✓ dnsmasq is running"
fi
# Check dnscrypt-proxy
if sudo systemctl is-active --quiet dnscrypt-proxy; then
echo "✓ dnscrypt-proxy is running"
else
echo "⚠️ dnscrypt-proxy not running"
fi
# DNS health check - verify DNS resolution works
echo "Testing DNS resolution via local_service_ip (172.16.0.1)..."
if dig @172.16.0.1 google.com +short +timeout=5 | grep -q .; then
echo "✓ DNS resolution working"
else
echo "⚠️ DNS resolution failed (service may still be starting)"
fi
- name: Verify generated configs
run: |
echo "Checking generated configuration files..."
# WireGuard configs
if [[ "${{ matrix.vpn_type }}" == "wireguard" || "${{ matrix.vpn_type }}" == "both" ]]; then
for user in alice bob; do
if [ ! -f "configs/localhost/wireguard/${user}.conf" ]; then
echo "✗ Missing WireGuard config for ${user}"
exit 1
fi
if [ ! -f "configs/localhost/wireguard/${user}.png" ]; then
echo "✗ Missing WireGuard QR code for ${user}"
exit 1
fi
done
echo "✓ All WireGuard configs generated"
fi
# IPsec configs (p12 in manual/, mobileconfig in apple/)
if [[ "${{ matrix.vpn_type }}" == "ipsec" || "${{ matrix.vpn_type }}" == "both" ]]; then
for user in alice bob; do
if [ ! -f "configs/localhost/ipsec/manual/${user}.p12" ]; then
echo "✗ Missing IPsec certificate for ${user}"
exit 1
fi
if [ ! -f "configs/localhost/ipsec/apple/${user}.mobileconfig" ]; then
echo "✗ Missing IPsec mobile config for ${user}"
exit 1
fi
done
echo "✓ All IPsec configs generated"
fi
- name: Test VPN connectivity
run: |
echo "Testing basic VPN connectivity..."
# Test WireGuard
if [[ "${{ matrix.vpn_type }}" == "wireguard" || "${{ matrix.vpn_type }}" == "both" ]]; then
# Get server's WireGuard public key
SERVER_PUBKEY=$(sudo wg show wg0 public-key)
echo "Server public key: $SERVER_PUBKEY"
# Check if interface has peers
PEER_COUNT=$(sudo wg show wg0 peers | wc -l)
echo "✓ WireGuard has $PEER_COUNT peer(s) configured"
fi
# Test StrongSwan
if [[ "${{ matrix.vpn_type }}" == "ipsec" || "${{ matrix.vpn_type }}" == "both" ]]; then
# Check IPsec policies
sudo ipsec statusall | grep -E "INSTALLED|ESTABLISHED" || echo "No active IPsec connections (expected)"
fi
- name: Run E2E VPN connectivity tests
env:
VPN_TYPE: ${{ matrix.vpn_type }}
run: |
chmod +x tests/e2e/test-vpn-connectivity.sh
sudo tests/e2e/test-vpn-connectivity.sh "${VPN_TYPE}"
- name: Collect E2E debug info on failure
if: failure()
run: |
echo "=== E2E Test Debug Information ==="
echo "=== Network Namespaces ==="
ip netns list || true
echo "=== WireGuard Config (alice) ==="
cat configs/localhost/wireguard/alice.conf 2>/dev/null || echo "Not found"
echo "=== IPsec Certificates ==="
ls -la configs/localhost/ipsec/.pki/certs/ 2>/dev/null || echo "Not found"
echo "=== iptables NAT ==="
sudo iptables -t nat -L -n -v || true
- name: Upload configs as artifacts
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: vpn-configs-${{ matrix.vpn_type }}-${{ github.run_id }}
path: configs/
retention-days: 7
- name: Upload logs on failure
if: failure()
run: |
echo "=== Network Interfaces ==="
ip addr || true
echo "=== Listening Ports ==="
sudo ss -tulnp || true
echo "=== WireGuard Status ==="
sudo wg show || true
echo "=== IPsec Status ==="
sudo ipsec statusall || true
echo "=== DNS Services ==="
sudo systemctl status dnscrypt-proxy dnscrypt-proxy.socket dnsmasq --no-pager || true
echo "=== WireGuard Log ==="
sudo journalctl -u wg-quick@wg0 -n 50 --no-pager || true
echo "=== StrongSwan Log ==="
sudo journalctl -u strongswan -n 50 --no-pager || true
echo "=== dnscrypt-proxy Log ==="
sudo journalctl -u dnscrypt-proxy -n 50 --no-pager || true
echo "=== System Log (last 100 lines) ==="
sudo journalctl -n 100 --no-pager || true
docker-build-test:
name: Docker Image Build Test
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1
with:
persist-credentials: false
- name: Build Algo Docker image
run: |
docker build -t algo:ci-test .
- name: Test Docker image
run: |
# Test that the image can run and show help
docker run --rm --entrypoint /bin/sh algo:ci-test -c "cd /algo && ./algo --help" || true
# Test that required binaries exist in the virtual environment
docker run --rm --entrypoint /bin/sh algo:ci-test -c "cd /algo && uv run which ansible"
docker run --rm --entrypoint /bin/sh algo:ci-test -c "which python3"
docker run --rm --entrypoint /bin/sh algo:ci-test -c "which rsync"
- name: Test Docker config validation
run: |
# Create a minimal valid config
mkdir -p test-data
cat > test-data/config.cfg << 'EOF'
users:
- test-user
cloud_providers:
ec2:
size: t3.micro
region: us-east-1
wireguard_enabled: true
ipsec_enabled: false
dns_encryption: true
algo_provider: ec2
EOF
# Test that config is readable
docker run --rm --entrypoint cat -v $(pwd)/test-data:/data algo:ci-test /data/config.cfg
echo "✓ Docker image built and basic tests passed"