new-site/scripts/tests/e2e_full_order.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

312 lines
11 KiB
Python

"""
E2E test: full CRTC order flow on dev.performancewest.net
- Fill all form steps
- Submit order
- Verify PG record
- Verify ERPNext Customer + Sales Order
- Verify Stripe checkout session created
Run via: docker cp to workers, then exec.
"""
import asyncio
import json
import os
import re
import sys
from datetime import datetime
import psycopg2
import requests
from playwright.async_api import async_playwright, Page
BASE = "https://dev.performancewest.net"
API = "https://api.dev.performancewest.net"
DB_URL = os.getenv("DATABASE_URL", "postgresql://pw:pw_dev_2026@127.0.0.1:5432/performancewest")
# ERPNext
ERP_URL = os.getenv("ERPNEXT_URL", "http://207.174.124.71:8080")
ERP_KEY = os.getenv("ERPNEXT_API_KEY", "")
ERP_SECRET = os.getenv("ERPNEXT_API_SECRET", "")
ERP_SITE = os.getenv("ERPNEXT_SITE_NAME", "performancewest.net")
UNIQUE = datetime.utcnow().strftime("%H%M%S")
TEST_EMAIL = f"e2e+{UNIQUE}@performancewest.net"
TEST_NAME = f"E2E Test {UNIQUE}"
def erp_get(path):
headers = {
"Authorization": f"token {ERP_KEY}:{ERP_SECRET}",
"X-Frappe-Site-Name": ERP_SITE,
}
r = requests.get(f"{ERP_URL}{path}", headers=headers, timeout=15)
return r.json()
async def fill_and_submit(page: Page) -> str:
"""Fill all 5 steps and submit. Returns the page URL after submit (Stripe redirect or error)."""
await page.goto(f"{BASE}/order/canada-crtc?test_mode=1", wait_until="domcontentloaded", timeout=60000)
await page.wait_for_timeout(2000)
# ── Step 1: Company type (numbered is default)
print("Step 1: company type")
await page.click("#btn-next")
await page.wait_for_timeout(500)
# ── Step 2: Director info
print("Step 2: director")
await page.fill("#director_first_name", "John")
await page.fill("#director_middle_name", "Q")
await page.fill("#director_last_name", "Testerton")
await page.select_option("#director_country", "US")
await page.wait_for_timeout(400)
await page.fill("#director_street", "742 Evergreen Terrace")
await page.fill("#director_city", "Springfield")
await page.select_option("#director_province_select", "IL")
await page.evaluate("document.getElementById('director_province_select').dispatchEvent(new Event('change',{bubbles:true}))")
await page.wait_for_timeout(200)
await page.fill("#director_postal", "62704")
try:
await page.select_option("#director_citizenship", "United States", timeout=3000)
except Exception:
pass
await page.click("#btn-next")
await page.wait_for_timeout(500)
# ── Step 3: Services
print("Step 3: services")
textarea = await page.query_selector("#service_description")
if textarea:
await textarea.fill("VoIP and data reseller services — E2E test order")
geo = await page.query_selector("#geographic_coverage")
if geo:
await geo.fill("Canada-wide + US interconnect")
await page.click("#btn-next")
await page.wait_for_timeout(500)
# ── Step 4: Identity (test_mode bypasses)
print("Step 4: identity (test_mode bypass)")
await page.click("#btn-next")
await page.wait_for_timeout(500)
# ── Step 5: Billing + payment
print("Step 5: billing contact + payment")
await page.fill("#customer_name", TEST_NAME)
await page.fill("#customer_email", TEST_EMAIL)
await page.fill("#customer_phone", "+12175551234")
# Select card payment
await page.click('input[name="payment_method_choice"][value="card"]')
await page.wait_for_timeout(200)
# Check consent
await page.check("#consent")
await page.wait_for_timeout(200)
# Submit
print("Clicking Submit Order...")
await page.click("#btn-next")
# Wait for redirect or error
for i in range(20):
await page.wait_for_timeout(1000)
try:
url = page.url
if "checkout.stripe.com" in url:
print(f" Redirected to Stripe at t+{i+1}s")
return url
status = await page.query_selector("#submit-status")
if status:
txt = (await status.inner_text()).strip()
if txt and txt not in ("", "Placing your order...", "Creating your order...", "Redirecting to payment..."):
return f"ERROR: {txt}"
except Exception:
await page.wait_for_timeout(1000)
return page.url
return "TIMEOUT"
def verify_pg_order(order_number: str) -> dict:
"""Check PG for the order and return all fields."""
conn = psycopg2.connect(DB_URL)
try:
with conn.cursor() as cur:
cur.execute("SELECT * FROM canada_crtc_orders WHERE order_number = %s", (order_number,))
cols = [d[0] for d in cur.description]
row = cur.fetchone()
if not row:
return {"error": "Order not found in PG"}
return dict(zip(cols, row))
finally:
conn.close()
def verify_erp_customer(email: str) -> dict:
"""Check ERPNext for the customer."""
data = erp_get(f"/api/resource/Customer?filters=[[\"email_id\",\"=\",\"{email}\"]]&fields=[\"name\",\"customer_name\",\"email_id\"]&limit_page_length=1")
results = data.get("data", [])
return results[0] if results else {"error": "Customer not found in ERPNext"}
def verify_erp_sales_order(order_id: str) -> dict:
"""Check ERPNext for the Sales Order linked to this external order."""
data = erp_get(f"/api/resource/Sales Order?filters=[[\"custom_external_order_id\",\"=\",\"{order_id}\"]]&fields=[\"name\",\"customer\",\"grand_total\",\"status\",\"workflow_state\",\"custom_external_order_id\",\"custom_payment_gateway\"]&limit_page_length=1")
results = data.get("data", [])
if not results:
return {"error": "Sales Order not found in ERPNext"}
so = results[0]
# Get items
items_data = erp_get(f"/api/resource/Sales Order/{so['name']}?fields=[\"items\"]")
items = items_data.get("data", {}).get("items", [])
so["items"] = [{"item_code": i.get("item_code"), "qty": i.get("qty"), "rate": i.get("rate"), "amount": i.get("amount")} for i in items]
return so
async def main():
print("=" * 60)
print(f"E2E FULL ORDER TEST — {datetime.utcnow().isoformat()}")
print(f"Email: {TEST_EMAIL}")
print("=" * 60)
# Step 1: Submit order via Playwright
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=True)
page = await browser.new_page()
errors = []
page.on("pageerror", lambda e: errors.append(str(e)))
result_url = await fill_and_submit(page)
await browser.close()
if errors:
print(f"\nPage errors: {errors}")
print(f"\nResult: {result_url[:120]}")
if "checkout.stripe.com" not in result_url and not result_url.startswith("ERROR"):
print("FAIL: Did not redirect to Stripe")
sys.exit(1)
# Extract order number from the dev API logs or PG
# The order was created before the Stripe redirect — find it by email
print("\n" + "=" * 60)
print("VERIFICATION")
print("=" * 60)
conn = psycopg2.connect(DB_URL)
try:
with conn.cursor() as cur:
cur.execute("SELECT order_number FROM canada_crtc_orders WHERE customer_email = %s ORDER BY created_at DESC LIMIT 1", (TEST_EMAIL.lower(),))
row = cur.fetchone()
if not row:
print("FAIL: No order found in PG for", TEST_EMAIL)
sys.exit(1)
order_number = row[0]
finally:
conn.close()
print(f"\nOrder Number: {order_number}")
# ── Verify PG ──
print("\n--- PostgreSQL Order Record ---")
pg = verify_pg_order(order_number)
if "error" in pg:
print(f"FAIL: {pg['error']}")
else:
checks = {
"order_number": order_number,
"customer_name": TEST_NAME,
"customer_email": TEST_EMAIL.lower(),
"customer_phone": "+12175551234",
"company_type": "numbered",
"director_name": "John Q Testerton",
"director_first_name": "John",
"director_middle_name": "Q",
"director_last_name": "Testerton",
"services_description": lambda v: "E2E test" in (v or ""),
"geographic_coverage": lambda v: "Canada" in (v or ""),
"include_bits": True,
"payment_status": "pending_payment",
"status": "received",
"expedited": False,
"has_own_ca_address": False,
}
pass_count = 0
fail_count = 0
for field, expected in checks.items():
actual = pg.get(field)
if callable(expected):
ok = expected(actual)
else:
ok = actual == expected
status = "PASS" if ok else "FAIL"
if ok:
pass_count += 1
else:
fail_count += 1
print(f" {status}: {field} = {repr(actual)}" + (f" (expected {repr(expected)})" if not ok else ""))
# Check non-null fields
for field in ["stripe_session_id", "service_fee_cents", "total_cents", "director_address", "mailbox_address"]:
val = pg.get(field)
ok = val is not None and val != "" and val != 0
status = "PASS" if ok else "FAIL"
if ok:
pass_count += 1
else:
fail_count += 1
print(f" {status}: {field} is set = {repr(val)[:60]}")
# Check erpnext_sales_order
erp_so_name = pg.get("erpnext_sales_order")
ok = erp_so_name is not None and erp_so_name != ""
status = "PASS" if ok else "FAIL"
if ok:
pass_count += 1
else:
fail_count += 1
print(f" {status}: erpnext_sales_order = {repr(erp_so_name)}")
print(f"\n PG: {pass_count} passed, {fail_count} failed")
# ── Verify ERPNext Customer ──
print("\n--- ERPNext Customer ---")
cust = verify_erp_customer(TEST_EMAIL.lower())
if "error" in cust:
print(f" FAIL: {cust['error']}")
else:
print(f" PASS: Customer found — {cust['name']} ({cust['customer_name']}, {cust['email_id']})")
# ── Verify ERPNext Sales Order ──
print("\n--- ERPNext Sales Order ---")
so = verify_erp_sales_order(order_number)
if "error" in so:
print(f" FAIL: {so['error']}")
else:
print(f" PASS: Sales Order found — {so['name']}")
print(f" customer: {so.get('customer')}")
print(f" grand_total: ${so.get('grand_total')}")
print(f" status: {so.get('status')}")
print(f" workflow_state: {so.get('workflow_state')}")
print(f" payment_gateway: {so.get('custom_payment_gateway')}")
print(f" external_order_id: {so.get('custom_external_order_id')}")
print(f" items:")
for item in so.get("items", []):
print(f" - {item['item_code']} x{item['qty']} @ ${item['rate']} = ${item['amount']}")
# ── Summary ──
print("\n" + "=" * 60)
all_ok = (
"error" not in pg
and "error" not in cust
and "error" not in so
and fail_count == 0
and "checkout.stripe.com" in result_url
)
print(f"RESULT: {'ALL CHECKS PASSED' if all_ok else 'SOME CHECKS FAILED'}")
print("=" * 60)
asyncio.run(main())