refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
120
api/tests/test_error_code_registry.py
Normal file
120
api/tests/test_error_code_registry.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Ensure runtime error_code values stay within the OpenAPI registry."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.error_codes import ALL_ERROR_CODES, ERROR_CODE_ENUM
|
||||
from app.core.errors import (
|
||||
AppError,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
BadRequestError,
|
||||
ConflictError,
|
||||
GatewayTimeoutError,
|
||||
NotFoundError,
|
||||
ProviderError,
|
||||
QuotaExceededError,
|
||||
RateLimitedError,
|
||||
ServiceUnavailableError,
|
||||
ValidationError,
|
||||
_STATUS_TO_ERROR_CODE,
|
||||
)
|
||||
from app.features.auth.service import _AUTH_CODE_MAP
|
||||
from app.features.payment.payment_exceptions import _PAYMENT_CODE_MAP
|
||||
|
||||
_APP_FEATURES_ROOT = Path(__file__).resolve().parents[1] / "app" / "features"
|
||||
_LITERAL_ERROR_CODE_RE = re.compile(r"""error_code\s*=\s*["']([A-Z][A-Z0-9_]*)["']""")
|
||||
|
||||
|
||||
def _app_error_subclass_codes() -> set[str]:
|
||||
codes: set[str] = set()
|
||||
for cls in (
|
||||
NotFoundError,
|
||||
BadRequestError,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
ValidationError,
|
||||
ConflictError,
|
||||
ServiceUnavailableError,
|
||||
GatewayTimeoutError,
|
||||
ProviderError,
|
||||
QuotaExceededError,
|
||||
RateLimitedError,
|
||||
):
|
||||
sig = inspect.signature(cls.__init__)
|
||||
default = sig.parameters.get("message")
|
||||
# Instantiate with defaults to read resolved error_code from AppError base.
|
||||
instance = cls()
|
||||
codes.add(instance.error_code)
|
||||
return codes
|
||||
|
||||
|
||||
def _auth_runtime_codes() -> set[str]:
|
||||
return {external for _, external in _AUTH_CODE_MAP.values()}
|
||||
|
||||
|
||||
def _payment_runtime_codes() -> set[str]:
|
||||
return {external for _, external in _PAYMENT_CODE_MAP.values()}
|
||||
|
||||
|
||||
def _literal_feature_error_codes() -> set[str]:
|
||||
codes: set[str] = set()
|
||||
for path in _APP_FEATURES_ROOT.rglob("*.py"):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
codes.update(_LITERAL_ERROR_CODE_RE.findall(text))
|
||||
return codes
|
||||
|
||||
|
||||
def _runtime_error_codes() -> set[str]:
|
||||
return (
|
||||
_app_error_subclass_codes()
|
||||
| _auth_runtime_codes()
|
||||
| _payment_runtime_codes()
|
||||
| set(_STATUS_TO_ERROR_CODE.values())
|
||||
| _literal_feature_error_codes()
|
||||
)
|
||||
|
||||
|
||||
def test_runtime_error_codes_are_registered_in_openapi_enum() -> None:
|
||||
runtime = _runtime_error_codes()
|
||||
registry = set(ERROR_CODE_ENUM)
|
||||
missing = runtime - registry
|
||||
assert not missing, f"Unregistered runtime error_code values: {sorted(missing)}"
|
||||
|
||||
|
||||
def test_auth_and_payment_registry_http_status_matches_runtime_maps() -> None:
|
||||
registry_by_code = {entry["code"]: entry for entry in ALL_ERROR_CODES}
|
||||
for internal, (status_code, external) in {**_AUTH_CODE_MAP, **_PAYMENT_CODE_MAP}.items():
|
||||
if external not in registry_by_code:
|
||||
continue
|
||||
entry = registry_by_code[external]
|
||||
assert entry["http_status"] == status_code, (
|
||||
f"{internal} maps to {external} with HTTP {status_code}, "
|
||||
f"but registry lists {entry['http_status']}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"error_cls,expected_code",
|
||||
[
|
||||
(NotFoundError, "NOT_FOUND"),
|
||||
(BadRequestError, "BAD_REQUEST"),
|
||||
(AuthenticationError, "AUTHENTICATION_FAILED"),
|
||||
(AuthorizationError, "FORBIDDEN"),
|
||||
(ValidationError, "VALIDATION_ERROR"),
|
||||
(ConflictError, "CONFLICT"),
|
||||
(ServiceUnavailableError, "SERVICE_UNAVAILABLE"),
|
||||
(GatewayTimeoutError, "GATEWAY_TIMEOUT"),
|
||||
(ProviderError, "PROVIDER_ERROR"),
|
||||
(QuotaExceededError, "QUOTA_EXCEEDED"),
|
||||
(RateLimitedError, "RATE_LIMITED"),
|
||||
],
|
||||
)
|
||||
def test_app_error_subclasses_use_registered_codes(error_cls: type[AppError], expected_code: str) -> None:
|
||||
assert error_cls().error_code == expected_code
|
||||
assert expected_code in ERROR_CODE_ENUM
|
||||
Reference in New Issue
Block a user