aboutsummaryrefslogtreecommitdiff
path: root/tests/managers/thl/test_user_streak.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/managers/thl/test_user_streak.py')
-rw-r--r--tests/managers/thl/test_user_streak.py225
1 files changed, 225 insertions, 0 deletions
diff --git a/tests/managers/thl/test_user_streak.py b/tests/managers/thl/test_user_streak.py
new file mode 100644
index 0000000..7728f9f
--- /dev/null
+++ b/tests/managers/thl/test_user_streak.py
@@ -0,0 +1,225 @@
+import copy
+from datetime import datetime, timezone, timedelta, date
+from decimal import Decimal
+from zoneinfo import ZoneInfo
+
+import pytest
+
+from generalresearch.managers.thl.user_streak import compute_streaks_from_days
+from generalresearch.models.thl.definitions import StatusCode1, Status
+from generalresearch.models.thl.user_streak import (
+ UserStreak,
+ StreakState,
+ StreakPeriod,
+ StreakFulfillment,
+)
+
+
+def test_compute_streaks_from_days():
+ days = [
+ date(2026, 1, 1),
+ date(2026, 1, 4),
+ date(2026, 1, 5),
+ date(2026, 1, 6),
+ date(2026, 1, 8),
+ date(2026, 1, 9),
+ date(2026, 2, 11),
+ date(2026, 2, 12),
+ ]
+
+ # Active
+ today = date(2026, 2, 12)
+ res = compute_streaks_from_days(days, "us", period=StreakPeriod.DAY, today=today)
+ assert res == (2, 3, StreakState.ACTIVE, date(2026, 2, 12))
+
+ # At Risk
+ today = date(2026, 2, 13)
+ res = compute_streaks_from_days(days, "us", period=StreakPeriod.DAY, today=today)
+ assert res == (2, 3, StreakState.AT_RISK, date(2026, 2, 12))
+
+ # Broken
+ today = date(2026, 2, 14)
+ res = compute_streaks_from_days(days, "us", period=StreakPeriod.DAY, today=today)
+ assert res == (0, 3, StreakState.BROKEN, date(2026, 2, 12))
+
+ # Monthly, active
+ today = date(2026, 2, 14)
+ res = compute_streaks_from_days(days, "us", period=StreakPeriod.MONTH, today=today)
+ assert res == (2, 2, StreakState.ACTIVE, date(2026, 2, 1))
+
+ # monthly, at risk
+ today = date(2026, 3, 1)
+ res = compute_streaks_from_days(days, "us", period=StreakPeriod.MONTH, today=today)
+ assert res == (2, 2, StreakState.AT_RISK, date(2026, 2, 1))
+
+ # monthly, broken
+ today = date(2026, 4, 1)
+ res = compute_streaks_from_days(days, "us", period=StreakPeriod.MONTH, today=today)
+ assert res == (0, 2, StreakState.BROKEN, date(2026, 2, 1))
+
+
+@pytest.fixture
+def broken_active_streak(user):
+ return [
+ UserStreak(
+ period=StreakPeriod.DAY,
+ fulfillment=StreakFulfillment.ACTIVE,
+ country_iso="us",
+ user_id=user.user_id,
+ last_fulfilled_period_start=date(2025, 2, 11),
+ current_streak=0,
+ longest_streak=1,
+ state=StreakState.BROKEN,
+ ),
+ UserStreak(
+ period=StreakPeriod.WEEK,
+ fulfillment=StreakFulfillment.ACTIVE,
+ country_iso="us",
+ user_id=user.user_id,
+ last_fulfilled_period_start=date(2025, 2, 10),
+ current_streak=0,
+ longest_streak=1,
+ state=StreakState.BROKEN,
+ ),
+ UserStreak(
+ period=StreakPeriod.MONTH,
+ fulfillment=StreakFulfillment.ACTIVE,
+ country_iso="us",
+ user_id=user.user_id,
+ last_fulfilled_period_start=date(2025, 2, 1),
+ current_streak=0,
+ longest_streak=1,
+ state=StreakState.BROKEN,
+ ),
+ ]
+
+
+def create_session_fail(session_manager, start, user):
+ session = session_manager.create_dummy(started=start, country_iso="us", user=user)
+ session_manager.finish_with_status(
+ session,
+ finished=start + timedelta(minutes=1),
+ status=Status.FAIL,
+ status_code_1=StatusCode1.BUYER_FAIL,
+ )
+
+
+def create_session_complete(session_manager, start, user):
+ session = session_manager.create_dummy(started=start, country_iso="us", user=user)
+ session_manager.finish_with_status(
+ session,
+ finished=start + timedelta(minutes=1),
+ status=Status.COMPLETE,
+ status_code_1=StatusCode1.COMPLETE,
+ payout=Decimal(1),
+ )
+
+
+def test_user_streak_empty(user_streak_manager, user):
+ streaks = user_streak_manager.get_user_streaks(
+ user_id=user.user_id, country_iso="us"
+ )
+ assert streaks == []
+
+
+def test_user_streaks_active_broken(
+ user_streak_manager, user, session_manager, broken_active_streak
+):
+ # Testing active streak, but broken (not today or yesterday)
+ start1 = datetime(2025, 2, 12, tzinfo=timezone.utc)
+ end1 = start1 + timedelta(minutes=1)
+
+ # abandon counts as inactive
+ session = session_manager.create_dummy(started=start1, country_iso="us", user=user)
+ streak = user_streak_manager.get_user_streaks(user_id=user.user_id)
+ assert streak == []
+
+ # session start fail counts as inactive
+ session_manager.finish_with_status(
+ session,
+ finished=end1,
+ status=Status.FAIL,
+ status_code_1=StatusCode1.SESSION_START_QUALITY_FAIL,
+ )
+ streak = user_streak_manager.get_user_streaks(
+ user_id=user.user_id, country_iso="us"
+ )
+ assert streak == []
+
+ # Create another as a real failure
+ create_session_fail(session_manager, start1, user)
+
+ # This is going to be the day before b/c midnight utc is 8pm US
+ last_active_day = start1.astimezone(ZoneInfo("America/New_York")).date()
+ assert last_active_day == date(2025, 2, 11)
+ expected_streaks = broken_active_streak.copy()
+ streaks = user_streak_manager.get_user_streaks(user_id=user.user_id)
+ assert streaks == expected_streaks
+
+ # Create another the next day
+ start2 = start1 + timedelta(days=1)
+ create_session_fail(session_manager, start2, user)
+
+ last_active_day = start2.astimezone(ZoneInfo("America/New_York")).date()
+ expected_streaks = copy.deepcopy(broken_active_streak)
+ expected_streaks[0].longest_streak = 2
+ expected_streaks[0].last_fulfilled_period_start = last_active_day
+
+ streaks = user_streak_manager.get_user_streaks(
+ user_id=user.user_id, country_iso="us"
+ )
+ assert streaks == expected_streaks
+
+
+def test_user_streak_complete_active(user_streak_manager, user, session_manager):
+ """Testing active streak that is today"""
+
+ # They completed yesterday NY time. Today isn't over so streak is pending
+ start1 = datetime.now(tz=ZoneInfo("America/New_York")) - timedelta(days=1)
+ create_session_complete(session_manager, start1.astimezone(tz=timezone.utc), user)
+
+ last_complete_day = start1.date()
+ expected_streak = UserStreak(
+ longest_streak=1,
+ current_streak=1,
+ state=StreakState.AT_RISK,
+ last_fulfilled_period_start=last_complete_day,
+ country_iso="us",
+ user_id=user.user_id,
+ fulfillment=StreakFulfillment.COMPLETE,
+ period=StreakPeriod.DAY,
+ )
+ streaks = user_streak_manager.get_user_streaks(
+ user_id=user.user_id, country_iso="us"
+ )
+ streak = [
+ s
+ for s in streaks
+ if s.fulfillment == StreakFulfillment.COMPLETE and s.period == StreakPeriod.DAY
+ ][0]
+ assert streak == expected_streak
+
+ # And now they complete today
+ start2 = datetime.now(tz=ZoneInfo("America/New_York"))
+ create_session_complete(session_manager, start2.astimezone(tz=timezone.utc), user)
+ last_complete_day = start2.date()
+ expected_streak = UserStreak(
+ longest_streak=2,
+ current_streak=2,
+ state=StreakState.ACTIVE,
+ last_fulfilled_period_start=last_complete_day,
+ country_iso="us",
+ user_id=user.user_id,
+ fulfillment=StreakFulfillment.COMPLETE,
+ period=StreakPeriod.DAY,
+ )
+
+ streaks = user_streak_manager.get_user_streaks(
+ user_id=user.user_id, country_iso="us"
+ )
+ streak = [
+ s
+ for s in streaks
+ if s.fulfillment == StreakFulfillment.COMPLETE and s.period == StreakPeriod.DAY
+ ][0]
+ assert streak == expected_streak