"""Shared discourse test helpers — admin user + API key + JSON HTTP. The bitnamilegacy/discourse recipe (compose app env) sets ONLY the DB + SMTP vars — it does NOT seed an admin user, and Discourse's Admin API requires `Api-Key` + `Api-Username` headers (there is no auto-created API key). So the functional tests bootstrap their own admin by running Rails inside the `app` container (Discourse ships its Rails env at /opt/bitnami/discourse): find-or-create an admin user, then create an ApiKey and print its plaintext `.key` (Discourse returns the plaintext only at create time; it's stored hashed). Both the admin user and the key are class-B run-scoped — they live only in the per-run app and are destroyed at teardown. `mint_admin(domain)` returns (api_key, api_username). Each call creates a fresh ApiKey (cheap; idempotent enough — the shared deployment's functional tests each mint their own), reusing the same admin user across calls. Uses `lifecycle.exec_in_app` (hardened exec with retry). """ from __future__ import annotations import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner")) from harness import lifecycle # noqa: E402 # Rails snippet (single line): find-or-create an admin, create an ApiKey, print key + username as the # last two lines. SecureRandom is available in the Rails runtime. We mark the user active + approved # so the API accepts it. created_by_id must be set (ApiKey validates it). _BOOTSTRAP_RB = ( "u = User.where(admin: true).order(:id).first; " "if u.nil?; " "u = User.create!(username: 'ccciadmin', name: 'CCCI Admin', " "email: 'ccciadmin@ccci.example.com', password: SecureRandom.hex(20), " "active: true, approved: true, trust_level: 1); " "u.update!(admin: true); u.activate; " "end; " "k = ApiKey.create!(description: 'ccci-run', created_by_id: u.id); " "puts 'CCCI_API_KEY=' + k.key; " "puts 'CCCI_API_USER=' + u.username" ) def mint_admin(domain: str) -> tuple[str, str]: """Bootstrap an admin + fresh API key via Rails in the app container. Returns (api_key, username).""" cmd = ( "cd /opt/bitnami/discourse && " f"RAILS_ENV=production bin/rails runner \"{_BOOTSTRAP_RB}\"" ) out = lifecycle.exec_in_app(domain, ["bash", "-lc", cmd], service="app", timeout=240) key = user = None for line in out.splitlines(): line = line.strip() if line.startswith("CCCI_API_KEY="): key = line.split("=", 1)[1].strip() elif line.startswith("CCCI_API_USER="): user = line.split("=", 1)[1].strip() assert key and user, ( f"could not bootstrap discourse admin/API key; rails output tail:\n{out[-1000:]}" ) return key, user def admin_headers(api_key: str, api_username: str) -> dict[str, str]: return {"Api-Key": api_key, "Api-Username": api_username}