feat(platform): close AI expense value loop

Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
caoxiaozhu
2026-07-17 14:14:08 +08:00
parent 242d68c36f
commit 787bc3a481
507 changed files with 82072 additions and 6344 deletions

View File

@@ -0,0 +1,230 @@
from __future__ import annotations
from datetime import UTC, datetime
from decimal import Decimal
import pytest
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
import app.models # noqa: F401 - 注册完整 metadata
from app.db.base_class import Base
from app.models.commercial_billing import CommercialAdminEvent, CommercialBillingPeriod
from app.schemas.commercial import CommercialPlanCreate, CommercialSubscriptionCreate
from app.services.commercial_admin import CommercialAdminService
from app.services.commercial_subscription_rollover import (
CommercialSubscriptionRolloverService,
)
@pytest.fixture()
def db() -> Session:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
with factory() as session:
yield session
Base.metadata.drop_all(engine)
engine.dispose()
def test_subscription_creation_issues_period_and_redacted_audit(db: Session) -> None:
start = datetime(2026, 1, 1, tzinfo=UTC)
end = datetime(2026, 2, 1, tzinfo=UTC)
_, subscription = _seed_subscription(
db,
"tenant-a",
start=start,
end=end,
request_prefix="initial",
)
periods = list(
db.scalars(
select(CommercialBillingPeriod).where(CommercialBillingPeriod.tenant_id == "tenant-a")
).all()
)
assert len(periods) == 1
assert periods[0].subscription_id == subscription.id
assert periods[0].period_start.replace(tzinfo=UTC) == start
assert periods[0].period_end.replace(tzinfo=UTC) == end
assert periods[0].base_fee_snapshot == Decimal("100.0000")
events = list(
db.scalars(
select(CommercialAdminEvent).where(CommercialAdminEvent.tenant_id == "tenant-a")
).all()
)
assert {event.action for event in events} >= {
"plan_created",
"plan_activated",
"subscription_created",
"billing_period_created",
}
serialized = str([(event.before_json, event.after_json) for event in events]).casefold()
for forbidden in (
"contract_terms_json",
"metadata_json",
"external_subscription_id",
"secret",
"password",
"token",
):
assert forbidden not in serialized
def test_auto_renew_rollover_is_idempotent_and_keeps_month_end_anchor(db: Session) -> None:
start = datetime(2027, 1, 31, tzinfo=UTC)
due = datetime(2027, 2, 28, tzinfo=UTC)
_, subscription = _seed_subscription(
db,
"tenant-rollover",
start=start,
end=due,
auto_renew=True,
request_prefix="rollover",
)
first = CommercialSubscriptionRolloverService(db).rollover_due(
"tenant-rollover",
subscription.id,
as_of=due,
)
replay = CommercialSubscriptionRolloverService(db).rollover_due(
"tenant-rollover",
subscription.id,
as_of=due,
)
assert first.status == "rolled_over"
assert len(first.created_period_ids) == 1
assert first.current_period_start == due
assert first.current_period_end == datetime(2027, 3, 31, tzinfo=UTC)
assert replay.status == "not_due"
periods = list(
db.scalars(
select(CommercialBillingPeriod)
.where(CommercialBillingPeriod.subscription_id == subscription.id)
.order_by(CommercialBillingPeriod.period_sequence)
).all()
)
assert [period.period_sequence for period in periods] == [1, 2]
assert periods[0].period_start.replace(tzinfo=UTC) == start
assert periods[0].period_end.replace(tzinfo=UTC) == due
assert periods[1].source == "auto_renew"
assert subscription.version == 2
actions = set(
db.scalars(
select(CommercialAdminEvent.action).where(
CommercialAdminEvent.tenant_id == "tenant-rollover"
)
).all()
)
assert {"billing_period_created", "subscription_rolled_over"} <= actions
@pytest.mark.parametrize(
("interval", "contract_end", "reason_code"),
[
("contract", None, "contract_interval_requires_explicit_renewal"),
(
"monthly",
datetime(2026, 2, 15, tzinfo=UTC),
"contract_boundary_requires_renewal",
),
],
)
def test_rollover_fails_closed_at_unprovable_contract_boundary(
db: Session,
interval: str,
contract_end: datetime | None,
reason_code: str,
) -> None:
tenant_id = f"tenant-{interval}"
_, subscription = _seed_subscription(
db,
tenant_id,
start=datetime(2026, 1, 1, tzinfo=UTC),
end=datetime(2026, 2, 1, tzinfo=UTC),
billing_interval=interval,
auto_renew=True,
contract_end=contract_end,
request_prefix=interval,
)
result = CommercialSubscriptionRolloverService(db).rollover_due(
tenant_id,
subscription.id,
as_of=datetime(2026, 2, 1, tzinfo=UTC),
)
assert result.status == "ineligible"
assert result.reason_code == reason_code
assert (
db.scalar(
select(CommercialBillingPeriod.period_sequence).where(
CommercialBillingPeriod.subscription_id == subscription.id
)
)
== 1
)
def _seed_subscription(
db: Session,
tenant_id: str,
*,
start: datetime,
end: datetime,
billing_interval: str = "monthly",
auto_renew: bool = False,
contract_end: datetime | None = None,
request_prefix: str,
):
admin = CommercialAdminService(db)
plan = admin.create_plan(
tenant_id,
CommercialPlanCreate(
plan_code="billing",
name="不可变账期测试套餐",
pricing_model="subscription",
billing_interval=billing_interval,
currency="CNY",
base_fee=Decimal("100"),
effective_from=start,
contract_terms_json={"secret": "must-not-enter-audit"},
),
actor_id="platform-admin",
request_id=f"{request_prefix}-plan-create",
)
admin.activate_plan(
tenant_id,
plan.id,
expected_version=plan.version,
actor_id="platform-admin",
request_id=f"{request_prefix}-plan-activate",
reason="测试激活套餐",
)
subscription = admin.create_subscription(
tenant_id,
CommercialSubscriptionCreate(
subscription_key=f"{tenant_id}-subscription",
plan_id=plan.id,
starts_at=start,
ends_at=contract_end,
current_period_start=start,
current_period_end=end,
seats=3,
auto_renew=auto_renew,
metadata_json={"token": "must-not-enter-audit"},
),
actor_id="platform-admin",
request_id=f"{request_prefix}-subscription-create",
)
db.flush()
return plan, subscription