feat(backend): harden pricing mutation contract and sync backend docs

- add typed response schemas for pricing write endpoints
- add stale draft version guard for pricing mutations
- unify pricing API contract around expected_scheme_version_id
- update API route map
- add smoke regression checklist for backend routes and artifact flows
This commit is contained in:
greebo
2026-03-19 19:11:33 +03:00
parent c7c9184a71
commit fbeac890be
5 changed files with 197 additions and 215 deletions

View File

@@ -4,24 +4,17 @@ from app.repositories.scheme_versions import get_current_scheme_version
from app.repositories.schemes import get_scheme_record_by_scheme_id
def ensure_expected_scheme_version_id(
def build_stale_draft_version_detail(
*,
expected_scheme_version_id: str,
actual_scheme_version_id: str,
expected_scheme_version_id: str | None,
) -> None:
if expected_scheme_version_id is None:
return
if expected_scheme_version_id != actual_scheme_version_id:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail={
"code": "stale_draft_version",
"message": "Draft scheme version is stale. Reload current draft state before applying mutation.",
"expected_scheme_version_id": expected_scheme_version_id,
"actual_scheme_version_id": actual_scheme_version_id,
},
)
) -> dict:
return {
"code": "stale_draft_version",
"message": "Draft scheme version is stale. Reload current draft state before applying mutation.",
"expected_scheme_version_id": expected_scheme_version_id,
"actual_scheme_version_id": actual_scheme_version_id,
}
async def get_current_draft_context(
@@ -40,9 +33,27 @@ async def get_current_draft_context(
detail="Current scheme version is not editable because it is not in draft state",
)
ensure_expected_scheme_version_id(
actual_scheme_version_id=version.scheme_version_id,
expected_scheme_version_id=expected_scheme_version_id,
)
if expected_scheme_version_id and expected_scheme_version_id != version.scheme_version_id:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=build_stale_draft_version_detail(
expected_scheme_version_id=expected_scheme_version_id,
actual_scheme_version_id=version.scheme_version_id,
),
)
return scheme, version
async def validate_expected_draft_version_if_provided(
scheme_id: str,
expected_scheme_version_id: str | None,
):
if not expected_scheme_version_id:
return None
scheme, version = await get_current_draft_context(
scheme_id=scheme_id,
expected_scheme_version_id=expected_scheme_version_id,
)
return scheme, version