Files
live-hub-py/tests/test_douyu_collab_orders.py
T

396 lines
13 KiB
Python

"""斗鱼外部单协作:账号导入、状态机、鱼翅/积分与兑换记录查询。"""
import pytest
from tests.support.db import get_test_engine, make_test_session
from web.backend.models import Account, User
from web.backend.schemas import DouyuCollabAccountLookupOut, DouyuCollabOrderOut
from web.backend.services import douyu_collab_service as collab
class FakeClient:
def __init__(self, *, points=5000, fish=1000, records=None):
self.points = points
self.fish = fish
self.records = records if records is not None else []
def elite_user_info(self, **_):
return {
"manual_type": 1,
"manual_score": self.points,
"expire_time": 0,
"user_info": {"nickname": "斗鱼昵称"},
}
def bind_info(self, **_):
return {
"role_name": "角色A",
"area_name": "微信",
"plat_name": "苹果(iOS)",
"nickname": "微信昵称",
}
def gold_account(self):
return {"gold": self.fish}
def list_goods(self, **_):
return {
"goods": [
{
"commodityId": "gift-a",
"commodityName": "礼包A",
"score": 100,
"status": 0,
"openStatus": 1,
"mark": "",
"count": 10,
}
]
}
def exchange_records(self, **_):
return {"records": self.records}
def exchange_orders(self, **_):
return {"orders": []}
class FakeNoBindClient(FakeClient):
def bind_info(self, **_):
raise RuntimeError("未绑定角色")
class TestDouyuCollabOrders:
def setup_method(self):
self.engine = get_test_engine()
self.session = make_test_session(self.engine)
self.admin = User(
username="douyu-collab-admin",
password_hash="hash",
role="super_admin",
is_active=True,
)
self.session.add(self.admin)
self.session.commit()
self.account = Account(
username="douyu-account-a",
password="password",
email="a@example.com",
email_password="mail-password",
cookie="acf_uid=100; acf_nickname=test",
)
self.session.add(self.account)
self.session.commit()
def teardown_method(self):
self.session.close()
def test_lookup_account_resolves_internal_ck_and_role(self):
result = collab.lookup_account(
self.session, "douyu-account-a", client=FakeClient()
)
assert result["found"] is True
assert result["ok"] is True
assert result["account_id"] == self.account.id
assert result["bind_game_role"] == "角色A"
assert result["manual_score"] == 5000
assert result["fish_fin_balance"] == 1000
assert result["goods"][0]["commodity_id"] == "gift-a"
DouyuCollabAccountLookupOut.model_validate(result)
def test_create_strict_order_uses_internal_account_and_advances(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-001",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-1.png"],
client=FakeClient(),
)
assert order.account_id == self.account.id
assert order.bind_game_role == "角色A"
assert order.points_balance == 5000
assert order.fish_fin_balance == 1000
assert order.status == collab.STATUS_TO_RECHARGE
DouyuCollabOrderOut.model_validate(collab.serialize_one(self.session, order))
def test_same_account_allows_multiple_orders(self):
collab.create_order(
self.session,
self.admin,
order_no="DY-002",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-1.png"],
client=FakeClient(),
)
second = collab.create_order(
self.session,
self.admin,
order_no="DY-003",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-2.png"],
client=FakeClient(),
)
assert second.account_id == self.account.id
assert collab.group_points_total(self.session, second) == 200
def test_status_flow_and_exchange_record_marks_done(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-004",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-3.png"],
client=FakeClient(),
)
collab.set_status(
self.session,
self.admin,
order,
status=collab.STATUS_RECHARGED,
reason="鱼翅已充值",
)
collab.set_status(
self.session,
self.admin,
order,
status=collab.STATUS_SCRIPTED,
reason="脚本已上",
)
result = collab.query_activity(
self.session,
self.admin,
order,
client=FakeClient(records=[{"commodityName": "礼包A"}]),
)
assert result["verdict"] == collab.VERDICT_SUCCESS
assert result["order"].status == collab.STATUS_DONE
assert result["order"].done_at is not None
def test_points_shortage_enters_points_error(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-005",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-4.png"],
client=FakeClient(),
)
collab.set_status(
self.session,
self.admin,
order,
status=collab.STATUS_RECHARGED,
reason="鱼翅已充值",
)
order.points_threshold = 10
self.session.commit()
result = collab.query_activity(
self.session,
self.admin,
order,
client=FakeClient(points=50, records=[]),
)
assert result["points_reason"] == "shortage"
assert result["order"].status == collab.STATUS_POINTS_ERROR
def test_draft_can_be_created_before_account_resolution(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-006",
shop="",
account_username="not-exists-yet",
draft=True,
)
assert order.status == collab.STATUS_INCOMPLETE
assert order.account_id is None
assert order.account_username == "not-exists-yet"
def test_draft_existing_username_links_account_and_can_be_completed(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-009",
shop="店铺A",
account_username="douyu-account-a",
draft=True,
)
assert order.account_id == self.account.id
collab.update_order(
self.session,
self.admin,
order,
{
"account_username": "douyu-account-a",
"gift_name": "礼包A",
"commodity_id": "gift-a",
"required_points": 100,
"home_images": ["collab/home-7.png"],
},
client=FakeClient(),
)
self.session.refresh(order)
assert order.bind_game_role == "角色A"
assert order.status == collab.STATUS_TO_RECHARGE
assert collab.serialize_one(self.session, order)["missing_fields"] == []
def test_missing_bind_role_keeps_incomplete_and_explains_why(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-011",
shop="店铺A",
account_username="douyu-account-a",
draft=True,
)
collab.update_order(
self.session,
self.admin,
order,
{
"account_username": "douyu-account-a",
"gift_name": "礼包A",
"commodity_id": "gift-a",
"required_points": 100,
"home_images": ["collab/home-missing-role.png"],
},
client=FakeNoBindClient(),
)
self.session.refresh(order)
assert order.status == collab.STATUS_INCOMPLETE
payload = collab.serialize_one(self.session, order)
assert "绑定角色(先校验账号)" in payload["missing_fields"]
DouyuCollabOrderOut.model_validate(payload)
def test_done_order_is_not_counted_again_in_group_points(self):
first = collab.create_order(
self.session,
self.admin,
order_no="DY-007",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-5.png"],
client=FakeClient(),
)
second = collab.create_order(
self.session,
self.admin,
order_no="DY-008",
shop="店铺A",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-6.png"],
client=FakeClient(),
)
assert collab.group_points_total(self.session, second) == 200
collab.set_status(
self.session,
self.admin,
first,
status=collab.STATUS_DONE,
reason="已兑换",
)
assert collab.group_points_total(self.session, second) == 100
def test_goods_normalization_keeps_zero_values(self):
goods = collab.serialize_live_goods(
[
{
"commodityId": "gift-23",
"commodityName": "23返场礼包",
"score": 8000,
"status": 0,
"openStatus": 0,
"startTime": "1792749600",
"endTime": "1793375999",
"mark": "限时",
"count": 2000,
}
]
)
assert goods[0]["status"] == "0"
assert goods[0]["open_status"] == 0
assert goods[0]["start_time"] == 1792749600
assert goods[0]["is_limited"] is True
def test_shop_rename_cascades_and_delete_rejects_in_use(self):
shop = collab.create_shop(self.session, self.admin, name="店铺B", remark="测试")
order = collab.create_order(
self.session,
self.admin,
order_no="DY-009",
shop="店铺B",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-7.png"],
client=FakeClient(),
)
collab.update_shop(
self.session,
self.admin,
shop,
{"name": "店铺B-改名", "is_active": False},
)
self.session.refresh(order)
assert order.shop == "店铺B-改名"
assert shop.is_active is False
with pytest.raises(collab.CollabError):
collab.delete_shop(self.session, self.admin, shop)
collab.delete_order(self.session, self.admin, order)
assert collab.delete_shop(self.session, self.admin, shop)["success"] is True
def test_ck_status_filter_and_summary_count_invalid(self):
order = collab.create_order(
self.session,
self.admin,
order_no="DY-010",
shop="店铺C",
account_username="douyu-account-a",
gift_name="礼包A",
required_points=100,
home_images=["collab/home-8.png"],
client=FakeClient(),
)
self.account.ck_check_status = collab.CK_INVALID
self.session.commit()
invalid = collab.list_orders(self.session, ck_status=collab.CK_INVALID)
assert [item["id"] for item in invalid["items"]] == [order.id]
assert collab.list_orders(self.session, ck_status=collab.CK_VALID)["total"] == 0
assert collab.summary(self.session)["ck_invalid"] == 1
def test_douyu_collab_routes_registered(self):
from web.backend.main import app
paths = app.openapi()["paths"]
assert "/api/douyu/external-orders" in paths
assert "/api/douyu/external-orders/{order_id}/status" in paths
assert "/api/douyu/external-orders/status/batch" in paths
assert "/api/douyu/external-orders/verify/batch" in paths
assert "/api/douyu/external-orders/accounts/lookup" in paths
assert "/api/douyu/collab-shops/{shop_id}" in paths
assert "put" in paths["/api/douyu/collab-shops/{shop_id}"]
assert "delete" in paths["/api/douyu/collab-shops/{shop_id}"]