Skip to content

Commit 00de17c

Browse files
committed
test: stabilize fixture boundaries and settings test split
1 parent 5c04683 commit 00de17c

6 files changed

Lines changed: 121 additions & 65 deletions

File tree

Justfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,25 @@ restore-s3-mongodb s3uri:
202202
api-check:
203203
@curl -sf http://localhost:8000/readyz > /dev/null && echo "stack ready" || (echo "stack not ready — run: just up" >&2; exit 1)
204204

205+
# Smoke deploy against the prod-like compose stack.
206+
smoke-deploy:
207+
#!/usr/bin/env bash
208+
set -euo pipefail
209+
bash scripts/ops/02-compose-profile.sh prod-like up -d db redis redpanda ingestor
210+
trap 'bash scripts/ops/02-compose-profile.sh prod-like down -v' EXIT
211+
212+
for _ in $(seq 1 60); do
213+
if curl -fsS http://localhost:8000/health >/dev/null && curl -fsS http://localhost:8000/readyz >/dev/null; then
214+
echo "smoke deploy passed"
215+
exit 0
216+
fi
217+
sleep 5
218+
done
219+
220+
echo "smoke deploy failed" >&2
221+
bash scripts/ops/02-compose-profile.sh prod-like logs ingestor
222+
exit 1
223+
205224
# Wipe DB to a clean empty state: stop → remove container+volume → restart → wait.
206225
db-reset:
207226
docker compose rm -sfv ingestor db

services/ingestor/tests/conftest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
`conftest.py` during normal pytest collection.
55
"""
66

7-
from tests.fixtures_shared import * # noqa: F401,F403
8-
from tests.fixtures_shared import _auto_provision_postgres # noqa: F401
7+
from tests import fixtures_shared as shared
8+
9+
10+
globals().update({name: getattr(shared, name) for name in shared.__all__})
11+
__all__ = shared.__all__
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Integration tests for settings-related runtime behavior.
2+
3+
These tests intentionally live under the integration tree because they rely on
4+
ASGI client/database-backed fixtures.
5+
"""
6+
7+
import pytest
8+
from httpx import AsyncClient
9+
10+
11+
# ---------------------------------------------------------------------------
12+
# App Behavior with Different Settings
13+
# ---------------------------------------------------------------------------
14+
@pytest.mark.integration
15+
class TestAppBehaviorWithSettings:
16+
"""App behavior changes based on settings."""
17+
18+
async def test_app_includes_version_in_response(self, client: AsyncClient) -> None:
19+
"""App returns versioned endpoints."""
20+
r = await client.get("/readyz")
21+
assert r.status_code in [200, 503]
22+
23+
24+
# ---------------------------------------------------------------------------
25+
# Observation Fixtures Behavior
26+
# ---------------------------------------------------------------------------
27+
@pytest.mark.integration
28+
class TestObservationFixtures:
29+
"""Observation fixtures create predictable test data."""
30+
31+
async def test_created_observation_fixture_produces_valid_observation(
32+
self, created_observation: dict
33+
) -> None:
34+
"""created_observation fixture produces a valid observation with expected fields."""
35+
assert "id" in created_observation
36+
assert "source" in created_observation
37+
assert isinstance(created_observation["id"], int)
38+
assert isinstance(created_observation["source"], str)
39+
assert "raw_data" in created_observation or "data" in created_observation
40+
41+
async def test_created_observations_fixture_produces_multiple(
42+
self, created_observations: list[dict]
43+
) -> None:
44+
"""created_observations fixture produces exactly 3 observations."""
45+
assert len(created_observations) == 3
46+
47+
for observation in created_observations:
48+
assert "id" in observation
49+
assert observation["source"].startswith("source-")
50+
51+
async def test_observation_payload_fixture_is_mutable_copy(
52+
self, observation_payload: dict
53+
) -> None:
54+
"""observation_payload fixture returns a mutable copy."""
55+
observation_payload["source"] = "modified"
56+
assert observation_payload["source"] == "modified"
57+
58+
async def test_created_observation_has_tags_lowercased(
59+
self, created_observation: dict
60+
) -> None:
61+
"""Tags are normalized to lowercase (per validator)."""
62+
tags = created_observation["tags"]
63+
assert all(tag.islower() for tag in tags)

