feat: add visual settings and readiness gate

This commit is contained in:
Codex
2026-08-01 22:14:20 +08:00
parent 6a15217d55
commit 3b4106e8cc
25 changed files with 2447 additions and 239 deletions
+37 -1
View File
@@ -1,11 +1,23 @@
import httpx
import pytest
from pathlib import Path
from app.config import Settings
from app.main import app
from app.runtime_settings import EncryptedSettingsStore, get_runtime_store
@pytest.fixture
def transport() -> httpx.ASGITransport:
def transport(tmp_path: Path) -> httpx.ASGITransport:
store = EncryptedSettingsStore(
Settings(
_env_file=None,
runtime_settings_dir=str(tmp_path),
database_url="postgresql://user:secret@postgres:5432/app",
redis_url="redis://:secret@redis:6379/0",
)
)
app.dependency_overrides[get_runtime_store] = lambda: store
return httpx.ASGITransport(app=app)
@@ -25,3 +37,27 @@ async def test_demo_project_contract(transport: httpx.ASGITransport) -> None:
body = response.json()
assert body["stage"] == "plan_review"
assert body["plan"]["cad_layer_count"] == 43
@pytest.mark.asyncio
async def test_settings_contract_never_returns_secret_values(transport: httpx.ASGITransport) -> None:
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/v1/settings")
assert response.status_code == 200
body = response.json()
assert len(body["categories"]) == 6
assert "ai_api_key" not in body["values"]
assert body["readiness"]["ready"] is False
@pytest.mark.asyncio
async def test_workflow_command_is_blocked_until_required_settings_are_ready(
transport: httpx.ASGITransport,
) -> None:
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/v1/projects/demo-apartment/commands",
json={"command": "confirm_plan", "expected_revision": 3, "payload": {}},
)
assert response.status_code == 503
assert response.json()["detail"]["code"] == "SETTINGS_INCOMPLETE"
+69
View File
@@ -0,0 +1,69 @@
from pathlib import Path
from app.config import Settings
from app.runtime_settings import (
EncryptedSettingsStore,
RuntimeSettingsTestResult,
generate_secret,
)
def create_store(path: Path) -> EncryptedSettingsStore:
settings = Settings(
_env_file=None,
runtime_settings_dir=str(path),
database_url="postgresql://user:secret@postgres:5432/app",
redis_url="redis://:secret@redis:6379/0",
)
return EncryptedSettingsStore(settings)
def test_runtime_secrets_are_encrypted_and_never_returned(tmp_path: Path) -> None:
store = create_store(tmp_path)
store.update(
{
"s3_endpoint": "http://minio:9000",
"s3_public_endpoint": "http://localhost:9000",
"s3_access_key_id": "service-account",
"s3_secret_access_key": "very-private-secret",
}
)
response = store.public_response()
assert "s3_secret_access_key" not in response.values
assert response.configured["s3_secret_access_key"] is True
assert b"very-private-secret" not in store.data_path.read_bytes()
def test_readiness_requires_configuration_and_successful_tests(tmp_path: Path) -> None:
store = create_store(tmp_path)
store.update(
{
"s3_endpoint": "http://minio:9000",
"s3_public_endpoint": "http://localhost:9000",
"s3_access_key_id": "access",
"s3_secret_access_key": "secret",
"ai_provider": "lingke",
"ai_base_url": "https://example.com/v1",
"ai_api_key": "api-secret",
"orchestrator_model": "planner",
"vision_model": "vision",
"image_model": "gpt-image-2",
}
)
assert store.public_response().readiness.ready is False
for target in ("infrastructure", "storage", "ai_models"):
store.record_test(RuntimeSettingsTestResult(target=target, ok=True, message="ok"))
assert store.public_response().readiness.ready is True
store.update({"image_model": "another-image-model"})
assert store.public_response().readiness.ready is False
assert store.public_response().tests.get("ai_models") is None
def test_secret_generators_use_expected_lengths() -> None:
assert len(generate_secret("hex24")) == 48
assert len(generate_secret("hex32")) == 64
assert len(generate_secret("base64_32")) == 44