new-site/scripts/tests/e2e_fcc_compliance.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

580 lines
23 KiB
Python

"""
E2E test: FCC Compliance Platform
Tests:
1. Compliance wizard page — load, enter FRN, verify checks render
2. 499-A questionnaire — full 7-step flow with de minimis + LIRE classification
3. Entity CRUD API — create, list, get, update, per-entity compliance
4. Guide pages — all 6 return 200
Usage:
python3 -m scripts.tests.e2e_fcc_compliance
python3 -m scripts.tests.e2e_fcc_compliance --headed
"""
from __future__ import annotations
import argparse
import json
import logging
import os
import sys
import time
import uuid
from datetime import datetime
from pathlib import Path
from dotenv import load_dotenv
env_path = Path(__file__).parent / ".env.test"
if env_path.exists():
load_dotenv(env_path)
import requests
from playwright.sync_api import sync_playwright, Page
LOG = logging.getLogger("tests.fcc")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(levelname)s %(message)s",
stream=sys.stdout,
)
SITE_URL = os.environ.get("SITE_URL", "https://dev.performancewest.net")
API_URL = os.environ.get("DEV_API_URL", "http://207.174.124.71:3002")
DEV_DB_URL = os.environ.get(
"DEV_DATABASE_URL",
"postgresql://pw:pw_dev_2026@207.174.124.71:5433/performancewest",
)
SCREENSHOT_DIR = Path(__file__).parent / "screenshots" / "fcc"
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
PASS = 0
FAIL = 0
RESULTS: list[dict] = []
def screenshot(page: Page, name: str) -> Path:
ts = datetime.now().strftime("%H%M%S")
path = SCREENSHOT_DIR / f"{name}_{ts}.png"
try:
page.screenshot(path=str(path), full_page=True, timeout=15000)
LOG.info(" Screenshot: %s", path.name)
except Exception as e:
LOG.warning(" Screenshot failed: %s", e)
return path
def check(label: str, ok: bool, detail: str = ""):
global PASS, FAIL
if ok:
PASS += 1
LOG.info(" PASS: %s", label)
else:
FAIL += 1
LOG.info(" FAIL: %s%s", label, detail)
RESULTS.append({"label": label, "ok": ok, "detail": detail})
# ──────────────────────────────────────────────────────────────────────
# Test 1: Compliance Wizard Page
# ──────────────────────────────────────────────────────────────────────
def test_compliance_wizard(page: Page):
LOG.info("\n=== Test 1: Compliance Wizard Page ===")
page.goto(f"{SITE_URL}/tools/fcc-compliance-check", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(1000)
check("Wizard page loads", "fcc-compliance-check" in page.url)
check("Has FRN input", page.locator("#frn-input").count() > 0)
check("Has Check button", page.locator("#btn-check").count() > 0)
check("Has title", page.locator("text=FCC Compliance Check").count() > 0)
screenshot(page, "fcc_wizard_01_loaded")
# Enter a test FRN and run check
page.fill("#frn-input", "0004309000")
page.click("#btn-check")
page.wait_for_timeout(5000)
screenshot(page, "fcc_wizard_02_results")
# Verify results rendered
results_visible = not page.locator("#results").is_hidden()
check("Results section visible", results_visible)
if results_visible:
checks_count = page.locator("#checks-container > div").count()
check("Has compliance checks rendered", checks_count >= 4, f"got {checks_count}")
# Check for entity header
entity_frn = page.locator("#entity-frn").text_content()
check("FRN displayed in results", "0004309000" in (entity_frn or ""), entity_frn)
# Check for CTA section
has_cta = page.locator("text=Need help with FCC compliance").count() > 0
check("CTA section present", has_cta)
# Test with entity ID parameter
page.goto(f"{SITE_URL}/tools/fcc-compliance-check?frn=0004309000", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(5000)
screenshot(page, "fcc_wizard_03_url_param")
check("Auto-runs with FRN in URL", not page.locator("#results").is_hidden())
# ──────────────────────────────────────────────────────────────────────
# Test 2: 499-A Questionnaire (Full 7-Step Flow)
# ──────────────────────────────────────────────────────────────────────
def test_499a_questionnaire(page: Page):
LOG.info("\n=== Test 2: 499-A Questionnaire ===")
page.goto(f"{SITE_URL}/order/fcc-499a", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(1000)
check("Questionnaire page loads", "fcc-499a" in page.url)
check("Has pricing tiers", page.locator("text=$499").count() > 0)
screenshot(page, "fcc_499a_01_loaded")
# ── Step 1: Business Classification ──
LOG.info(" Step 1: Business Classification")
# Select "Yes" for provides telecom
page.locator('input[name="provides_telecom"][value="yes"]').check()
page.wait_for_timeout(300)
# PSTN connected = Yes (interconnected VoIP)
page.locator('input[name="pstn_connected"][value="yes"]').check()
page.wait_for_timeout(300)
# Infrastructure = Reseller
page.locator('input[name="infra_type"][value="reseller"]').check()
page.wait_for_timeout(300)
# Verify reseller note appears
reseller_note = page.locator("#reseller-note")
check("Step 1: Reseller note visible", not reseller_note.is_hidden())
screenshot(page, "fcc_499a_02_step1")
page.click("#btn-next")
page.wait_for_timeout(500)
# ── Step 2: Service Categories ──
LOG.info(" Step 2: Service Categories")
check("Step 2 visible", not page.locator("#step-2").is_hidden())
# Select interconnected VoIP + IXC
page.locator('input[name="svc_cat"][value="interconnected_voip"]').check()
page.locator('input[name="svc_cat"][value="ixc"]').check()
page.wait_for_timeout(300)
screenshot(page, "fcc_499a_03_step2")
page.click("#btn-next")
page.wait_for_timeout(500)
# ── Step 3: Revenue Estimation ──
LOG.info(" Step 3: Revenue Estimation")
check("Step 3 visible", not page.locator("#step-3").is_hidden())
page.fill("#total_revenue", "500000")
page.fill("#pct_interstate", "60")
page.fill("#pct_international", "15")
page.fill("#pct_intrastate", "25")
page.fill("#pct_end_user", "90")
page.fill("#pct_wholesale", "10")
# Verify percentage total shows 100%
pct_total_text = page.locator("#pct-total").text_content()
check("Step 3: Pct total = 100%", "100%" in (pct_total_text or ""), pct_total_text)
screenshot(page, "fcc_499a_04_step3")
page.click("#btn-next")
page.wait_for_timeout(500)
# ── Step 4: Classification Results ──
LOG.info(" Step 4: Classification Results")
check("Step 4 visible", not page.locator("#step-4").is_hidden())
# Check filer type
filer_type = page.locator("#result-filer-type").text_content()
check("Step 4: Filer type = Interconnected VoIP", "Interconnected VoIP" in (filer_type or ""), filer_type)
# Check de minimis status
# $500K * 75% (interstate+intl) * 90% (end user) / 4 = $84,375/quarter > $37,175 → Full Contributor
dm_label = page.locator("#result-dm-label").text_content()
check("Step 4: Full contributor (not de minimis)", "Full Contributor" in (dm_label or ""), dm_label)
# Check LIRE status
# Interstate=$300K, International=$75K, ratio = 300K/375K = 80% > 12% → NOT LIRE
lire_label = page.locator("#result-lire-label").text_content() or ""
check("Step 4: Not LIRE eligible", "Not LIRE" in lire_label, lire_label)
# Check required filings list
filings_text = page.locator("#result-filings").text_content() or ""
check("Step 4: Shows 499-A required", "499-A" in filings_text)
check("Step 4: Shows 499-Q required", "499-Q" in filings_text and "exempt" not in filings_text.lower())
screenshot(page, "fcc_499a_05_step4_results")
page.click("#btn-next")
page.wait_for_timeout(500)
# ── Step 5: Company Information ──
LOG.info(" Step 5: Company Information")
check("Step 5 visible", not page.locator("#step-5").is_hidden())
# Verify import section exists
check("Step 5: Import from USAC section present", page.locator("#btn-import").count() > 0)
uid = uuid.uuid4().hex[:6]
page.fill("#entity_name", f"E2E Test VoIP {uid}")
page.fill("#dba_name", "TestVoIP")
page.fill("#ein", "12-3456789")
page.fill("#frn", "0099887766")
page.locator("#new_filer").check()
page.fill("#service_start_date", "2024-06")
page.fill("#address_street", "100 Test Blvd")
page.fill("#address_city", "Houston")
page.fill("#address_state", "TX")
page.fill("#address_zip", "77001")
page.fill("#contact_name", "Test Contact")
page.fill("#contact_email", f"fcc-test+{uid}@performancewest.net")
page.fill("#contact_phone", "+17135551234")
page.fill("#ceo_name", "Jane CEO")
screenshot(page, "fcc_499a_06_step5")
page.click("#btn-next")
page.wait_for_timeout(500)
# ── Step 6: Revenue Detail ──
LOG.info(" Step 6: Revenue Detail")
check("Step 6 visible", not page.locator("#step-6").is_hidden())
# Verify revenue lines populated based on service categories
rev_rows = page.locator("#revenue-lines tr").count()
check("Step 6: Revenue lines populated", rev_rows > 0, f"got {rev_rows} rows")
# Fill some revenue values
rev_inputs = page.locator(".rev-input")
if rev_inputs.count() >= 2:
rev_inputs.first.fill("300000")
rev_inputs.nth(1).fill("50000")
screenshot(page, "fcc_499a_07_step6")
page.click("#btn-next")
page.wait_for_timeout(500)
# ── Step 7: Review & Submit ──
LOG.info(" Step 7: Review & Submit")
check("Step 7 visible", not page.locator("#step-7").is_hidden())
# Verify review summary populated
review_summary = page.locator("#review-summary").text_content() or ""
check("Step 7: Review shows entity name", f"E2E Test VoIP {uid}" in review_summary, review_summary[:80])
# Select service tier
page.locator('input[name="service_tier"][value="499a_499q"]').check()
# Accept disclaimer
page.locator("#disclaimer").check()
page.wait_for_timeout(300)
screenshot(page, "fcc_499a_08_step7_review")
# Submit
page.click("#btn-next")
page.wait_for_timeout(3000)
# Verify success message
submit_status = page.locator("#submit-status")
status_visible = submit_status.is_visible()
check("Step 7: Submit status visible", status_visible)
if status_visible:
status_text = submit_status.text_content() or ""
check("Step 7: Shows success message", "received" in status_text.lower() or "entity" in status_text.lower(), status_text[:100])
# Check entity was created
check("Step 7: Entity ID in response", "Entity ID" in status_text or "received" in status_text.lower(), status_text[:100])
screenshot(page, "fcc_499a_09_submitted")
return uid
# ──────────────────────────────────────────────────────────────────────
# Test 3: Entity CRUD API
# ──────────────────────────────────────────────────────────────────────
def test_entity_api():
LOG.info("\n=== Test 3: Entity CRUD API ===")
uid = uuid.uuid4().hex[:6]
# Create FCC entity
LOG.info(" Creating FCC entity...")
resp = requests.post(f"{API_URL}/api/v1/entities/telecom", json={
"jurisdiction": "FCC",
"legal_name": f"API Test Corp {uid}",
"frn": "0011223344",
"filer_id_499": "999888",
"filer_type": "interconnected_voip",
"infra_type": "reseller",
"is_deminimis": False,
"is_lire": False,
"service_categories": ["interconnected_voip", "ixc"],
"contact_name": "API Tester",
"contact_email": f"api-test+{uid}@performancewest.net",
"address_state": "TX",
"ein": "98-7654321",
}, timeout=10)
check("Create FCC entity: 201", resp.status_code == 201, f"got {resp.status_code}")
fcc_entity = resp.json() if resp.ok else {}
fcc_id = fcc_entity.get("id")
if fcc_id:
check("Entity has ID", fcc_id is not None)
check("Entity legal_name correct", fcc_entity.get("legal_name") == f"API Test Corp {uid}")
check("Entity jurisdiction = FCC", fcc_entity.get("jurisdiction") == "FCC")
check("Entity FRN stored", fcc_entity.get("frn") == "0011223344")
# Create CRTC entity
LOG.info(" Creating CRTC entity...")
resp = requests.post(f"{API_URL}/api/v1/entities/telecom", json={
"jurisdiction": "CRTC",
"legal_name": f"9876543 B.C. Ltd. ({uid})",
"incorporation_number": "BC9876543",
"incorporation_province": "BC",
"crtc_registration_number": f"CRTC-TEST-{uid}",
"contact_email": f"api-test+{uid}@performancewest.net",
}, timeout=10)
check("Create CRTC entity: 201", resp.status_code == 201, f"got {resp.status_code}")
crtc_entity = resp.json() if resp.ok else {}
crtc_id = crtc_entity.get("id")
if crtc_id:
check("CRTC entity jurisdiction", crtc_entity.get("jurisdiction") == "CRTC")
check("CRTC incorporation_province = BC", crtc_entity.get("incorporation_province") == "BC")
# Get single entity
if fcc_id:
LOG.info(" Getting entity by ID...")
resp = requests.get(f"{API_URL}/api/v1/entities/telecom/{fcc_id}", timeout=10)
check("Get entity: 200", resp.status_code == 200, f"got {resp.status_code}")
# Update entity
if fcc_id:
LOG.info(" Updating entity...")
resp = requests.patch(f"{API_URL}/api/v1/entities/telecom/{fcc_id}", json={
"dba_name": "Updated DBA",
"is_deminimis": True,
"notes": "Updated by e2e test",
}, timeout=10)
check("Update entity: 200", resp.status_code == 200, f"got {resp.status_code}")
if resp.ok:
updated = resp.json()
check("Update: dba_name changed", updated.get("dba_name") == "Updated DBA")
check("Update: is_deminimis changed", updated.get("is_deminimis") is True)
# Per-entity compliance check (FCC)
if fcc_id:
LOG.info(" Running per-entity compliance check (FCC)...")
resp = requests.get(f"{API_URL}/api/v1/entities/telecom/{fcc_id}/compliance", timeout=30)
check("FCC compliance check: 200", resp.status_code == 200, f"got {resp.status_code}")
if resp.ok:
data = resp.json()
check("Compliance has entity info", "entity" in data)
check("Compliance has checks", "checks" in data and len(data["checks"]) >= 4, f"got {len(data.get('checks', []))} checks")
# Per-entity compliance check (CRTC)
if crtc_id:
LOG.info(" Running per-entity compliance check (CRTC)...")
resp = requests.get(f"{API_URL}/api/v1/entities/telecom/{crtc_id}/compliance", timeout=10)
check("CRTC compliance check: 200", resp.status_code == 200, f"got {resp.status_code}")
if resp.ok:
data = resp.json()
check("CRTC has entity info", data.get("entity", {}).get("jurisdiction") == "CRTC")
check("CRTC has incorporation check", any(c["id"] == "provincial_incorporation" for c in data.get("checks", [])))
# Validation: missing legal_name
LOG.info(" Testing validation...")
resp = requests.post(f"{API_URL}/api/v1/entities/telecom", json={"jurisdiction": "FCC"}, timeout=10)
check("Missing legal_name: 400", resp.status_code == 400, f"got {resp.status_code}")
# Nonexistent entity
resp = requests.get(f"{API_URL}/api/v1/entities/telecom/99999", timeout=10)
check("Nonexistent entity: 404", resp.status_code == 404, f"got {resp.status_code}")
return fcc_id, crtc_id
# ──────────────────────────────────────────────────────────────────────
# Test 4: Guide Pages (all 6 return 200)
# ──────────────────────────────────────────────────────────────────────
def test_guide_pages():
LOG.info("\n=== Test 4: Guide Pages ===")
guides = [
"/services/telecom/guides/fcc-499a",
"/services/telecom/guides/fcc-499q",
"/services/telecom/guides/rmd-filing",
"/services/telecom/guides/cpni-certification",
"/services/telecom/guides/cores-registration",
"/services/telecom/guides/usac-account-setup",
]
for path in guides:
try:
resp = requests.get(f"{SITE_URL}{path}", timeout=10)
name = path.split("/")[-1]
check(f"Guide {name}: 200", resp.status_code == 200, f"got {resp.status_code}")
except Exception as e:
check(f"Guide {path}: reachable", False, str(e))
# ──────────────────────────────────────────────────────────────────────
# Test 5: Service Pages
# ──────────────────────────────────────────────────────────────────────
def test_service_pages():
LOG.info("\n=== Test 5: Service Pages ===")
pages = [
("/services/telecom/fcc-499a", "FCC 499-A service page"),
("/services/telecom/cpni", "CPNI service page"),
]
for path, label in pages:
try:
resp = requests.get(f"{SITE_URL}{path}", timeout=10)
check(f"{label}: 200", resp.status_code == 200, f"got {resp.status_code}")
if resp.ok:
check(f"{label}: has pricing", "$" in resp.text)
except Exception as e:
check(f"{label}: reachable", False, str(e))
# ──────────────────────────────────────────────────────────────────────
# Test 6: FCC Lookup API
# ──────────────────────────────────────────────────────────────────────
def test_fcc_lookup_api():
LOG.info("\n=== Test 6: FCC Lookup API ===")
# Valid FRN
resp = requests.get(f"{API_URL}/api/v1/fcc/lookup", params={"frn": "0004309000"}, timeout=30)
check("Lookup API: 200", resp.status_code == 200, f"got {resp.status_code}")
if resp.ok:
data = resp.json()
check("Lookup: has frn", data.get("frn") == "0004309000")
check("Lookup: has checks array", "checks" in data and len(data["checks"]) >= 4)
check("Lookup: has checked_at", "checked_at" in data)
# Invalid FRN
resp = requests.get(f"{API_URL}/api/v1/fcc/lookup", params={"frn": "abc"}, timeout=10)
check("Invalid FRN: 400", resp.status_code == 400, f"got {resp.status_code}")
# Missing FRN
resp = requests.get(f"{API_URL}/api/v1/fcc/lookup", timeout=10)
check("Missing FRN: 400", resp.status_code == 400, f"got {resp.status_code}")
# ──────────────────────────────────────────────────────────────────────
# Cleanup
# ──────────────────────────────────────────────────────────────────────
def cleanup():
LOG.info("\n=== Cleanup ===")
try:
import psycopg2
conn = psycopg2.connect(DEV_DB_URL)
cur = conn.cursor()
cur.execute("DELETE FROM telecom_entities WHERE contact_email LIKE '%%test+%%@performancewest.net' OR legal_name LIKE 'E2E Test%%' OR legal_name LIKE 'API Test%%'")
deleted = cur.rowcount
conn.commit()
conn.close()
LOG.info(" Deleted %d test entity(ies)", deleted)
except Exception as e:
LOG.warning(" Cleanup failed: %s", e)
# ──────────────────────────────────────────────────────────────────────
# Main
# ──────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="FCC Compliance Platform E2E Test")
parser.add_argument("--headed", action="store_true", help="Run browser in headed mode")
parser.add_argument("--keep", action="store_true", help="Don't cleanup test data")
args = parser.parse_args()
LOG.info("=" * 60)
LOG.info(" FCC COMPLIANCE PLATFORM E2E TEST")
LOG.info(" Site: %s | API: %s", SITE_URL, API_URL)
LOG.info("=" * 60)
with sync_playwright() as p:
browser = p.chromium.launch(
headless=not args.headed,
args=["--disable-blink-features=AutomationControlled"],
)
context = browser.new_context(
viewport={"width": 1440, "height": 900},
user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
)
page = context.new_page()
page.on("console", lambda msg: LOG.warning("[browser] %s: %s", msg.type, msg.text) if msg.type in ("error",) else None)
# Browser-based tests
test_compliance_wizard(page)
test_499a_questionnaire(page)
browser.close()
# API-only tests
test_entity_api()
test_guide_pages()
test_service_pages()
test_fcc_lookup_api()
if not args.keep:
cleanup()
# Report
LOG.info("\n" + "=" * 60)
LOG.info(" RESULTS: %d passed, %d failed, %d total", PASS, FAIL, PASS + FAIL)
LOG.info("=" * 60)
if FAIL > 0:
LOG.info("\n Failed checks:")
for r in RESULTS:
if not r["ok"]:
LOG.info(" X %s%s", r["label"], r["detail"])
LOG.info("")
LOG.info(" Screenshots: %s", SCREENSHOT_DIR)
if FAIL == 0:
LOG.info(" ALL CHECKS PASSED")
else:
LOG.info(" %d CHECK(S) FAILED", FAIL)
LOG.info("=" * 60)
sys.exit(1 if FAIL > 0 else 0)
if __name__ == "__main__":
main()