mirror of
https://github.com/zvx-echo6/central.git
synced 2026-05-22 02:24:38 +02:00
Compare commits
No commits in common. "e33a89659268d00f5103d7e52fa770096e091f30" and "496dd1626fe7b4ddba61c4e23a30202764f391b8" have entirely different histories.
e33a896592
...
496dd1626f
5 changed files with 14 additions and 116 deletions
|
|
@ -13,25 +13,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
from central.bootstrap_config import Settings
|
from central.bootstrap_config import Settings
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def isolate_enrichment_cache(tmp_path, monkeypatch):
|
|
||||||
"""Redirect the supervisor's enrichment cache off the production path.
|
|
||||||
|
|
||||||
`central.supervisor.ENRICHMENT_CACHE_DB_PATH` defaults to
|
|
||||||
/var/lib/central/enrichment_cache.db. Constructing a Supervisor opens it,
|
|
||||||
so without this fixture the suite writes to (or, for any user without write
|
|
||||||
access to /var/lib/central, fails on) the live cache. Point it at a
|
|
||||||
per-test temp dir so no test ever touches the production path.
|
|
||||||
"""
|
|
||||||
import central.supervisor as supervisor_mod
|
|
||||||
|
|
||||||
monkeypatch.setattr(
|
|
||||||
supervisor_mod,
|
|
||||||
"ENRICHMENT_CACHE_DB_PATH",
|
|
||||||
tmp_path / "enrichment_cache.db",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def event_loop():
|
def event_loop():
|
||||||
"""Create an event loop for the test session."""
|
"""Create an event loop for the test session."""
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ from central.config_source import (
|
||||||
ConfigSource,
|
ConfigSource,
|
||||||
DbConfigSource,
|
DbConfigSource,
|
||||||
)
|
)
|
||||||
from central.bootstrap_config import get_settings
|
|
||||||
from central.crypto import KEY_SIZE, clear_key_cache
|
from central.crypto import KEY_SIZE, clear_key_cache
|
||||||
|
|
||||||
# Test database DSN
|
# Test database DSN
|
||||||
|
|
@ -32,20 +31,11 @@ def master_key_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch):
|
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""Configure master key path for all tests.
|
"""Configure master key path for all tests."""
|
||||||
|
clear_key_cache()
|
||||||
Clear get_settings (and the crypto key cache) AFTER setting the env so
|
|
||||||
crypto rebuilds from the test key regardless of suite order, and again on
|
|
||||||
teardown so the test key never leaks into a later test. See PR M-b.
|
|
||||||
"""
|
|
||||||
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
||||||
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
yield
|
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import asyncpg
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from central.bootstrap_config import get_settings
|
|
||||||
from central.config_store import ConfigStore
|
from central.config_store import ConfigStore
|
||||||
from central.crypto import KEY_SIZE, clear_key_cache
|
from central.crypto import KEY_SIZE, clear_key_cache
|
||||||
|
|
||||||
|
|
@ -35,24 +34,12 @@ def master_key_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch):
|
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""Configure master key path for all tests.
|
"""Configure master key path for all tests."""
|
||||||
|
clear_key_cache()
|
||||||
CENTRAL_MASTER_KEY_PATH feeds Settings, which get_settings() lru-caches. An
|
|
||||||
earlier test can warm that cache with the default /etc/central/master.key
|
|
||||||
before this fixture runs, so the env change alone is not enough — clear
|
|
||||||
get_settings (and the crypto key cache) AFTER setting the env so crypto
|
|
||||||
rebuilds from the test key regardless of suite order, and again on teardown
|
|
||||||
so the test key never leaks into a later test.
|
|
||||||
"""
|
|
||||||
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
||||||
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
||||||
monkeypatch.setenv("CENTRAL_CSRF_SECRET", "test-csrf-secret-for-testing-only-32chars")
|
monkeypatch.setenv("CENTRAL_CSRF_SECRET", "test-csrf-secret-for-testing-only-32chars")
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
yield
|
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
|
|
@ -351,13 +338,3 @@ class TestListenerReconnect:
|
||||||
pytest.fail("Listener did not stop after cancellation")
|
pytest.fail("Listener did not stop after cancellation")
|
||||||
|
|
||||||
assert listen_task.cancelled() or listen_task.done()
|
assert listen_task.cancelled() or listen_task.done()
|
||||||
|
|
||||||
|
|
||||||
def test_master_key_path_is_isolated(master_key_path: Path) -> None:
|
|
||||||
"""Contract: after setup_master_key runs, get_settings() resolves the master
|
|
||||||
key to the per-session test key — never the production /etc/central path —
|
|
||||||
regardless of suite order. Fails on the pre-fix code in a full-suite run
|
|
||||||
where get_settings was warmed with the default path by an earlier test.
|
|
||||||
"""
|
|
||||||
assert get_settings().master_key_path == master_key_path
|
|
||||||
assert get_settings().master_key_path != Path("/etc/central/master.key")
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import pytest_asyncio
|
||||||
from central.config_models import AdapterConfig
|
from central.config_models import AdapterConfig
|
||||||
from central.config_source import DbConfigSource
|
from central.config_source import DbConfigSource
|
||||||
from central.config_store import ConfigStore
|
from central.config_store import ConfigStore
|
||||||
from central.bootstrap_config import get_settings
|
|
||||||
from central.crypto import KEY_SIZE, clear_key_cache
|
from central.crypto import KEY_SIZE, clear_key_cache
|
||||||
|
|
||||||
# Test database DSN
|
# Test database DSN
|
||||||
|
|
@ -34,20 +33,11 @@ def master_key_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch):
|
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""Configure master key path for all tests.
|
"""Configure master key path for all tests."""
|
||||||
|
clear_key_cache()
|
||||||
Clear get_settings (and the crypto key cache) AFTER setting the env so
|
|
||||||
crypto rebuilds from the test key regardless of suite order, and again on
|
|
||||||
teardown so the test key never leaks into a later test. See PR M-b.
|
|
||||||
"""
|
|
||||||
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
||||||
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
yield
|
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from central.config_models import AdapterConfig
|
from central.config_models import AdapterConfig
|
||||||
from central.bootstrap_config import get_settings
|
|
||||||
from central.crypto import KEY_SIZE, clear_key_cache
|
from central.crypto import KEY_SIZE, clear_key_cache
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,20 +56,11 @@ def master_key_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch):
|
def setup_master_key(master_key_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""Configure master key path for all tests.
|
"""Configure master key path for all tests."""
|
||||||
|
clear_key_cache()
|
||||||
Clear get_settings (and the crypto key cache) AFTER setting the env so
|
|
||||||
crypto rebuilds from the test key regardless of suite order, and again on
|
|
||||||
teardown so the test key never leaks into a later test. See PR M-b.
|
|
||||||
"""
|
|
||||||
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
monkeypatch.setenv("CENTRAL_DB_DSN", TEST_DB_DSN)
|
||||||
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", str(master_key_path))
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
yield
|
|
||||||
clear_key_cache()
|
|
||||||
get_settings.cache_clear()
|
|
||||||
|
|
||||||
|
|
||||||
class MockConfigSource:
|
class MockConfigSource:
|
||||||
|
|
@ -149,18 +139,12 @@ class MockNWSAdapter:
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_nats():
|
def mock_nats():
|
||||||
"""Mock NATS connection.
|
"""Mock NATS connection."""
|
||||||
|
|
||||||
nats-py's `nc.jetstream()` is synchronous, so model it with a sync
|
|
||||||
MagicMock. (As an AsyncMock attribute, `supervisor._js = nc.jetstream()`
|
|
||||||
would assign an unawaited coroutine — the "coroutine ... was never awaited"
|
|
||||||
warning — rather than the JetStream mock.)
|
|
||||||
"""
|
|
||||||
mock_nc = AsyncMock()
|
mock_nc = AsyncMock()
|
||||||
mock_nc.publish = AsyncMock()
|
mock_nc.publish = AsyncMock()
|
||||||
mock_js = AsyncMock()
|
mock_js = AsyncMock()
|
||||||
mock_js.publish = AsyncMock()
|
mock_js.publish = AsyncMock()
|
||||||
mock_nc.jetstream = MagicMock(return_value=mock_js)
|
mock_nc.jetstream.return_value = mock_js
|
||||||
return mock_nc
|
return mock_nc
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -590,27 +574,3 @@ class TestEnableDisableEnableIntegration:
|
||||||
|
|
||||||
# State should be gone
|
# State should be gone
|
||||||
assert "nws" not in supervisor._adapter_states
|
assert "nws" not in supervisor._adapter_states
|
||||||
|
|
||||||
|
|
||||||
def test_enrichment_cache_path_is_hermetic(mock_config_store, tmp_path: Path) -> None:
|
|
||||||
"""No test may touch the production enrichment cache.
|
|
||||||
|
|
||||||
The autouse `isolate_enrichment_cache` fixture (conftest) must redirect
|
|
||||||
ENRICHMENT_CACHE_DB_PATH off /var/lib/central onto a per-test temp dir, and
|
|
||||||
constructing a Supervisor must open the cache there — not in production.
|
|
||||||
"""
|
|
||||||
import central.supervisor as supervisor_mod
|
|
||||||
|
|
||||||
patched = supervisor_mod.ENRICHMENT_CACHE_DB_PATH
|
|
||||||
assert tmp_path in patched.parents
|
|
||||||
assert "/var/lib/central" not in str(patched)
|
|
||||||
|
|
||||||
supervisor = supervisor_mod.Supervisor(
|
|
||||||
config_source=MockConfigSource(),
|
|
||||||
config_store=mock_config_store,
|
|
||||||
nats_url="nats://localhost:4222",
|
|
||||||
cloudevents_config=None,
|
|
||||||
)
|
|
||||||
# __init__ opened the cache at the temp path, leaving the db file behind.
|
|
||||||
assert patched.exists()
|
|
||||||
assert supervisor._enrichment_cache is not None
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue