test_points.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. from __future__ import annotations
  2. from datetime import UTC, datetime, timedelta
  3. import pytest
  4. async def test_adjustment_is_idempotent_and_balance_cannot_be_negative(app_modules):
  5. points = app_modules.load("wbb.utils.dbpoints")
  6. account, created = await points.adjust_points(
  7. chat_id=-100,
  8. user_id=10,
  9. delta=20,
  10. source=points.SOURCE_ADMIN,
  11. idempotency_key="adjust:1",
  12. actor_id="admin",
  13. reason="seed",
  14. )
  15. assert created is True
  16. assert account["balance"] == 20
  17. account, created = await points.adjust_points(
  18. chat_id=-100,
  19. user_id=10,
  20. delta=20,
  21. source=points.SOURCE_ADMIN,
  22. idempotency_key="adjust:1",
  23. actor_id="admin",
  24. reason="duplicate",
  25. )
  26. assert created is False
  27. assert account["balance"] == 20
  28. with pytest.raises(points.InsufficientPoints):
  29. await points.adjust_points(
  30. chat_id=-100,
  31. user_id=10,
  32. delta=-21,
  33. source=points.SOURCE_ADMIN,
  34. idempotency_key="adjust:2",
  35. reason="too much",
  36. )
  37. async def test_noop_set_is_recorded_and_retry_does_not_overwrite_new_points(app_modules):
  38. points = app_modules.load("wbb.utils.dbpoints")
  39. await points.adjust_points(
  40. chat_id=-100,
  41. user_id=10,
  42. delta=20,
  43. source=points.SOURCE_ADMIN,
  44. idempotency_key="set-seed",
  45. reason="seed",
  46. )
  47. account, created = await points.set_points(
  48. chat_id=-100,
  49. user_id=10,
  50. balance=20,
  51. actor_id="admin",
  52. reason="confirm balance",
  53. idempotency_key="set-request",
  54. )
  55. assert created is True
  56. assert account["balance"] == 20
  57. await points.adjust_points(
  58. chat_id=-100,
  59. user_id=10,
  60. delta=5,
  61. source=points.SOURCE_ADMIN,
  62. idempotency_key="set-later-award",
  63. reason="later award",
  64. )
  65. account, created = await points.set_points(
  66. chat_id=-100,
  67. user_id=10,
  68. balance=20,
  69. actor_id="admin",
  70. reason="retried request",
  71. idempotency_key="set-request",
  72. )
  73. assert created is False
  74. assert account["balance"] == 25
  75. transaction = await points.get_point_transaction_by_key("set-request")
  76. assert transaction["delta"] == 0
  77. async def test_checkin_uses_chat_timezone_day_boundary(app_modules):
  78. points = app_modules.load("wbb.utils.dbpoints")
  79. await points.set_point_rules(-100, {"enabled": True, "timezone": "Asia/Shanghai"})
  80. before_midnight = datetime(2026, 1, 1, 15, 59, tzinfo=UTC)
  81. account, created = await points.award_checkin(
  82. chat_id=-100,
  83. user_id=10,
  84. username="alice",
  85. first_name="Alice",
  86. now=before_midnight,
  87. )
  88. assert created is True
  89. assert account["balance"] == 10
  90. _, created = await points.award_checkin(
  91. chat_id=-100,
  92. user_id=10,
  93. username="alice",
  94. first_name="Alice",
  95. now=before_midnight + timedelta(seconds=30),
  96. )
  97. assert created is False
  98. account, created = await points.award_checkin(
  99. chat_id=-100,
  100. user_id=10,
  101. username="alice",
  102. first_name="Alice",
  103. now=before_midnight + timedelta(minutes=2),
  104. )
  105. assert created is True
  106. assert account["balance"] == 20
  107. async def test_activity_cooldown_duplicate_content_and_daily_cap(app_modules):
  108. points = app_modules.load("wbb.utils.dbpoints")
  109. await points.set_point_rules(
  110. -100,
  111. {
  112. "enabled": True,
  113. "activity_points": 1,
  114. "activity_cooldown_seconds": 300,
  115. "activity_daily_cap": 2,
  116. },
  117. )
  118. now = datetime.now(UTC)
  119. _, first = await points.award_activity(
  120. chat_id=-100,
  121. user_id=10,
  122. message_id=1,
  123. content="hello",
  124. username=None,
  125. first_name="Alice",
  126. now=now,
  127. )
  128. _, cooldown = await points.award_activity(
  129. chat_id=-100,
  130. user_id=10,
  131. message_id=2,
  132. content="another",
  133. username=None,
  134. first_name="Alice",
  135. now=now + timedelta(seconds=299),
  136. )
  137. _, repeated = await points.award_activity(
  138. chat_id=-100,
  139. user_id=10,
  140. message_id=3,
  141. content="hello",
  142. username=None,
  143. first_name="Alice",
  144. now=now + timedelta(seconds=301),
  145. )
  146. _, second = await points.award_activity(
  147. chat_id=-100,
  148. user_id=10,
  149. message_id=4,
  150. content="different",
  151. username=None,
  152. first_name="Alice",
  153. now=now + timedelta(seconds=301),
  154. )
  155. _, capped = await points.award_activity(
  156. chat_id=-100,
  157. user_id=10,
  158. message_id=5,
  159. content="third",
  160. username=None,
  161. first_name="Alice",
  162. now=now + timedelta(seconds=602),
  163. )
  164. assert (first, cooldown, repeated, second, capped) == (True, False, False, True, False)
  165. assert (await points.get_point_account(-100, 10))["balance"] == 2
  166. async def test_upvote_pair_cooldown_and_reconciliation(app_modules):
  167. points = app_modules.load("wbb.utils.dbpoints")
  168. await points.set_point_rules(-100, {"enabled": True})
  169. now = datetime.now(UTC)
  170. _, first = await points.award_upvote(
  171. chat_id=-100,
  172. voter_id=20,
  173. target_id=10,
  174. message_id=1,
  175. username=None,
  176. first_name="Alice",
  177. now=now,
  178. )
  179. _, blocked = await points.award_upvote(
  180. chat_id=-100,
  181. voter_id=20,
  182. target_id=10,
  183. message_id=2,
  184. username=None,
  185. first_name="Alice",
  186. now=now + timedelta(hours=23),
  187. )
  188. _, next_day = await points.award_upvote(
  189. chat_id=-100,
  190. voter_id=20,
  191. target_id=10,
  192. message_id=3,
  193. username=None,
  194. first_name="Alice",
  195. now=now + timedelta(hours=25),
  196. )
  197. assert (first, blocked, next_day) == (True, False, True)
  198. await points.accountsdb.update_one(
  199. {"chat_id": -100, "user_id": 10}, {"$set": {"balance": 999}}
  200. )
  201. repaired = await points.reconcile_account(-100, 10)
  202. assert repaired["balance"] == 4