agnes-the-ai-analyst/tests/test_docker_compose.py
ZdenekSrotyr c3df03beb3 fix(compose): drop corporate-memory + session-collector services (#176)
**BREAKING** for operators using `COMPOSE_PROFILES=full` or custom
Compose overrides that referenced these stanzas — they're gone in
docker-compose.yml and docker-compose.prod.yml. The scheduler-v2 model
(previous commit) is now the sole driver: every cadence is a job in
services/scheduler/__main__.py:JOBS hitting an admin HTTP endpoint.

Why drop instead of keep behind `profiles: [full]`:
- The previous stanzas were tight `restart: unless-stopped` boot loops.
  When the scheduled run ended (every cycle), Docker re-spawned the
  container, defeating any cadence the service intended.
- The whole point of #176 is that there's now exactly one driver. Two
  drivers (scheduler HTTP + standalone container loop) would race on
  the same /data/user_sessions and knowledge_items writes.
- Removing the stanzas is a louder signal than commenting them out —
  operators upgrading get a clean failure mode (no stale containers),
  not a silently double-driven pipeline.

The Python entry points (services/{corporate_memory, session_collector,
verification_detector}/__main__.py) stay — they're still callable from
the CLI for manual one-shot runs and from the new admin endpoints.

docs/architecture.md updated to reflect the new schedule table.
tests/test_docker_compose.py pins the contract: the two services must
not reappear under either Compose file.
2026-05-04 23:59:44 +02:00

66 lines
2.5 KiB
Python

"""Static contract tests for docker-compose.yml.
The corporate-memory and session-collector side-car services were dropped
in #176 — the scheduler container now drives them through HTTP. These
tests pin that contract so the services can't quietly come back.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import yaml
@pytest.fixture(scope="module")
def compose() -> dict:
root = Path(__file__).resolve().parent.parent
return yaml.safe_load((root / "docker-compose.yml").read_text())
class TestComposeServicesRemoved:
"""The two side-car services must not exist in docker-compose.yml."""
def test_corporate_memory_service_removed(self, compose):
assert "corporate-memory" not in compose["services"], (
"corporate-memory was dropped in #176 — scheduler drives it via HTTP. "
"Do not re-add the service stanza."
)
def test_session_collector_service_removed(self, compose):
assert "session-collector" not in compose["services"], (
"session-collector was dropped in #176 — scheduler drives it via HTTP. "
"Do not re-add the service stanza."
)
class TestComposeSchedulerWires:
"""The scheduler service must remain — it's the sole driver now."""
def test_scheduler_service_present(self, compose):
assert "scheduler" in compose["services"]
scheduler = compose["services"]["scheduler"]
assert scheduler["command"] == "python -m services.scheduler"
def test_app_service_present(self, compose):
assert "app" in compose["services"]
class TestComposeNoBootLoopProfile:
"""No service that imports anthropic / openai should ship as a tight
`restart: unless-stopped` boot loop. The previous corporate-memory and
session-collector stanzas were exactly this footgun."""
def test_only_scheduler_is_unconditional_long_running(self, compose):
# Services WITHOUT a `profiles:` key run on default `docker compose up`.
always_running = [
name
for name, svc in compose["services"].items()
if "profiles" not in svc
]
# Expected always-running set on a default deploy: app + scheduler.
# extract is one-shot so it has profiles=[extract]; caddy/telegram-bot/
# ws-gateway are all behind profiles too.
for boot_loop_offender in ("corporate-memory", "session-collector"):
assert boot_loop_offender not in always_running