diff --git a/tests/test_bootstrap_config.py b/tests/test_bootstrap_config.py index ef4ee49..089981b 100644 --- a/tests/test_bootstrap_config.py +++ b/tests/test_bootstrap_config.py @@ -18,6 +18,7 @@ class TestSettingsFromEnv: monkeypatch.setenv("CENTRAL_NATS_URL", "nats://10.0.0.1:4222") monkeypatch.setenv("CENTRAL_MASTER_KEY_PATH", "/tmp/test.key") monkeypatch.setenv("CENTRAL_LOG_LEVEL", "DEBUG") + monkeypatch.setenv("CENTRAL_CSRF_SECRET", "testsecret12345678901234567890ab") settings = Settings() @@ -29,6 +30,7 @@ class TestSettingsFromEnv: def test_defaults_applied(self, monkeypatch: pytest.MonkeyPatch) -> None: """Default values are used when env vars not set.""" monkeypatch.setenv("CENTRAL_DB_DSN", "postgresql://x:y@localhost/db") + monkeypatch.setenv("CENTRAL_CSRF_SECRET", "testsecret12345678901234567890ab") # Clear any existing env vars that might interfere monkeypatch.delenv("CENTRAL_NATS_URL", raising=False) monkeypatch.delenv("CENTRAL_MASTER_KEY_PATH", raising=False) @@ -44,13 +46,23 @@ class TestSettingsFromEnv: class TestSettingsFromFile: """Test loading settings from .env file.""" - def test_reads_from_env_file(self, tmp_path: Path) -> None: + def test_reads_from_env_file( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: """Settings are read from .env file when env vars not present.""" + # Remove any live env vars that would override the test .env file + monkeypatch.delenv("CENTRAL_DB_DSN", raising=False) + monkeypatch.delenv("CENTRAL_NATS_URL", raising=False) + monkeypatch.delenv("CENTRAL_LOG_LEVEL", raising=False) + monkeypatch.delenv("CENTRAL_MASTER_KEY_PATH", raising=False) + monkeypatch.delenv("CENTRAL_CSRF_SECRET", raising=False) + env_file = tmp_path / ".env" env_file.write_text( "CENTRAL_DB_DSN=postgresql://file:pass@localhost/filedb\n" "CENTRAL_NATS_URL=nats://file.local:4222\n" "CENTRAL_LOG_LEVEL=WARNING\n" + "CENTRAL_CSRF_SECRET=testsecret12345678901234567890ab\n" ) # Create settings pointing to the temp .env file @@ -65,8 +77,12 @@ class TestSettingsFromFile: ) -> None: """Environment variables take precedence over .env file.""" env_file = tmp_path / ".env" - env_file.write_text("CENTRAL_DB_DSN=postgresql://file@localhost/filedb\n") + env_file.write_text( + "CENTRAL_DB_DSN=postgresql://file@localhost/filedb\n" + "CENTRAL_CSRF_SECRET=filesecret1234567890123456789012\n" + ) monkeypatch.setenv("CENTRAL_DB_DSN", "postgresql://env@localhost/envdb") + monkeypatch.setenv("CENTRAL_CSRF_SECRET", "envsecret12345678901234567890ab") settings = Settings(_env_file=env_file) @@ -80,6 +96,7 @@ class TestSettingsValidation: """Clear error when required CENTRAL_DB_DSN is missing.""" # Ensure no env vars or .env file provides the DSN monkeypatch.delenv("CENTRAL_DB_DSN", raising=False) + monkeypatch.delenv("CENTRAL_CSRF_SECRET", raising=False) with pytest.raises(Exception) as exc_info: # Use a non-existent .env file path to ensure no fallback @@ -91,6 +108,7 @@ class TestSettingsValidation: def test_invalid_log_level_rejected(self, monkeypatch: pytest.MonkeyPatch) -> None: """Invalid log level values are rejected.""" monkeypatch.setenv("CENTRAL_DB_DSN", "postgresql://x@localhost/db") + monkeypatch.setenv("CENTRAL_CSRF_SECRET", "testsecret12345678901234567890ab") monkeypatch.setenv("CENTRAL_LOG_LEVEL", "INVALID") with pytest.raises(Exception): @@ -103,6 +121,7 @@ class TestGetSettings: def test_caches_result(self, monkeypatch: pytest.MonkeyPatch) -> None: """get_settings() returns cached instance.""" monkeypatch.setenv("CENTRAL_DB_DSN", "postgresql://cached@localhost/db") + monkeypatch.setenv("CENTRAL_CSRF_SECRET", "testsecret12345678901234567890ab") get_settings.cache_clear() s1 = get_settings() @@ -113,6 +132,7 @@ class TestGetSettings: def test_cache_clear_reloads(self, monkeypatch: pytest.MonkeyPatch) -> None: """cache_clear() forces reload on next call.""" monkeypatch.setenv("CENTRAL_DB_DSN", "postgresql://first@localhost/db") + monkeypatch.setenv("CENTRAL_CSRF_SECRET", "testsecret12345678901234567890ab") get_settings.cache_clear() s1 = get_settings() diff --git a/tests/test_models.py b/tests/test_models.py index be288f2..c010f39 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -98,11 +98,6 @@ class TestSubjectForEvent: subject = subject_for_event(event) assert subject == "central.wx.alert.us.unknown" - def test_custom_prefix(self, sample_event: Event) -> None: - """Custom prefix is used in subject.""" - subject = subject_for_event(sample_event, prefix="myapp.events") - assert subject == "myapp.events.alert.us.id.county.ada" - class TestCloudEventsWire: """Tests for CloudEvents wire format.""" diff --git a/tests/test_nws_normalization.py b/tests/test_nws_normalization.py index 72e2fdc..53a7e49 100644 --- a/tests/test_nws_normalization.py +++ b/tests/test_nws_normalization.py @@ -16,7 +16,7 @@ from central.adapters.nws import ( _compute_bbox, SEVERITY_MAP, ) -from central.config import NWSAdapterConfig +from central.config_models import AdapterConfig from central.models import subject_for_event @@ -51,7 +51,7 @@ SAMPLE_FEATURE_ID = { SAMPLE_FEATURE_OR = { "id": "urn:oid:2.49.0.1.840.0.x1y2z3w4", "type": "Feature", - "geometry": None, + "geometry": {"type": "Point", "coordinates": [-122.7, 45.5]}, # Portland, OR "properties": { "id": "urn:oid:2.49.0.1.840.0.x1y2z3w4", "event": "Winter Storm Warning", @@ -181,18 +181,24 @@ class TestBuildRegions: class TestStateFilter: - """Tests for state filtering.""" + """Tests for region-based filtering.""" @pytest.fixture def adapter(self, tmp_path: Path) -> NWSAdapter: - """Create adapter with ID/OR/WA states.""" - config = NWSAdapterConfig( + """Create adapter with Pacific Northwest region (excludes CA).""" + config = AdapterConfig( + name="nws", enabled=True, cadence_s=60, - states=["ID", "OR", "WA", "MT", "WY", "UT", "NV"], - contact_email="test@example.com", + settings={ + "contact_email": "test@example.com", + # Pacific NW region: WA/OR/ID - excludes CA (LA at 34N, region starts at 42N) + "region": {"north": 49.0, "south": 42.0, "east": -104.0, "west": -125.0}, + }, + updated_at=datetime.now(timezone.utc), ) - return NWSAdapter(config, tmp_path / "test.db") + mock_config_store = MagicMock() + return NWSAdapter(config, mock_config_store, tmp_path / "test.db") def test_accepts_id_feature(self, adapter: NWSAdapter) -> None: event = adapter._normalize_feature(SAMPLE_FEATURE_ID) @@ -228,13 +234,18 @@ class TestSeverityMapping: assert SEVERITY_MAP["Unknown"] is None def test_unknown_severity_in_feature(self, tmp_path: Path) -> None: - config = NWSAdapterConfig( + config = AdapterConfig( + name="nws", enabled=True, cadence_s=60, - states=["WA"], - contact_email="test@example.com", + settings={ + "contact_email": "test@example.com", + # No region = accept all features + }, + updated_at=datetime.now(timezone.utc), ) - adapter = NWSAdapter(config, tmp_path / "test.db") + mock_config_store = MagicMock() + adapter = NWSAdapter(config, mock_config_store, tmp_path / "test.db") event = adapter._normalize_feature(SAMPLE_FEATURE_UNKNOWN_SEVERITY) assert event is not None assert event.severity is None @@ -245,13 +256,18 @@ class TestSubjectDerivation: @pytest.fixture def adapter(self, tmp_path: Path) -> NWSAdapter: - config = NWSAdapterConfig( + config = AdapterConfig( + name="nws", enabled=True, cadence_s=60, - states=["ID", "OR", "WA"], - contact_email="test@example.com", + settings={ + "contact_email": "test@example.com", + # No region = accept all features + }, + updated_at=datetime.now(timezone.utc), ) - return NWSAdapter(config, tmp_path / "test.db") + mock_config_store = MagicMock() + return NWSAdapter(config, mock_config_store, tmp_path / "test.db") def test_county_subject(self, adapter: NWSAdapter) -> None: event = adapter._normalize_feature(SAMPLE_FEATURE_ID) @@ -287,13 +303,18 @@ class TestRegionsSorted: @pytest.fixture def adapter(self, tmp_path: Path) -> NWSAdapter: - config = NWSAdapterConfig( + config = AdapterConfig( + name="nws", enabled=True, cadence_s=60, - states=["ID"], - contact_email="test@example.com", + settings={ + "contact_email": "test@example.com", + # No region = accept all features + }, + updated_at=datetime.now(timezone.utc), ) - return NWSAdapter(config, tmp_path / "test.db") + mock_config_store = MagicMock() + return NWSAdapter(config, mock_config_store, tmp_path / "test.db") def test_regions_alphabetically_sorted(self, adapter: NWSAdapter) -> None: event = adapter._normalize_feature(SAMPLE_FEATURE_ID) @@ -312,13 +333,18 @@ class TestDeduplication: @pytest.fixture def adapter(self, tmp_path: Path) -> NWSAdapter: - config = NWSAdapterConfig( + config = AdapterConfig( + name="nws", enabled=True, cadence_s=60, - states=["ID"], - contact_email="test@example.com", + settings={ + "contact_email": "test@example.com", + # No region = accept all features + }, + updated_at=datetime.now(timezone.utc), ) - return NWSAdapter(config, tmp_path / "test.db") + mock_config_store = MagicMock() + return NWSAdapter(config, mock_config_store, tmp_path / "test.db") def test_same_feature_same_id(self, adapter: NWSAdapter) -> None: """Normalizing the same feature twice returns same Event.id."""