mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-11 11:54:59 +03:00
SPEC §13.15 / §16.14 acceptance criteria. - test_helpers.make_support_token: HS256-токен для тестов (зеркало token.py). - SupportSessionTests: happy path (snapshot+conversation+audit SUCCESS), invalid signature, expired, schema error, missing subject, disabled contract, wrong channel (CHANNEL_NOT_SUPPORT), product mismatch, missing token, continue session (reuses open conversation), raw token/jti не попадает в audit. - SupportContractApiTests: OWNER регистрирует контракт (version из code), OPERATOR forbidden (403), code должен соответствовать продукту. - Багфиксы по ходу тестов: code=CharField (точки в foxray.support.v1), jsonpath return current, audit-deny вне write-транзакции (откат не стирает audit), DRAFT/DISABLED → CONTRACT_DISABLED, extraction вынесен в extract.py (NO GOD FILES). - 109 passed (95 baseline + 14 support).
57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
from __future__ import annotations
|
|
|
|
from django.db.models import QuerySet
|
|
|
|
from hub_platform.support.models import (
|
|
ContractStatus,
|
|
ProductSupportContract,
|
|
SupportIdentitySnapshot,
|
|
)
|
|
|
|
|
|
def contracts_for_organization(organization_id: int) -> QuerySet[ProductSupportContract]:
|
|
return (
|
|
ProductSupportContract.objects.filter(organization_id=organization_id)
|
|
.select_related("product")
|
|
.prefetch_related("allowed_channels")
|
|
.order_by("code")
|
|
)
|
|
|
|
|
|
def contract_for_organization(
|
|
*, organization_id: int, contract_id: int
|
|
) -> ProductSupportContract:
|
|
return contracts_for_organization(organization_id).get(id=contract_id)
|
|
|
|
|
|
def contract_by_code(*, organization_id: int, code: str) -> ProductSupportContract | None:
|
|
"""Контракт по code (любой статус) — для различения CONTRACT_NOT_FOUND/DISABLED."""
|
|
return (
|
|
ProductSupportContract.objects.filter(
|
|
organization_id=organization_id, code=code
|
|
)
|
|
.select_related("product")
|
|
.first()
|
|
)
|
|
|
|
|
|
def active_contract_for(*, organization_id: int, code: str) -> ProductSupportContract | None:
|
|
"""Контракт, принимающий production traffic: ACTIVE или DEPRECATED (migration window)."""
|
|
return (
|
|
ProductSupportContract.objects.filter(
|
|
organization_id=organization_id, code=code
|
|
)
|
|
.filter(status__in=[ContractStatus.ACTIVE, ContractStatus.DEPRECATED])
|
|
.select_related("product")
|
|
.first()
|
|
)
|
|
|
|
|
|
def snapshots_for_subject(
|
|
*, product_id: int, subject_key: str
|
|
) -> QuerySet[SupportIdentitySnapshot]:
|
|
return (
|
|
SupportIdentitySnapshot.objects.filter(product_id=product_id, subject_key=subject_key)
|
|
.order_by("-verified_at")
|
|
)
|