from __future__ import annotations from dataclasses import dataclass from datetime import datetime from pathlib import Path from src.config import ControllerConfig from src.controller import InfraController from src.discovery import AppRegistration from src.metadata import InfraMetadata @dataclass class FakeServiceResult: returncode: int = 0 stdout: str = "" stderr: str = "" class FakeServiceManager: def __init__(self): self.applied: list[str] = [] self.stopped: list[str] = [] def apply_service(self, service_name: str) -> FakeServiceResult: self.applied.append(service_name) return FakeServiceResult() def stop_service(self, service_name: str) -> FakeServiceResult: self.stopped.append(service_name) return FakeServiceResult() class FakeDiscoveryManager: def __init__(self, apps: dict[str, AppRegistration]): self._apps = apps def set_apps(self, apps: dict[str, AppRegistration]) -> None: self._apps = apps def discover_all(self) -> dict[str, AppRegistration]: return dict(self._apps) def _app(name: str, services: list[str]) -> AppRegistration: md = InfraMetadata(project=name, requires={"services": services}) return AppRegistration(name=name, metadata=md, last_seen=datetime.now(), discovery_method="test") def test_controller_stops_unused_services_after_grace_period(tmp_path: Path, monkeypatch): cfg = ControllerConfig() cfg.services.grace_period_minutes = 0 cfg.services.state_file = tmp_path / "state.json" discovery = FakeDiscoveryManager({"a": _app("a", ["svc1"])}) services = FakeServiceManager() c = InfraController(cfg, discovery=discovery, services=services) monkeypatch.setattr("src.controller.time.time", lambda: 0.0) c.run_once() assert services.applied == ["svc1"] assert services.stopped == [] discovery.set_apps({}) monkeypatch.setattr("src.controller.time.time", lambda: 10.0) c.run_once() assert services.stopped == [] monkeypatch.setattr("src.controller.time.time", lambda: 20.0) c.run_once() assert services.stopped == ["svc1"] def test_controller_does_not_stop_service_within_grace_period(tmp_path: Path, monkeypatch): cfg = ControllerConfig() cfg.services.grace_period_minutes = 1 cfg.services.state_file = tmp_path / "state.json" discovery = FakeDiscoveryManager({"a": _app("a", ["svc1"])}) services = FakeServiceManager() c = InfraController(cfg, discovery=discovery, services=services) monkeypatch.setattr("src.controller.time.time", lambda: 0.0) c.run_once() discovery.set_apps({}) monkeypatch.setattr("src.controller.time.time", lambda: 10.0) c.run_once() monkeypatch.setattr("src.controller.time.time", lambda: 20.0) c.run_once() assert services.stopped == []