feat: add entitlement plans and topic sessions

This commit is contained in:
2026-07-31 15:22:11 +08:00
parent ad2161497f
commit 884dc765ad
27 changed files with 2389 additions and 15 deletions

View File

@@ -0,0 +1,122 @@
from __future__ import annotations
from datetime import UTC, datetime
import pytest
from fastapi import HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.models import Base
from app.models.chat import ChatSession, TopicSession
from app.models.entitlement import EntitlementPlan
from app.models.user import User
from app.services.chat_service import ChatService
from app.services.entitlement_service import EntitlementService
from app.services.topic_session_service import TopicSessionService
def _db() -> Session:
engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool)
Base.metadata.create_all(engine)
return Session(engine)
def _now() -> datetime:
return datetime.now(UTC).replace(tzinfo=None)
def _seed_user_session(db: Session) -> tuple[User, ChatSession]:
user = User(id=1, phone="13800000001", name="测试用户", daily_chat_limit=100, daily_chat_used=0)
session = ChatSession(id=1, user_id=1, title="新聊天", message_count=0, last_message_at=_now(), is_deleted=0)
db.add_all([user, session])
db.commit()
return user, session
def test_default_entitlement_uses_basic_plan_when_user_has_no_assignment():
with _db() as db:
user, _session = _seed_user_session(db)
db.add(
EntitlementPlan(
id=10,
name="大本营基础版",
plan_type="basic",
monthly_topic_limit=30,
status=1,
sort_order=10,
)
)
db.commit()
view = EntitlementService.active_entitlement(db, user, monthly_topic_used=3)
assert view.plan_id == 10
assert view.name == "大本营基础版"
assert view.source == "default"
assert view.monthly_topic_remaining == 27
def test_assign_user_plan_replaces_previous_active_plan():
with _db() as db:
user, _session = _seed_user_session(db)
db.add_all(
[
EntitlementPlan(id=10, name="基础版", plan_type="basic", monthly_topic_limit=30, status=1, sort_order=10),
EntitlementPlan(id=20, name="深度陪伴版", plan_type="deep", monthly_topic_limit=90, status=1, sort_order=20),
]
)
db.commit()
first = EntitlementService.assign_user_plan(db, user=user, plan_id=10, operated_by=99)
second = EntitlementService.assign_user_plan(db, user=user, plan_id=20, operated_by=99)
db.commit()
db.refresh(first)
db.refresh(second)
view = EntitlementService.active_entitlement(db, user, monthly_topic_used=4)
assert first.status == "replaced"
assert second.status == "active"
assert view.plan_id == 20
assert view.source == "assigned"
assert view.monthly_topic_remaining == 86
def test_monthly_topic_quota_blocks_new_topic_but_allows_existing_topic():
with _db() as db:
user, session = _seed_user_session(db)
db.add(EntitlementPlan(id=10, name="限额版", plan_type="basic", monthly_topic_limit=1, status=1, sort_order=10))
db.add(
TopicSession(
id=100,
user_id=user.id,
chat_session_id=99,
title="旧主题",
core_question="旧主题",
status="active",
quota_deducted=1,
started_at=_now(),
created_at=_now(),
updated_at=_now(),
)
)
db.commit()
entitlement = EntitlementService.active_entitlement(db, user, monthly_topic_used=TopicSessionService.monthly_used_count(db, user.id))
with pytest.raises(HTTPException) as exc:
ChatService._ensure_topic_quota(db, user, session, entitlement)
assert exc.value.status_code == 403
TopicSessionService.get_or_create_active(
db,
user=user,
session=session,
question="当前主题",
deduct_quota=True,
)
db.flush()
ChatService._ensure_topic_quota(db, user, session, entitlement)