aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_packaging_hub_unit.py
blob: e29164ea39dd5ec068c444f3ca490aa0366ccb0c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""
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"