"""Exercise disposable deployment containers through the host's trusted proxy address. The forwarded HTTPS header simulates Nginx TLS termination; this is never run against an existing hotel database. Cookie values and credentials are not logged. """ import json import os import sys import time from http.cookies import SimpleCookie from urllib.request import Request, urlopen from urllib.error import HTTPError cookies = SimpleCookie() csrf = "" def request(path, method="GET", data=None, expected=200): headers = {"X-Forwarded-Proto": "https", "Content-Type": "application/json", "Cookie": "; ".join(f"{k}={v.value}" for k, v in cookies.items()), "X-CSRF-TOKEN": csrf} req = Request("http://127.0.0.1:8080" + path, method=method, headers=headers, data=json.dumps(data).encode() if data is not None else None) try: response = urlopen(req, timeout=15) except HTTPError as error: response = error assert response.status == expected, f"{method} {path}: {response.status}, expected {expected}" for value in response.headers.get_all("Set-Cookie", []): parsed = SimpleCookie(value) for cookie in parsed.values(): assert cookie["secure"] and cookie["httponly"], "Authentication cookies must be secure and HttpOnly" cookies.load(value) body = response.read().decode() return json.loads(body) if "application/json" in response.headers.get("Content-Type", "") else body assert "root" in request("/"), "Container must serve the built React interface" request("/api/hotel", expected=401) session = request("/api/session") assert session["preview"] is False and session["user"] is None csrf = session["csrfToken"] request("/api/auth/login", "POST", {"email": os.environ["BOOTSTRAP_EMAIL"], "password": os.environ["BOOTSTRAP_PASSWORD"]}) session = request("/api/session") assert session["user"]["role"] == "Owner" csrf = session["csrfToken"] assert request("/health/ready") == {"status": "ready"} for attempt in range(10): health = request("/api/operations") if health["worker"]["state"] == "Reporting": break time.sleep(1) assert health["worker"]["state"] == "Reporting" and health["database"] == "Reachable" auto_status = request("/api/auto-replies/status") assert auto_status["liveConfigured"] is False request("/api/auto-replies/mode", "PUT", {"mode": "Live", "version": 0, "acceptanceConfirmed": True}, expected=400) payment_status = request("/api/payments/status") assert payment_status["configured"] is False and payment_status["createsConfigured"] is False assert "securityKey" not in payment_status request("/api/payments/controls", "PUT", {"version": 0, "enabled": True}, expected=400) assert request("/api/payments/requests") == [] hotel = request("/api/hotel") assert "root" in request("/account"), "Container must serve account link page" assert len(request("/api/onboarding")["steps"]) == 5 team = request("/api/team") assert all("passwordHash" not in member and "securityStamp" not in member and "accountLinkHash" not in member for member in team) if "--read" in sys.argv: assert hotel["signature"] == "Persisted across container restart" invited = next(member for member in team if member["email"] == "deployment-staff@example.invalid") assert invited["pending"] and not invited["active"] and invited["linkPurpose"] == "Invite" else: hotel["signature"] = "Persisted across container restart" request("/api/hotel", "PUT", hotel) invite = request("/api/team/invite", "POST", {"name": "Deployment Staff", "email": "deployment-staff@example.invalid"}) assert invite["link"].startswith("https://sandbox-guestops.futuresens.co.uk/account#token=") request("/api/auth/logout", "POST") request("/api/hotel", expected=401) print("Production smoke checks passed: built UI, secure cookies, owner login, persisted settings and staff invitation, onboarding and logout.")