Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions scripts/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import json
import os
import re
import secrets
import shutil
import subprocess
import sys
Expand Down Expand Up @@ -69,6 +70,7 @@
_api_post_json,
_decoded,
_engine_base,
_ensure_org,
_exec_request,
api_put_json,
)
Expand All @@ -87,6 +89,9 @@
TRANSFORM_APP = "dfe-transform-elastic"
TRANSFORM_REPO = "dfe-transform-elastic"
TRANSFORM_VERSION_KEY = "DFE_TRANSFORM_ELASTIC_VERSION"
# The organisation and account that complete the engine's first-run setup
DEMO_ORG = "demo"
DEMO_ANALYST = "demo-analyst"
RECEIVER_SERVICE = "dfe-receiver"
# dfe-engine main reads the catalogue from its dfe-schemas wheel and older images from the seeded /app/schemas, so this tries both in that order, at any depth because newer wheels file each entry under its tag folder
CATALOGUE_READ = "\n".join(
Expand Down Expand Up @@ -505,6 +510,28 @@ def _counts(*, tables: list[str]) -> dict[str, int | None]:
return {table: _row_count(source=table) for table in tables}


def _complete_setup(*, base: str, token: str) -> None:
"""Make the organisation and the real user the engine's first-run setup asks for.

Without them every console page opens on the setup wizard. Nobody logs in as the analyst, so its password is random and never shown.
"""
org = _ensure_org(base=base, token=token, name=DEMO_ORG)
if org.fault:
raise DemoError(f"the demo organisation: {org.fault}")
account = {
"username": DEMO_ANALYST,
"password": secrets.token_urlsafe(32),
"name": "Demo analyst",
"org": DEMO_ORG,
}
status, answer = _api_post_json(f"{base}/auth/accounts", account, token=token)
if status not in (201, 409):
raise DemoError(
f"creating account {DEMO_ANALYST!r} returned HTTP {status}: {answer}"
)
_print(msg=f"setup complete: organisation {DEMO_ORG!r}, account {DEMO_ANALYST!r}")


def _create(*, base: str, body: dict, token: str) -> dict:
"""Create one source from its file, then return the reply."""
name = body["source"]
Expand Down Expand Up @@ -906,6 +933,7 @@ def _install(*, args: argparse.Namespace, manifest: dict) -> int:

base = _engine_base()
token = _token(base=base)
_complete_setup(base=base, token=token)
replies = []
for body in [*demo.vendors, demo.intake]:
replies += _ensure_source(base=base, body=body, token=token)
Expand Down
42 changes: 42 additions & 0 deletions scripts/tests/test_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import demo
from _common import SOURCE_NAME
from post import HuntOrg

pytestmark = pytest.mark.usefixtures("engine_python_here")

Expand Down Expand Up @@ -652,6 +653,47 @@ def no_container(*, service: str) -> str:
ENGINE_PYTHON(code="", stdin="")


class TestCompleteSetup:
@pytest.mark.parametrize(
("org_fault", "status", "refused"),
[
("", 201, None),
("", 409, None),
("", 500, "returned HTTP 500"),
("POST /orgs returned HTTP 403: no", 201, "the demo organisation"),
],
ids=["created", "already-there", "account-refused", "org-refused"],
)
def test_makes_the_org_then_the_account(
self,
monkeypatch: pytest.MonkeyPatch,
org_fault: str,
status: int,
refused: str | None,
) -> None:
posted: list[dict] = []

def ensure_org(*, base: str, token: str, name: str) -> HuntOrg:
return HuntOrg(name, name, name, True, org_fault)

def post(url: str, payload: dict, token: str = "") -> tuple[int, dict]:
posted.append(payload)
return status, {}

monkeypatch.setattr(demo, "_ensure_org", ensure_org)
monkeypatch.setattr(demo, "_api_post_json", post)

if refused is not None:
with pytest.raises(demo.DemoError, match=refused):
demo._complete_setup(base="http://engine", token="t")
return
demo._complete_setup(base="http://engine", token="t")

assert [p["username"] for p in posted] == [demo.DEMO_ANALYST]
assert posted[0]["org"] == demo.DEMO_ORG
assert len(posted[0]["password"]) >= 32


class TestEventsDir:
def test_the_cases_cover_every_committed_vendor(self) -> None:
sources = sorted(case["source"] for case in EVENTS_DIR_SOURCE_CASES)
Expand Down
Loading