feat(management): 批次A六表模型——fact/宽表/通话小表+评分配置/快照/辅导
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -112,3 +112,22 @@ async def make_client() -> AsyncIterator[
|
||||
await client.aclose()
|
||||
await engine.dispose()
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def make_session() -> AsyncIterator[AsyncSession]:
|
||||
"""独立内存 SQLite AsyncSession(不挂 API 依赖)——存储层/管理域测试用。
|
||||
|
||||
与 make_client 共用 StaticPool 单连接模式;每个测试独享全新 schema。
|
||||
"""
|
||||
engine = create_async_engine(
|
||||
"sqlite+aiosqlite:///:memory:",
|
||||
poolclass=StaticPool,
|
||||
connect_args={"check_same_thread": False},
|
||||
)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
||||
async with SessionLocal() as s:
|
||||
yield s
|
||||
await engine.dispose()
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""管理域 6 张新表的模型冒烟测试(批次A Task1)。
|
||||
|
||||
只验证:建表可用、主键/唯一约束生效、默认值正确。业务逻辑在
|
||||
first_reply_facts / employee_metrics 的测试里。
|
||||
"""
|
||||
import datetime as dt # noqa: F401
|
||||
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from app.db.models import (
|
||||
EmployeeCallDaily,
|
||||
EmployeeMetricDaily,
|
||||
EmployeeScoreConfig,
|
||||
EmployeeScoreSnapshot,
|
||||
FirstReplyFact,
|
||||
ManagementFollowup,
|
||||
)
|
||||
|
||||
|
||||
async def test_first_reply_fact_roundtrip(make_session):
|
||||
s = make_session
|
||||
s.add(FirstReplyFact(channel="wechat", owner_account_id="wx1", peer_id="p1",
|
||||
thread_end_ts=1_700_000_000_000,
|
||||
reply_ts=1_700_000_600_000,
|
||||
day=20260901, fr_ms=600_000))
|
||||
await s.commit()
|
||||
val = (await s.execute(sa.select(FirstReplyFact.fr_ms))).scalar_one()
|
||||
assert val == 600_000
|
||||
|
||||
|
||||
async def test_first_reply_fact_unique_sample(make_session):
|
||||
s = make_session
|
||||
kwargs = {"channel": "wechat", "owner_account_id": "wx1", "peer_id": "p1",
|
||||
"thread_end_ts": 1, "reply_ts": 2, "day": 20260901, "fr_ms": 1}
|
||||
s.add(FirstReplyFact(**kwargs))
|
||||
await s.commit()
|
||||
s.add(FirstReplyFact(**kwargs))
|
||||
with pytest.raises(IntegrityError):
|
||||
await s.commit()
|
||||
|
||||
|
||||
async def test_metric_daily_versioning_coexists(make_session):
|
||||
s = make_session
|
||||
for v in ("v1", "v2"):
|
||||
s.add(EmployeeMetricDaily(day=20260901, channel="wechat",
|
||||
user_id="u1", metric_version=v))
|
||||
await s.commit()
|
||||
n = (await s.execute(
|
||||
sa.select(sa.func.count()).select_from(EmployeeMetricDaily))).scalar_one()
|
||||
assert n == 2
|
||||
|
||||
|
||||
async def test_metric_daily_defaults_zero_not_none(make_session):
|
||||
s = make_session
|
||||
s.add(EmployeeMetricDaily(day=20260901, channel="wechat", user_id="u1"))
|
||||
await s.commit()
|
||||
row = (await s.execute(sa.select(EmployeeMetricDaily))).scalar_one()
|
||||
assert row.msg_sent == 0 and row.cust_threads == 0 and row.fr_sum_ms == 0
|
||||
assert row.metric_version == "v1"
|
||||
assert row.data_complete is False or row.data_complete == 0 # SQLite 0/1
|
||||
|
||||
|
||||
async def test_call_daily_roundtrip(make_session):
|
||||
s = make_session
|
||||
s.add(EmployeeCallDaily(day=20260901, user_id="u1", call_count=2,
|
||||
call_seconds=164, answered_call_count=1,
|
||||
call_followup_count=1))
|
||||
await s.commit()
|
||||
row = (await s.execute(sa.select(EmployeeCallDaily))).scalar_one()
|
||||
assert row.call_seconds == 164
|
||||
|
||||
|
||||
async def test_score_config_version_unique(make_session):
|
||||
s = make_session
|
||||
s.add(EmployeeScoreConfig(version="v1.0", name="一期试算",
|
||||
config_json={"dimensions": {}}))
|
||||
await s.commit()
|
||||
s.add(EmployeeScoreConfig(version="v1.0", name="重复",
|
||||
config_json={}))
|
||||
with pytest.raises(IntegrityError):
|
||||
await s.commit()
|
||||
|
||||
|
||||
async def test_snapshot_batch_user_unique(make_session):
|
||||
s = make_session
|
||||
common = {"snapshot_batch_id": "b1", "period_type": "week",
|
||||
"period_start": 20260901, "period_end": 20260907,
|
||||
"score_config_version": "v1.0", "metric_version": "v1",
|
||||
"data_as_of": 20260907}
|
||||
s.add(EmployeeScoreSnapshot(user_id="u1", **common))
|
||||
await s.commit()
|
||||
s.add(EmployeeScoreSnapshot(user_id="u1", **common))
|
||||
with pytest.raises(IntegrityError):
|
||||
await s.commit()
|
||||
|
||||
|
||||
async def test_followup_defaults(make_session):
|
||||
s = make_session
|
||||
s.add(ManagementFollowup(employee_user_id="u1", source_type="manual"))
|
||||
await s.commit()
|
||||
row = (await s.execute(sa.select(ManagementFollowup))).scalar_one()
|
||||
assert row.status == "open"
|
||||
assert row.created_at is not None
|
||||
Reference in New Issue
Block a user