services/ingestor/tests/unit/settings/test_settings_and_config.py

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"""
99

1010
import pytest
11-
from httpx import AsyncClient
1211

1312
from services.ingestor.config import Settings
1413

@@ -119,20 +118,6 @@ def test_partial_docs_auth_disabled(self) -> None:
119118
# Application logic should treat this as "auth not configured"
120119

121120

122-
# ---------------------------------------------------------------------------
123-
# App Behavior with Different Settings
124-
# ---------------------------------------------------------------------------
125-
@pytest.mark.integration
126-
class TestAppBehaviorWithSettings:
127-
"""App behavior changes based on settings."""
128-
129-
async def test_app_includes_version_in_response(self, client: AsyncClient) -> None:
130-
"""App returns versioned endpoints."""
131-
# Make a request to verify app is functioning
132-
r = await client.get("/readyz")
133-
assert r.status_code in [200, 503]
134-
135-
136121
# ---------------------------------------------------------------------------
137122
# Environment Variable Overrides
138123
# ---------------------------------------------------------------------------
@@ -164,53 +149,6 @@ def test_settings_respects_case_insensitive_env_vars(self) -> None:
164149
# require environment variable setup
165150

166151

167-
# ---------------------------------------------------------------------------
168-
# Observation Fixtures Behavior
169-
# ---------------------------------------------------------------------------
170-
@pytest.mark.integration
171-
class TestObservationFixtures:
172-
"""Observation fixtures create predictable test data."""
173-
174-
async def test_created_observation_fixture_produces_valid_observation(
175-
self, created_observation: dict
176-
) -> None:
177-
"""created_observation fixture produces a valid observation with expected fields."""
178-
assert "id" in created_observation
179-
assert "source" in created_observation
180-
assert isinstance(created_observation["id"], int)
181-
assert isinstance(created_observation["source"], str)
182-
# raw_data field should be present (API call response)
183-
assert "raw_data" in created_observation or "data" in created_observation
184-
185-
async def test_created_observations_fixture_produces_multiple(
186-
self, created_observations: list[dict]
187-
) -> None:
188-
"""created_observations fixture produces exactly 3 observations."""
189-
assert len(created_observations) == 3
190-
191-
# Each observation is valid
192-
for observation in created_observations:
193-
assert "id" in observation
194-
assert observation["source"].startswith("source-")
195-
196-
async def test_observation_payload_fixture_is_mutable_copy(
197-
self, observation_payload: dict
198-
) -> None:
199-
"""observation_payload fixture returns a mutable copy."""
200-
# Should be able to modify without affecting original
201-
observation_payload["source"] = "modified"
202-
assert observation_payload["source"] == "modified"
203-
204-
async def test_created_observation_has_tags_lowercased(
205-
self, created_observation: dict
206-
) -> None:
207-
"""Tags are normalized to lowercase (per validator)."""
208-
# created_observation is from OBSERVATION_API which has ["Stock", "NASDAQ"]
209-
tags = created_observation["tags"]
210-
# Pydantic validator should lowercase them
211-
assert all(tag.islower() for tag in tags)
212-
213-
214152
# ---------------------------------------------------------------------------
215153
# Settings Impact on Feature Flags
216154
# ---------------------------------------------------------------------------

tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44
for tests collected under the top-level `tests/` tree.
55
"""
66

7-
from tests.fixtures_shared import * # noqa: F401,F403
7+
from tests import fixtures_shared as shared
8+
9+
10+
globals().update({name: getattr(shared, name) for name in shared.__all__})
11+
__all__ = shared.__all__

tests/fixtures_shared.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,35 @@
8888
from tests.shared.payloads import OBSERVATION_API # noqa: E402
8989

9090

91+
# Explicit shared fixture export surface for tree-level conftest re-exports.
92+
__all__ = [
93+
"observation_timestamp",
94+
"fake_redis",
95+
"redis_container",
96+
"real_redis",
97+
"client_with_cache",
98+
"_auto_provision_postgres",
99+
"apply_migrations",
100+
"db",
101+
"client",
102+
"client_isolated",
103+
"test_settings",
104+
"settings_with_docs_auth",
105+
"settings_with_api_token",
106+
"created_observation",
107+
"created_observations",
108+
"sample_observations_with_tags",
109+
"observation_payload",
110+
"postgresql_async_session",
111+
"postgresql_async_session_isolated",
112+
"mock_db_failure",
113+
"app_with_docs_auth",
114+
"app_with_api_token",
115+
"pytest_configure",
116+
"pytest_collection_modifyitems",
117+
]
118+
119+
91120
# For PostgreSQL, set pool size to match concurrent test load
92121
def _get_test_db_url() -> str:
93122
return os.environ.get("DATABASE_URL_TEST", "sqlite+aiosqlite:///:memory:")

0 commit comments

Comments
 (0)