fix: restore green builds and align frontend-backend contracts (P0)

- Isolate Settings tests from .env and process env leakage
- Fix analytics metadata test to unwrap psycopg Json wrapper
- Remove unused state variables causing frontend build failures
- Fix ReviewPage to use /classifications endpoint instead of nonexistent /result
- Normalize ReviewPage status enums (failed not error) and access_type values
- Align api.ts types with backend response shapes (ReplayPage, AnalyticsData, AgentUsage)
This commit is contained in:
Yaojia Wang
2026-04-05 23:00:39 +02:00
parent 189a0fad34
commit e55ec42ae5
6 changed files with 70 additions and 41 deletions

View File

@@ -145,4 +145,11 @@ class TestPostgresAnalyticsRecorder:
)
call_args = mock_conn.execute.call_args
params = call_args[0][1]
assert params["metadata"] == {"key": "val"}
# PostgresAnalyticsRecorder wraps metadata with psycopg Json() adapter.
# Unwrap to compare the inner dict.
from psycopg.types.json import Json
meta = params["metadata"]
if isinstance(meta, Json):
meta = meta.obj
assert meta == {"key": "val"}

View File

@@ -7,10 +7,41 @@ import pytest
from app.config import Settings
def _isolated_settings(**kwargs: object) -> Settings:
"""Create a Settings instance that ignores .env files and process env vars.
pydantic-settings reads from env_file and environment by default, which
causes test results to depend on the machine they run on. We override
model_config at the class level temporarily so that every test gets
deterministic results.
"""
# Build a throwaway subclass that disables env-file and env-var loading.
class _IsolatedSettings(Settings):
model_config = Settings.model_config.copy()
model_config["env_file"] = None # type: ignore[assignment]
model_config["env_ignore_empty"] = True
# _env_parse_none_str makes pydantic-settings treat missing env vars as
# absent rather than empty-string, so required fields will raise.
import os
env_backup = os.environ.copy()
# Strip all env vars that Settings knows about so they can't leak in.
settings_fields = set(Settings.model_fields)
for key in list(os.environ):
if key.lower() in settings_fields:
del os.environ[key]
try:
return _IsolatedSettings(**kwargs) # type: ignore[return-value]
finally:
os.environ.clear()
os.environ.update(env_backup)
@pytest.mark.unit
class TestSettings:
def test_default_values(self) -> None:
settings = Settings(
settings = _isolated_settings(
database_url="postgresql://x:x@localhost/db",
anthropic_api_key="key",
)
@@ -20,7 +51,7 @@ class TestSettings:
assert settings.interrupt_ttl_minutes == 30
def test_custom_values(self) -> None:
settings = Settings(
settings = _isolated_settings(
database_url="postgresql://x:x@localhost/db",
llm_provider="openai",
llm_model="gpt-4o",
@@ -33,18 +64,18 @@ class TestSettings:
def test_invalid_provider_rejected(self) -> None:
with pytest.raises(Exception):
Settings(
_isolated_settings(
database_url="postgresql://x:x@localhost/db",
llm_provider="invalid",
)
def test_missing_database_url_rejected(self) -> None:
with pytest.raises(Exception):
Settings(anthropic_api_key="key")
_isolated_settings(anthropic_api_key="key")
def test_empty_api_key_for_provider_rejected(self) -> None:
with pytest.raises(ValueError, match="API key"):
Settings(
_isolated_settings(
database_url="postgresql://x:x@localhost/db",
llm_provider="anthropic",
anthropic_api_key="",
@@ -52,7 +83,7 @@ class TestSettings:
def test_wrong_provider_key_rejected(self) -> None:
with pytest.raises(ValueError, match="API key"):
Settings(
_isolated_settings(
database_url="postgresql://x:x@localhost/db",
llm_provider="openai",
anthropic_api_key="key",