tests: realism migration regression coverage
Four gaps from the realism migration plan, plus one flaky test
fixed.
Added:
- tests/deploy/test_orchestrator_unit.py — replaces the dead
test_emailgen_unit.py. Asserts:
* decnet-orchestrator.service.j2 carries the DECNET_REALISM_*
env block (LLM, MODEL, TIMEOUT, PERSONAS) so per-host tuning
works without editing the .j2.
* Legacy DECNET_EMAILGEN_* vars are NOT referenced — clean break
contract from stage 5.
* decnet.target wants orchestrator + canary, does NOT want
decnet-emailgen.service. Anti-regression for service-collapse.
* deploy/decnet-emailgen.service.j2 stays deleted.
- tests/orchestrator/test_worker_integration.py — new
test_one_tick_email_branch_records_orchestrator_email. Pins the
action-roll to email, seeds a topology with an IMAP mail decky +
two personas, stubs LLM + docker-exec write paths, verifies an
orchestrator_emails row + bus event land. Restores end-to-end
email coverage that was lost when the pre-collapse
test_worker_integration.py was deleted.
- tests/realism/test_synthetic_files_truncation.py — pins the 64KB
last_body cap on create + edit, and documents the consequence:
edit candidates carry a truncated snapshot of files that exceeded
the cap. If a future change lifts the cap, _LIMIT in the test
must lift with it.
Fixed flaky:
- tests/orchestrator/test_scheduler.py — two pick_file tests
pinned to random.Random(1). Without a seed, the 3% canary gate
(stage 7) and 10% leave-alone roll occasionally flaked the
assertions because the _FakeRepo doesn't carry a
create_canary_token method.
Note: the existing
test_realism_subprocess_import_personas_rejects_in_agent_mode
already covers agent-mode rejection of decnet realism
import-personas; no new gating test needed.
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
"""Smoke tests for the emailgen systemd unit + decnet.target wiring.
|
||||
|
||||
These don't exercise systemd (the test host wouldn't have it), they
|
||||
just assert the static contents of ``deploy/decnet-emailgen.service.j2``
|
||||
and ``deploy/decnet.target`` match what ``decnet init`` will install.
|
||||
A regression here would only surface on a fresh host install — cheap
|
||||
to catch at CI time.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent.parent
|
||||
DEPLOY = REPO / "deploy"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unit_text() -> str:
|
||||
return (DEPLOY / "decnet-emailgen.service.j2").read_text()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def target_text() -> str:
|
||||
return (DEPLOY / "decnet.target").read_text()
|
||||
|
||||
|
||||
# ── unit file ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_emailgen_unit_exists():
|
||||
assert (DEPLOY / "decnet-emailgen.service.j2").exists()
|
||||
|
||||
|
||||
def test_emailgen_unit_uses_run_subcommand(unit_text):
|
||||
"""`decnet emailgen` is a sub-app now — the unit must call `run`,
|
||||
not bare `emailgen` (which still works but is implicit-default and
|
||||
fragile to future changes)."""
|
||||
assert "decnet emailgen run" in unit_text
|
||||
|
||||
|
||||
def test_emailgen_unit_has_docker_supplementary_group(unit_text):
|
||||
"""Driver shells `docker exec` to drop EMLs in the spool — without
|
||||
this group the worker can't reach the docker socket."""
|
||||
assert "SupplementaryGroups=docker" in unit_text
|
||||
|
||||
|
||||
def test_emailgen_unit_orders_after_bus(unit_text):
|
||||
"""Bus must come up first so emailgen's heartbeat publishes land."""
|
||||
assert "After=network-online.target decnet-bus.service" in unit_text
|
||||
assert "Wants=network-online.target decnet-bus.service" in unit_text
|
||||
|
||||
|
||||
def test_emailgen_unit_has_security_hardening(unit_text):
|
||||
"""Same hardening shape as orchestrator.service — defence in depth."""
|
||||
for directive in (
|
||||
"NoNewPrivileges=yes",
|
||||
"ProtectSystem=full",
|
||||
"ProtectHome=read-only",
|
||||
"PrivateTmp=yes",
|
||||
"ProtectKernelTunables=yes",
|
||||
"ProtectKernelModules=yes",
|
||||
"ProtectControlGroups=yes",
|
||||
"RestrictSUIDSGID=yes",
|
||||
"LockPersonality=yes",
|
||||
):
|
||||
assert directive in unit_text, f"missing {directive}"
|
||||
|
||||
|
||||
def test_emailgen_unit_writes_to_log_dir(unit_text):
|
||||
assert "/var/log/decnet/decnet.emailgen.log" in unit_text
|
||||
assert "ReadWritePaths={{ install_dir }} /var/log/decnet" in unit_text
|
||||
|
||||
|
||||
def test_emailgen_unit_restart_on_failure(unit_text):
|
||||
assert "Restart=on-failure" in unit_text
|
||||
|
||||
|
||||
# ── target wiring ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_target_wants_emailgen(target_text):
|
||||
"""A fresh `decnet init` must bring up emailgen with the rest of
|
||||
the fleet."""
|
||||
assert "decnet-emailgen.service" in target_text
|
||||
|
||||
|
||||
def test_target_wants_orchestrator(target_text):
|
||||
"""Orchestrator was an oversight historically — bundling it in here
|
||||
too while we're touching the file."""
|
||||
assert "decnet-orchestrator.service" in target_text
|
||||
151
tests/deploy/test_orchestrator_unit.py
Normal file
151
tests/deploy/test_orchestrator_unit.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""Smoke tests for the orchestrator systemd unit + decnet.target wiring.
|
||||
|
||||
These don't exercise systemd (the test host wouldn't have it); they
|
||||
just assert the static contents of ``deploy/decnet-orchestrator.service.j2``
|
||||
and ``deploy/decnet.target`` match what ``decnet init`` will install.
|
||||
|
||||
Anti-regressions for two specific failure modes:
|
||||
|
||||
1. After the realism migration (stage 5), ``decnet-emailgen.service``
|
||||
is gone — the orchestrator covers the email branch. A regression
|
||||
that re-introduces the emailgen unit file or the ``decnet.target``
|
||||
entry would only surface on a fresh host install; cheap to catch
|
||||
here.
|
||||
2. The orchestrator unit must ship the ``DECNET_REALISM_*`` env block
|
||||
so the LLM enrichment + persona-pool path are configurable per
|
||||
host without editing the .j2.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent.parent
|
||||
DEPLOY = REPO / "deploy"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unit_text() -> str:
|
||||
return (DEPLOY / "decnet-orchestrator.service.j2").read_text()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def target_text() -> str:
|
||||
return (DEPLOY / "decnet.target").read_text()
|
||||
|
||||
|
||||
# ── orchestrator unit ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_orchestrator_unit_exists():
|
||||
assert (DEPLOY / "decnet-orchestrator.service.j2").exists()
|
||||
|
||||
|
||||
def test_orchestrator_unit_uses_orchestrate_subcommand(unit_text):
|
||||
assert "decnet orchestrate" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_has_docker_supplementary_group(unit_text):
|
||||
"""SSHDriver shells `docker exec` against decky containers — without
|
||||
this group the worker can't reach the docker socket."""
|
||||
assert "SupplementaryGroups=docker" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_orders_after_bus(unit_text):
|
||||
"""Bus must be up first so heartbeats publish from the start."""
|
||||
assert "After=network-online.target decnet-bus.service" in unit_text
|
||||
assert "Wants=network-online.target decnet-bus.service" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_has_security_hardening(unit_text):
|
||||
for directive in (
|
||||
"NoNewPrivileges=yes",
|
||||
"ProtectSystem=full",
|
||||
"ProtectHome=read-only",
|
||||
"PrivateTmp=yes",
|
||||
"ProtectKernelTunables=yes",
|
||||
"ProtectKernelModules=yes",
|
||||
"ProtectControlGroups=yes",
|
||||
"RestrictSUIDSGID=yes",
|
||||
"LockPersonality=yes",
|
||||
):
|
||||
assert directive in unit_text, f"missing {directive}"
|
||||
|
||||
|
||||
def test_orchestrator_unit_writes_to_log_dir(unit_text):
|
||||
assert "/var/log/decnet/decnet.orchestrator.log" in unit_text
|
||||
assert "ReadWritePaths={{ install_dir }} /var/log/decnet" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_restart_on_failure(unit_text):
|
||||
assert "Restart=on-failure" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_carries_realism_env_block(unit_text):
|
||||
"""Stage 5 + 6 contract: the orchestrator's LLM enrichment and
|
||||
persona-pool path are configured per host via DECNET_REALISM_*
|
||||
env vars. Shipping them in the .j2 means an operator who never
|
||||
drops a .env.local still gets sane defaults."""
|
||||
for var in (
|
||||
"DECNET_REALISM_LLM",
|
||||
"DECNET_REALISM_MODEL",
|
||||
"DECNET_REALISM_TIMEOUT",
|
||||
"DECNET_REALISM_PERSONAS",
|
||||
):
|
||||
assert var in unit_text, f"missing {var} in unit"
|
||||
|
||||
|
||||
def test_orchestrator_unit_does_not_carry_legacy_emailgen_envs(unit_text):
|
||||
"""Pre-v1 clean break per the realism migration: the
|
||||
DECNET_EMAILGEN_* env vars are no longer read. Carrying them in
|
||||
the unit would mislead operators into thinking they still apply."""
|
||||
for legacy in (
|
||||
"DECNET_EMAILGEN_LLM",
|
||||
"DECNET_EMAILGEN_MODEL",
|
||||
"DECNET_EMAILGEN_TIMEOUT",
|
||||
"DECNET_EMAILGEN_PERSONAS",
|
||||
):
|
||||
assert legacy not in unit_text, (
|
||||
f"legacy env {legacy} still referenced; clean-break broken"
|
||||
)
|
||||
|
||||
|
||||
# ── decnet.target ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_target_wants_orchestrator(target_text):
|
||||
assert "decnet-orchestrator.service" in target_text
|
||||
|
||||
|
||||
def test_target_does_not_want_emailgen(target_text):
|
||||
"""Stage 5 of the realism migration deleted decnet-emailgen.service.
|
||||
A fresh `decnet init` against a target file that still mentions it
|
||||
fails systemctl start with `Unit decnet-emailgen.service could not
|
||||
be found`, blocking the whole target. Anti-regression."""
|
||||
assert "decnet-emailgen.service" not in target_text
|
||||
|
||||
|
||||
def test_target_wants_canary(target_text):
|
||||
"""Canary worker is a peer of orchestrator; both are part of the
|
||||
realism + callback story. Bundle check."""
|
||||
assert "decnet-canary.service" in target_text
|
||||
|
||||
|
||||
def test_target_orders_after_bus(target_text):
|
||||
"""Whole target depends on the bus being up."""
|
||||
assert "After=decnet-bus.service" in target_text
|
||||
|
||||
|
||||
# ── unit file no longer exists ───────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_emailgen_unit_template_is_gone():
|
||||
"""The pre-collapse ``deploy/decnet-emailgen.service.j2`` must stay
|
||||
deleted. A future commit that re-creates it (e.g. by accident
|
||||
during a partial revert) would break the realism migration's
|
||||
service-collapse contract."""
|
||||
assert not (DEPLOY / "decnet-emailgen.service.j2").exists(), (
|
||||
"decnet-emailgen.service.j2 reappeared — service collapse undone?"
|
||||
)
|
||||
Reference in New Issue
Block a user