#!/usr/bin/env bash # GC-4/5, FUND-25: WOO_BASE_URL validation tests. # Tests WOO_BASE_URL is compose-only HTTPS, startup refusal of http://, # and settings read-only behavior. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" BACKEND_DIR="$REPO_ROOT/backend" APP_HOSTNAME="${APP_HOSTNAME:-slab.local}" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' pass() { echo -e "${GREEN}PASS${NC} $*"; } fail() { echo -e "${RED}FAIL${NC} $*"; exit 1; } warn() { echo -e "${YELLOW}WARN${NC} $*"; } TOTAL=0 PASSED=0 FAILED=0 check() { local label="$1" shift TOTAL=$((TOTAL + 1)) if "$@"; then PASSED=$((PASSED + 1)) pass "$label" else FAILED=$((FAILED + 1)) fail "$label" fi } # ── GC-5: FastAPI refuses http:// WOO_BASE_URL ──────────────────────────────── # Test via Python: import and validate Settings test_gc5_http_rejected() { cd "$BACKEND_DIR" python3 -c " from app.config import Settings import sys try: s = Settings(aes_key='dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk', woo_base_url='http://test.example.com') s.validate() sys.exit(1) # Should have raised ValueError except ValueError as e: if 'https' in str(e).lower(): sys.exit(0) sys.exit(1) " } # ── GC-5: FastAPI refuses missing WOO_BASE_URL ──────────────────────────────── test_gc5_missing_rejected() { cd "$BACKEND_DIR" python3 -c " from app.config import Settings import sys try: s = Settings(aes_key='dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk') s.validate() sys.exit(1) except ValueError as e: if 'required' in str(e).lower() or 'missing' in str(e).lower(): sys.exit(0) sys.exit(1) " } # ── GC-5: HTTPS WOO_BASE_URL is accepted ────────────────────────────────────── test_gc5_https_accepted() { cd "$BACKEND_DIR" python3 -c " from app.config import Settings s = Settings(aes_key='dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk', woo_base_url='https://test.example.com') s.validate() assert s.woo_base_url == 'https://test.example.com' print('OK') " } # ── GC-4: Settings GET returns woo_base_url ─────────────────────────────────── # Test via FastAPI TestClient (requires compose or local server). # If no server is running, we test via Python directly. test_gc4_settings_get_returns_url() { cd "$BACKEND_DIR" python3 -c " from app.config import Settings from app.main import create_app from fastapi.testclient import TestClient settings = Settings( aes_key='dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk', woo_base_url='https://test.example.com', ) app = create_app(settings) with TestClient(app) as client: r = client.get('/api/v1/settings') assert r.status_code == 200, f'Expected 200, got {r.status_code}' data = r.json() assert 'woo_base_url' in data, 'Missing woo_base_url in settings response' assert data['woo_base_url'] == 'https://test.example.com', f'Unexpected woo_base_url: {data[\"woo_base_url\"]}' app.dependency_overrides.clear() print('OK') " } # ── GC-4: Settings PUT rejects woo_base_url ─────────────────────────────────── test_gc4_put_rejects_url() { cd "$BACKEND_DIR" python3 -c " from app.config import Settings from app.main import create_app from fastapi.testclient import TestClient settings = Settings( aes_key='dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk', woo_base_url='https://test.example.com', ) app = create_app(settings) with TestClient(app) as client: r = client.put('/api/v1/settings', json={'woo_base_url': 'https://hacked.example.com'}) assert r.status_code == 422, f'Expected 422, got {r.status_code}' body = r.json() assert 'detail' in body or 'errors' in body, 'Missing error detail' app.dependency_overrides.clear() print('OK') " } # ── FUND-25: Port collision detection (compose-only check) ──────────────────── # Check that FRONTEND_PORT and FASTAPI_PORT defaults don't collide with known services. test_fund25_port_free() { FRONTEND_PORT="${FRONTEND_PORT:-18080}" FASTAPI_PORT="${FASTAPI_PORT:-18081}" if [ "$SKIP_COMPOSE" -eq 0 ]; then # On a real host, check if ports are free for PORT in "$FRONTEND_PORT" "$FASTAPI_PORT"; do if ss -tlnp 2>/dev/null | grep -q ":$PORT "; then echo "Port $PORT is in use" return 1 fi done fi return 0 } # ── Run tests ────────────────────────────────────────────────────────────────── SKIP_COMPOSE=0 if ! command -v docker &>/dev/null; then SKIP_COMPOSE=1 warn "Docker not available — skipping compose-dependent tests" fi echo "" echo "=== WOO_BASE_URL validation tests ===" echo "" check "GC-5: HTTP WOO_BASE_URL rejected" test_gc5_http_rejected check "GC-5: Missing WOO_BASE_URL rejected" test_gc5_missing_rejected check "GC-5: HTTPS WOO_BASE_URL accepted" test_gc5_https_accepted check "GC-4: Settings GET returns woo_base_url" test_gc4_settings_get_returns_url check "GC-4: Settings PUT rejects woo_base_url" test_gc4_put_rejects_url if [ "$SKIP_COMPOSE" -eq 0 ]; then check "FUND-25: Port collision check" test_fund25_port_free else pass "FUND-25: Port collision check (skipped — no Docker)" fi echo "" echo "=== Results: $PASSED passed, $FAILED failed, $TOTAL total ===" if [ "$FAILED" -gt 0 ]; then exit 1 fi exit 0