Files
chatballs/apps/backend/hub_platform/support/selectors.py
T
Andrey caa51f4e29 test(support): cover token verify, session start and denials
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).
2026-07-09 01:20:04 +03:00

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")
)