| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- from __future__ import annotations
- from datetime import UTC, datetime, timedelta
- import pytest
- def test_leaderboard_labels_never_mention_members(app_modules):
- identity = app_modules.load("wbb.services.member_identity")
- assert (
- identity.non_mention_account_name(
- {
- "user_id": 10,
- "username": "alice",
- "first_name": "小明",
- "display_name": "小明 同学",
- }
- )
- == "小明 同学"
- )
- fallback = identity.non_mention_account_name(
- {
- "user_id": 11,
- "username": "alice",
- "first_name": "",
- "display_name": "",
- }
- )
- assert fallback == "用户 11"
- assert "@" not in fallback
- assert "tg://user" not in fallback
- async def test_adjustment_is_idempotent_and_balance_cannot_be_negative(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- account, created = await points.adjust_points(
- chat_id=-100,
- user_id=10,
- delta=20,
- source=points.SOURCE_ADMIN,
- idempotency_key="adjust:1",
- actor_id="admin",
- reason="seed",
- )
- assert created is True
- assert account["balance"] == 20
- account, created = await points.adjust_points(
- chat_id=-100,
- user_id=10,
- delta=20,
- source=points.SOURCE_ADMIN,
- idempotency_key="adjust:1",
- actor_id="admin",
- reason="duplicate",
- )
- assert created is False
- assert account["balance"] == 20
- with pytest.raises(points.InsufficientPoints):
- await points.adjust_points(
- chat_id=-100,
- user_id=10,
- delta=-21,
- source=points.SOURCE_ADMIN,
- idempotency_key="adjust:2",
- reason="too much",
- )
- async def test_noop_set_is_recorded_and_retry_does_not_overwrite_new_points(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- await points.adjust_points(
- chat_id=-100,
- user_id=10,
- delta=20,
- source=points.SOURCE_ADMIN,
- idempotency_key="set-seed",
- reason="seed",
- )
- account, created = await points.set_points(
- chat_id=-100,
- user_id=10,
- balance=20,
- actor_id="admin",
- reason="confirm balance",
- idempotency_key="set-request",
- )
- assert created is True
- assert account["balance"] == 20
- await points.adjust_points(
- chat_id=-100,
- user_id=10,
- delta=5,
- source=points.SOURCE_ADMIN,
- idempotency_key="set-later-award",
- reason="later award",
- )
- account, created = await points.set_points(
- chat_id=-100,
- user_id=10,
- balance=20,
- actor_id="admin",
- reason="retried request",
- idempotency_key="set-request",
- )
- assert created is False
- assert account["balance"] == 25
- transaction = await points.get_point_transaction_by_key("set-request")
- assert transaction["delta"] == 0
- async def test_checkin_uses_chat_timezone_day_boundary(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- await points.set_point_rules(-100, {"enabled": True, "timezone": "Asia/Shanghai"})
- before_midnight = datetime(2026, 1, 1, 15, 59, tzinfo=UTC)
- account, created = await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=before_midnight,
- )
- assert created is True
- assert account["balance"] == 10
- _, created = await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=before_midnight + timedelta(seconds=30),
- )
- assert created is False
- account, created = await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=before_midnight + timedelta(minutes=2),
- )
- assert created is True
- assert account["balance"] == 20
- async def test_checkin_messages_are_normalized_and_persisted(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- saved = await points.set_point_rules(
- -100,
- {
- "checkin_success_message": " 获得 {points},余额 {balance} ",
- "checkin_already_message": "",
- },
- )
- assert saved["checkin_success_message"] == "获得 {points},余额 {balance}"
- assert (
- saved["checkin_already_message"]
- == points.DEFAULT_CHECKIN_ALREADY_MESSAGE
- )
- loaded = await points.get_point_rules(-100)
- assert loaded["checkin_success_message"] == "获得 {points},余额 {balance}"
- async def test_checkin_metrics_include_daily_rank_streak_and_month_total(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- await points.set_point_rules(-100, {"enabled": True, "timezone": "Asia/Shanghai"})
- first_day = datetime(2026, 2, 1, 0, 0, tzinfo=UTC)
- await points.award_checkin(
- chat_id=-100,
- user_id=11,
- username="first",
- first_name="First",
- now=first_day,
- )
- await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=first_day + timedelta(minutes=1),
- )
- first_metrics = await points.get_checkin_metrics(
- chat_id=-100,
- user_id=10,
- now=first_day + timedelta(minutes=1),
- )
- assert first_metrics == {"rank": 2, "streak": 1, "month_days": 1}
- second_day = first_day + timedelta(days=1)
- await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=second_day,
- )
- second_metrics = await points.get_checkin_metrics(
- chat_id=-100,
- user_id=10,
- now=second_day,
- )
- assert second_metrics == {"rank": 1, "streak": 2, "month_days": 2}
- _, duplicate = await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=second_day + timedelta(hours=1),
- )
- assert duplicate is False
- assert await points.get_checkin_metrics(
- chat_id=-100,
- user_id=10,
- now=second_day + timedelta(hours=1),
- ) == {"rank": 1, "streak": 2, "month_days": 2}
- fourth_day = first_day + timedelta(days=3)
- await points.award_checkin(
- chat_id=-100,
- user_id=10,
- username="alice",
- first_name="Alice",
- now=fourth_day,
- )
- fourth_metrics = await points.get_checkin_metrics(
- chat_id=-100,
- user_id=10,
- now=fourth_day,
- )
- assert fourth_metrics == {"rank": 1, "streak": 1, "month_days": 3}
- async def test_checkin_streak_continues_across_month_boundary(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- await points.set_point_rules(-200, {"enabled": True, "timezone": "Asia/Shanghai"})
- february_last_day = datetime(2026, 2, 28, 0, 0, tzinfo=UTC)
- march_first_day = february_last_day + timedelta(days=1)
- for current in (february_last_day, march_first_day):
- await points.award_checkin(
- chat_id=-200,
- user_id=20,
- username="bob",
- first_name="Bob",
- now=current,
- )
- assert await points.get_checkin_metrics(
- chat_id=-200,
- user_id=20,
- now=march_first_day,
- ) == {"rank": 1, "streak": 2, "month_days": 1}
- async def test_activity_cooldown_duplicate_content_and_daily_cap(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- await points.set_point_rules(
- -100,
- {
- "enabled": True,
- "activity_points": 1,
- "activity_cooldown_seconds": 300,
- "activity_daily_cap": 2,
- },
- )
- now = datetime(2099, 1, 1, 4, 0, tzinfo=UTC)
- _, first = await points.award_activity(
- chat_id=-100,
- user_id=10,
- message_id=1,
- content="hello",
- username=None,
- first_name="Alice",
- now=now,
- )
- _, cooldown = await points.award_activity(
- chat_id=-100,
- user_id=10,
- message_id=2,
- content="another",
- username=None,
- first_name="Alice",
- now=now + timedelta(seconds=299),
- )
- _, repeated = await points.award_activity(
- chat_id=-100,
- user_id=10,
- message_id=3,
- content="hello",
- username=None,
- first_name="Alice",
- now=now + timedelta(seconds=301),
- )
- _, second = await points.award_activity(
- chat_id=-100,
- user_id=10,
- message_id=4,
- content="different",
- username=None,
- first_name="Alice",
- now=now + timedelta(seconds=301),
- )
- _, capped = await points.award_activity(
- chat_id=-100,
- user_id=10,
- message_id=5,
- content="third",
- username=None,
- first_name="Alice",
- now=now + timedelta(seconds=602),
- )
- assert (first, cooldown, repeated, second, capped) == (True, False, False, True, False)
- assert (await points.get_point_account(-100, 10))["balance"] == 2
- async def test_upvote_pair_cooldown_and_reconciliation(app_modules):
- points = app_modules.load("wbb.utils.dbpoints")
- await points.set_point_rules(-100, {"enabled": True})
- now = datetime.now(UTC)
- _, first = await points.award_upvote(
- chat_id=-100,
- voter_id=20,
- target_id=10,
- message_id=1,
- username=None,
- first_name="Alice",
- now=now,
- )
- _, blocked = await points.award_upvote(
- chat_id=-100,
- voter_id=20,
- target_id=10,
- message_id=2,
- username=None,
- first_name="Alice",
- now=now + timedelta(hours=23),
- )
- _, next_day = await points.award_upvote(
- chat_id=-100,
- voter_id=20,
- target_id=10,
- message_id=3,
- username=None,
- first_name="Alice",
- now=now + timedelta(hours=25),
- )
- assert (first, blocked, next_day) == (True, False, True)
- await points.accountsdb.update_one(
- {"chat_id": -100, "user_id": 10}, {"$set": {"balance": 999}}
- )
- repaired = await points.reconcile_account(-100, 10)
- assert repaired["balance"] == 4
|