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>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
0
scripts/tests/providers/__init__.py
Normal file
0
scripts/tests/providers/__init__.py
Normal file
137
scripts/tests/providers/flowroute_client.py
Normal file
137
scripts/tests/providers/flowroute_client.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
Flowroute API client for Canadian DID provisioning.
|
||||
|
||||
Docs: https://developer.flowroute.com/api/numbers/v2.0/
|
||||
Auth: HTTP Basic with access_key:secret_key.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
LOG = logging.getLogger("tests.flowroute")
|
||||
|
||||
API_BASE = "https://api.flowroute.com"
|
||||
|
||||
|
||||
class FlowrouteClient:
|
||||
def __init__(
|
||||
self,
|
||||
access_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
):
|
||||
self.access_key = access_key or os.environ["FLOWROUTE_ACCESS_KEY"]
|
||||
self.secret_key = secret_key or os.environ["FLOWROUTE_SECRET_KEY"]
|
||||
self.session = requests.Session()
|
||||
self.session.auth = (self.access_key, self.secret_key)
|
||||
self.session.headers["Accept"] = "application/json"
|
||||
|
||||
# BC area codes in preference order
|
||||
BC_AREA_CODES = ["1604", "1778", "1236", "1250"]
|
||||
|
||||
def search_available_dids(
|
||||
self,
|
||||
starts_with: str = "",
|
||||
limit: int = 5,
|
||||
number_type: str = "standard",
|
||||
province: str = "BC",
|
||||
) -> list[dict]:
|
||||
"""Search for available Canadian DIDs.
|
||||
|
||||
If starts_with is empty and province is BC, tries all BC area codes
|
||||
(604, 778, 236, 250) until DIDs are found.
|
||||
|
||||
Args:
|
||||
starts_with: Number prefix (e.g. "1604"). If empty, searches all BC codes.
|
||||
limit: Max results per area code
|
||||
number_type: 'standard' or 'tollfree'
|
||||
province: Province hint — used to pick area codes when starts_with is empty
|
||||
|
||||
Returns: List of {"did": "+1604...", "monthly_cost": "1.25", ...}
|
||||
"""
|
||||
prefixes = [starts_with] if starts_with else self.BC_AREA_CODES
|
||||
|
||||
for prefix in prefixes:
|
||||
params = {
|
||||
"starts_with": prefix,
|
||||
"limit": limit,
|
||||
"number_type": number_type,
|
||||
}
|
||||
|
||||
r = self.session.get(f"{API_BASE}/v2/numbers/available", params=params, timeout=15)
|
||||
|
||||
if r.status_code != 200:
|
||||
LOG.warning("Flowroute search %s: HTTP %d", prefix, r.status_code)
|
||||
continue
|
||||
|
||||
data = r.json()
|
||||
results = []
|
||||
for item in data.get("data", []):
|
||||
did_id = item.get("id", "")
|
||||
attrs = item.get("attributes", {})
|
||||
results.append({
|
||||
"did": did_id,
|
||||
"rate_center": attrs.get("rate_center", ""),
|
||||
"state": attrs.get("state", ""),
|
||||
"monthly_cost": attrs.get("monthly_cost", ""),
|
||||
"number_type": attrs.get("number_type", ""),
|
||||
})
|
||||
|
||||
if results:
|
||||
LOG.info("Flowroute search '%s': %d results", prefix, len(results))
|
||||
return results
|
||||
|
||||
LOG.info("Flowroute search '%s': 0 results, trying next area code", prefix)
|
||||
|
||||
LOG.warning("Flowroute: no DIDs found in any BC area code")
|
||||
return []
|
||||
|
||||
def purchase_did(self, did: str) -> dict:
|
||||
"""Purchase a DID.
|
||||
|
||||
Args:
|
||||
did: The phone number to purchase (e.g. "16045551234")
|
||||
|
||||
Returns: {"success": bool, "did": str, ...}
|
||||
"""
|
||||
r = self.session.post(f"{API_BASE}/v2/numbers/{did}", timeout=15)
|
||||
|
||||
if r.status_code in (200, 201):
|
||||
LOG.info("Flowroute purchased DID: %s", did)
|
||||
return {"success": True, "did": did, "raw": r.json()}
|
||||
else:
|
||||
LOG.error("Flowroute purchase failed: %d %s", r.status_code, r.text[:200])
|
||||
return {"success": False, "did": did, "error": r.text[:200]}
|
||||
|
||||
def release_did(self, did: str) -> dict:
|
||||
"""Release (cancel) a purchased DID."""
|
||||
r = self.session.delete(f"{API_BASE}/v2/numbers/{did}", timeout=15)
|
||||
|
||||
if r.status_code in (200, 204):
|
||||
LOG.info("Flowroute released DID: %s", did)
|
||||
return {"success": True, "did": did}
|
||||
else:
|
||||
LOG.error("Flowroute release failed: %d %s", r.status_code, r.text[:200])
|
||||
return {"success": False, "did": did, "error": r.text[:200]}
|
||||
|
||||
def list_dids(self, limit: int = 10) -> list[dict]:
|
||||
"""List currently owned DIDs."""
|
||||
r = self.session.get(
|
||||
f"{API_BASE}/v2/numbers",
|
||||
params={"limit": limit},
|
||||
timeout=15,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
return []
|
||||
return [
|
||||
{"did": item.get("id", ""), **item.get("attributes", {})}
|
||||
for item in r.json().get("data", [])
|
||||
]
|
||||
|
||||
def ping(self) -> bool:
|
||||
"""Verify API credentials by listing DIDs (simplest authenticated call)."""
|
||||
r = self.session.get(f"{API_BASE}/v2/numbers", params={"limit": 1}, timeout=10)
|
||||
ok = r.status_code == 200
|
||||
LOG.info("Flowroute API ping: %s", "OK" if ok else f"FAIL {r.status_code}")
|
||||
return ok
|
||||
122
scripts/tests/providers/porkbun_client.py
Normal file
122
scripts/tests/providers/porkbun_client.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
Porkbun API client for .ca domain operations.
|
||||
|
||||
Docs: https://porkbun.com/api/json/v3/documentation
|
||||
All endpoints are POST with JSON body containing apikey + secretapikey.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
LOG = logging.getLogger("tests.porkbun")
|
||||
|
||||
API_BASE = "https://api.porkbun.com/api/json/v3"
|
||||
|
||||
|
||||
class PorkbunClient:
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
):
|
||||
self.api_key = api_key or os.environ["PORKBUN_API_KEY"]
|
||||
self.secret_key = secret_key or os.environ["PORKBUN_SECRET_KEY"]
|
||||
self.session = requests.Session()
|
||||
self.session.headers["Content-Type"] = "application/json"
|
||||
|
||||
def _auth_body(self, **extra) -> dict:
|
||||
return {"apikey": self.api_key, "secretapikey": self.secret_key, **extra}
|
||||
|
||||
def ping(self) -> bool:
|
||||
"""Verify API credentials are valid."""
|
||||
r = self.session.post(f"{API_BASE}/ping", json=self._auth_body())
|
||||
data = r.json()
|
||||
if data.get("status") == "SUCCESS":
|
||||
LOG.info("Porkbun API ping OK — IP: %s", data.get("yourIp"))
|
||||
return True
|
||||
LOG.error("Porkbun ping failed: %s", data)
|
||||
return False
|
||||
|
||||
def check_availability(self, domain: str) -> dict:
|
||||
"""Check if a domain is available for registration.
|
||||
|
||||
Returns: {"available": bool, "price": str, "currency": str}
|
||||
"""
|
||||
r = self.session.post(
|
||||
f"{API_BASE}/domain/checkDomainAvailability/{domain}",
|
||||
json=self._auth_body(),
|
||||
timeout=15,
|
||||
)
|
||||
data = r.json()
|
||||
avail = data.get("status") == "SUCCESS" and data.get("pricing")
|
||||
price = ""
|
||||
currency = ""
|
||||
if avail and data.get("pricing"):
|
||||
# Pricing is keyed by TLD
|
||||
tld = domain.split(".", 1)[1] if "." in domain else ""
|
||||
tld_pricing = data["pricing"].get(tld, {})
|
||||
price = tld_pricing.get("registration", "")
|
||||
currency = tld_pricing.get("currency", "USD")
|
||||
|
||||
return {
|
||||
"available": avail,
|
||||
"domain": domain,
|
||||
"price": price,
|
||||
"currency": currency,
|
||||
"raw": data,
|
||||
}
|
||||
|
||||
def register_domain(
|
||||
self,
|
||||
domain: str,
|
||||
years: int = 1,
|
||||
nameservers: Optional[list[str]] = None,
|
||||
) -> dict:
|
||||
"""Register a domain. Returns registration details."""
|
||||
body = self._auth_body(domain=domain, years=years)
|
||||
if nameservers:
|
||||
body["ns"] = nameservers
|
||||
|
||||
r = self.session.post(
|
||||
f"{API_BASE}/domain/register/{domain}",
|
||||
json=body,
|
||||
timeout=30,
|
||||
)
|
||||
data = r.json()
|
||||
success = data.get("status") == "SUCCESS"
|
||||
LOG.info("Porkbun register %s: %s", domain, "OK" if success else data.get("message"))
|
||||
return {"success": success, "domain": domain, "raw": data}
|
||||
|
||||
def get_dns_records(self, domain: str) -> list[dict]:
|
||||
"""List DNS records for a domain."""
|
||||
r = self.session.post(
|
||||
f"{API_BASE}/dns/retrieve/{domain}",
|
||||
json=self._auth_body(),
|
||||
timeout=10,
|
||||
)
|
||||
data = r.json()
|
||||
return data.get("records", [])
|
||||
|
||||
def add_dns_record(
|
||||
self, domain: str, record_type: str, name: str, content: str, ttl: int = 300
|
||||
) -> dict:
|
||||
"""Add a DNS record."""
|
||||
r = self.session.post(
|
||||
f"{API_BASE}/dns/create/{domain}",
|
||||
json=self._auth_body(
|
||||
type=record_type, name=name, content=content, ttl=str(ttl)
|
||||
),
|
||||
timeout=10,
|
||||
)
|
||||
return r.json()
|
||||
|
||||
def delete_domain(self, domain: str) -> dict:
|
||||
"""Delete/cancel a domain (for test cleanup)."""
|
||||
r = self.session.post(
|
||||
f"{API_BASE}/domain/delete/{domain}",
|
||||
json=self._auth_body(),
|
||||
timeout=10,
|
||||
)
|
||||
return r.json()
|
||||
Loading…
Add table
Add a link
Reference in a new issue