aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_packaging_units.py
blob: 2b9ad73e07c778471000f803c1a6e3d440381019 (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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""
The systemd units, and which directory each belongs in.

`meshbay-node.spec` installed the SYSTEM template — the one carrying `User=%i` —
into the *user* unit directory. A user unit already runs as its owner and cannot
carry `User=`; systemd refuses the file, so the packaged unit could never have
started. Nothing caught it because nothing had built and installed the RPM.

These read the files rather than installing them: no rpmbuild here. Weak
evidence, and enough for this defect, which is a file in the wrong place.
"""

from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parents[3]
SYSTEMD = ROOT / "packaging" / "systemd"
SPEC = ROOT / "packaging" / "rpm" / "meshbay-node.spec"
# Where the files are actually placed: the spec's %install is a blind copy of
# this script's output (see test_each_unit_is_installed_where_it_can_run).
BUILD_NODE = ROOT / "packaging" / "build" / "build-node.sh"

pytestmark = pytest.mark.skipif(not SPEC.exists(), reason="packaging not present")


def _system_unit() -> str:
    return (SYSTEMD / "meshbay-node.service").read_text(encoding="utf-8")


def _user_unit() -> str:
    return (SYSTEMD / "meshbay-node-user.service").read_text(encoding="utf-8")


def _directives(unit: str) -> 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. The same mistake as reading a CSP out
    of the HTML comment above the meta tag.
    """
    return [line.strip() for line in unit.splitlines()
            if line.strip() and not line.strip().startswith("#")]


def test_the_system_unit_is_a_template_that_names_its_user():
    directives = _directives(_system_unit())
    assert any(d == "User=%i" for d in directives), (
        "the system template must run as the instance name")
    assert any("%h" in d for d in directives), "it reads the instance's own home"


def test_the_user_unit_names_no_user():
    """
    It already runs as its owner. `User=` in a user unit is not ignored —
    systemd refuses to load the file at all.
    """
    directives = _directives(_user_unit())
    assert not any(d.startswith("User=") for d in directives)
    assert not any(d.startswith("Group=") for d in directives)


def test_each_unit_is_installed_where_it_can_run():
    """
    Read where the units are *staged*, not what the spec says.

    This used to inspect `meshbay-node.spec`'s `%install` for `install -D` lines
    and their destination on the following line. The packaging overhaul
    (2026-08-31) replaced that section with `cp -a %{_staging_root}/*
    %{buildroot}/`: the spec no longer places individual files, `build-node.sh`
    does, and the spec only declares them in `%files`. The test kept reading the
    spec and failed for months against packaging that was correct all along —
    checking a mechanism that no longer existed while the property it defends
    still held.

    So it now reads the script that actually places them. Same guarantee, aimed
    at the thing that does the work: the template — the one carrying `User=%i` —
    into the system directory, and the user unit, which cannot carry `User=`,
    into the user one. systemd refuses the file outright if these are swapped,
    which is how the original defect presented.
    """
    stage = BUILD_NODE.read_text(encoding="utf-8")

    # The template goes to the system directory, instantiated per person.
    assert "usr/lib/systemd/system/meshbay-node@.service" in stage, (
        "the system template is not staged into the system unit directory")
    # The user unit goes to the user directory, enabled without a password.
    assert "usr/lib/systemd/user/meshbay-node.service" in stage, (
        "the per-user unit is not staged into the user unit directory")

    # And each comes from the right source file — the two are one `cp` apart,
    # so a swap would put `User=%i` where systemd will not load it.
    for source, destination in (
        ("packaging/systemd/meshbay-node.service",
         "usr/lib/systemd/system/meshbay-node@.service"),
        ("packaging/systemd/meshbay-node-user.service",
         "usr/lib/systemd/user/meshbay-node.service"),
    ):
        i = stage.index(source)
        assert destination in stage[i:i + 200], (
            f"{source} is not staged to {destination} — check the cp pair in "
            "build-node.sh")


def test_both_units_are_listed_in_files():
    files = SPEC.read_text(encoding="utf-8").split("%files")[1]
    assert "%{_unitdir}/meshbay-node@.service" in files
    assert "%{_userunitdir}/meshbay-node.service" in files


def test_the_user_unit_can_be_reloaded_without_dropping_anyone():
    """
    `meshbay-node reload` sends SIGHUP so a group's directories can change
    without restarting. Without ExecReload the desktop client's reload would
    have to stop the service, which drops whoever is watching a film.
    """
    directives = _directives(_user_unit())
    reload_line = next((d for d in directives if d.startswith("ExecReload=")), "")
    assert reload_line, "no ExecReload"
    assert "HUP" in reload_line


def test_the_user_unit_documents_how_a_drive_outside_home_is_added():
    """
    ProtectSystem=strict hides it, and a volume mounted after the service
    started is invisible inside the unit's mount namespace — so the drop-in
    needs RequiresMountsFor as well as ReadWritePaths. Written down where
    somebody debugging an empty directory will find it.
    """
    unit = _user_unit()
    assert "ProtectSystem=strict" in _directives(unit)
    # These two belong in the comment: they are what an operator has to write in
    # a drop-in, not what this file declares.
    assert "RequiresMountsFor" in unit
    assert "meshbay-node.service.d" in unit