diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_packaging_hub_unit.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_packaging_hub_unit.py | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_packaging_hub_unit.py b/packages/meshbay-hub/tests/test_packaging_hub_unit.py new file mode 100644 index 0000000..7d8f5d2 --- /dev/null +++ b/packages/meshbay-hub/tests/test_packaging_hub_unit.py @@ -0,0 +1,101 @@ +""" +The hub's systemd unit, and the migration step it runs before starting. + +`ExecStartPre` was `alembic -c /opt/meshbay-hub/migrations/alembic.ini upgrade +head`. The file was staged there, so the path existed — but `alembic.ini` +resolves `script_location` with `%(here)s`, so a copy of it is only correct +where it was copied *from*. Staged into `/opt/meshbay-hub/migrations/` it +pointed at `/opt/meshbay-hub/migrations/src/meshbay_hub/db/migrations`, which +nothing installs: the migrations ship inside `meshbay_hub`, in the shared venv. + +`ExecStartPre` failing stops the unit, so **a hub installed from the RPM or the +DEB could not start at all**, and nothing noticed because the reference +deployment was assembled by hand. Exactly the shape of the node unit defect +recorded in `test_packaging_units.py`: a packaged file nobody had installed. + +The same `%(here)s` trap had already been found once on the server, where a +stray `alembic.ini` resolved to a month-old snapshot of the tree +(`QE/server-state/meshbay.org.md`). Twice is a trap, not an accident: the fix +is that nothing carries the path any more. `meshbay-hub migrate` asks the +installed package where its own migrations are. +""" + +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[3] +UNIT = ROOT / "packaging" / "systemd" / "meshbay-hub.service" +BUILD = ROOT / "packaging" / "build" / "build-hub.sh" + +pytestmark = pytest.mark.skipif( + not UNIT.exists(), reason="packaging not present") + + +def _directives() -> list[str]: + """The lines systemd acts on — comments dropped. + + Searching the whole file finds the comment explaining why a directive is + absent and calls that the directive; `test_packaging_units.py` records + that mistake being made. + """ + return [line.strip() for line in UNIT.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.strip().startswith("#")] + + +def _exec_start_pre() -> list[str]: + return [d for d in _directives() if d.startswith("ExecStartPre=")] + + +def test_the_migration_step_runs_the_hubs_own_command(): + pre = _exec_start_pre() + assert len(pre) == 1, f"expected one ExecStartPre, got {pre}" + assert pre[0].endswith("meshbay-hub migrate --config /etc/meshbay/hub.toml"), pre[0] + + +def test_no_directive_names_an_alembic_config_by_path(): + """A path to `alembic.ini` in a unit is the defect itself: the file is only + correct where it was written, and a package moves it.""" + offenders = [d for d in _directives() if "alembic" in d] + assert not offenders, ( + "a unit that names alembic.ini carries a path that the packaging " + f"relocates: {offenders}") + + +def test_the_build_stages_no_alembic_config(): + """Staging a copy is what made the path exist and the contents wrong.""" + staged = [line.strip() for line in BUILD.read_text(encoding="utf-8").splitlines() + if "alembic.ini" in line and not line.strip().startswith("#")] + assert not staged, f"build-hub.sh still stages alembic.ini: {staged}" + + +def test_the_migrations_are_where_the_command_looks_for_them(): + """The other half. The unit is right only if the package carries them — + they are `.py` files inside `meshbay_hub`, so a wheel does, but nothing + said so and nothing would notice if that changed.""" + from meshbay_hub.daemon import migrations_dir + + scripts = migrations_dir() + assert (scripts / "env.py").is_file(), scripts + revisions = list((scripts / "versions").glob("*.py")) + assert revisions, f"no revisions under {scripts}" + + +def test_the_command_resolves_from_the_installed_package_not_the_checkout(): + """Derived from `meshbay_hub.__file__`, so it is correct in a venv, an RPM + and a checkout alike — which is the whole point of not writing it down.""" + import meshbay_hub + + from meshbay_hub.daemon import migrations_dir + + assert migrations_dir() == ( + Path(meshbay_hub.__file__).resolve().parent / "db" / "migrations") + + +def test_the_hub_runs_as_the_service_account_under_the_hardening(): + """Read once while here, because an ExecStartPre that cannot run is not the + only way a unit fails to start.""" + directives = _directives() + for required in ("User=meshbay", "Group=meshbay", + "NoNewPrivileges=true", "ProtectSystem=strict"): + assert required in directives, f"{required} is not in the unit" |