Spaces:
Running
Running
| """Tests for the policy hot-reload watcher.""" | |
| from __future__ import annotations | |
| import os | |
| import shutil | |
| from pathlib import Path | |
| from app.policy.store import PolicyStore | |
| from app.policy.watcher import PolicyWatcher | |
| REAL_BUNDLE = Path(__file__).resolve().parents[1] / "policies" / "v1" | |
| def _temp_bundle(tmp_path: Path) -> Path: | |
| dest = tmp_path / "v1" | |
| shutil.copytree(REAL_BUNDLE, dest) | |
| return dest | |
| def test_poll_no_change_returns_false(tmp_path: Path) -> None: | |
| bundle = _temp_bundle(tmp_path) | |
| store = PolicyStore(bundle_path=bundle) | |
| watcher = PolicyWatcher(store, bundle_path=bundle) | |
| assert watcher.poll_once() is False | |
| def test_poll_reloads_on_change(tmp_path: Path) -> None: | |
| bundle = _temp_bundle(tmp_path) | |
| store = PolicyStore(bundle_path=bundle) | |
| assert "R-01" in store.active.catalog | |
| watcher = PolicyWatcher(store, bundle_path=bundle) | |
| # Rewrite the catalog with a distinct rule and force a newer mtime. | |
| catalog = bundle / "catalog.yaml" | |
| catalog.write_text('policies:\n - id: "R-99"\n disposition: STOP\n', encoding="utf-8") | |
| future = catalog.stat().st_mtime + 10 | |
| os.utime(catalog, (future, future)) | |
| assert watcher.poll_once() is True # change detected -> reloaded | |
| assert "R-99" in store.active.catalog # new policy is live | |
| assert "R-01" not in store.active.catalog # old policy swapped out | |
| def test_start_stop_is_clean(tmp_path: Path) -> None: | |
| bundle = _temp_bundle(tmp_path) | |
| store = PolicyStore(bundle_path=bundle) | |
| watcher = PolicyWatcher(store, bundle_path=bundle, interval=0.01) | |
| watcher.start() | |
| watcher.start() # idempotent — second start is a no-op | |
| watcher.stop() # must not raise | |