"""Local E2E hub for Phase D — Windows installer against a real HTTP Hub. Boots the control plane against a scratch SQLite DB, seeds a tenant + admin, publishes the real built EXE as release 1.1.1, creates a PENDING enrollment, generates an install code, then serves uvicorn on 127.0.0.1:PORT. The operator/Setup CLI then runs against this local Hub. Usage: python scripts/e2e_local_hub.py --port 8901 --exe dist/installer/GTIFiscalAgent-Setup-1.1.1.exe """ from __future__ import annotations import argparse import dataclasses import os import sys import tempfile import uuid from datetime import datetime, timedelta, timezone from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "central")) from central.app import config, enrollment # noqa: E402 from central.app.models import AgentEnrollment, Tenant # noqa: E402 from central.scripts.publish_agent_release import publish # noqa: E402 from central.tests.test_control_plane import create_admin, seed # noqa: E402 from sqlalchemy import select def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=8765) parser.add_argument("--exe", default=str(ROOT / "dist/installer/GTIFiscalAgent-Setup-1.1.1.exe")) args = parser.parse_args() workdir = Path(tempfile.mkdtemp(prefix="gti_e2e_hub_")) print(f"[e2e] workdir={workdir}") replaced = dataclasses.replace( config.settings, agent_release_storage_path=str(workdir / "releases"), base_url=f"http://127.0.0.1:{args.port}", web_cookie_secure=False, ) config.settings = replaced if "app.config" in sys.modules: sys.modules["app.config"].settings = replaced url = f"sqlite:///{workdir / 'e2e_hub.db'}" os.environ["DATABASE_URL"] = url from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import StaticPool from central.app.models import Base engine = create_engine(url, connect_args={"check_same_thread": False}, poolclass=StaticPool) Base.metadata.create_all(engine) factory = sessionmaker(bind=engine, expire_on_commit=False) seed(factory) with factory() as session: data = Path(args.exe) if not data.exists(): print(f"[e2e] EXE artefact not found: {data}", file=sys.stderr) return 2 release, _created = publish(session, data, version="1.1.1") release_id = release.id print(f"[e2e] published release {release.version} id={release_id}") tenant = session.scalars(select(Tenant).limit(1)).first() enr = AgentEnrollment(enrollment_uuid=str(uuid.uuid4()), tenant_id=tenant.id, display_name="Servidor E2E LOCAL", release_id=release_id, status="PENDING") session.add(enr) session.flush() code = enrollment.generate_install_code() enr.install_code_hash = enrollment.install_code_digest(code) enr.install_code_expires_at = datetime.now(timezone.utc) + timedelta(seconds=86400) enr.install_code_created_at = datetime.now(timezone.utc) session.commit() print(f"[e2e] enrollment_uuid={enr.enrollment_uuid}", flush=True) print(f"[e2e] INSTALL_CODE={code}", flush=True) (workdir / "install_code.txt").write_text(code) (workdir / "enrollment_uuid.txt").write_text(enr.enrollment_uuid) print(f"[e2e] workdir={workdir}", flush=True) import uvicorn from central.app.main import create_app app = create_app(database_probe=lambda: True, session_factory=factory, bootstrap_token=None) print(f"[e2e] serving hub on http://127.0.0.1:{args.port}") uvicorn.run(app, host="127.0.0.1", port=args.port, log_level="info") return 0 if __name__ == "__main__": sys.exit(main())