#!/usr/bin/env python3 """ Tests for streak calculation utility. Story 4.0: Backend API - Streak calculation utility """ import sys from pathlib import Path from datetime import datetime, timedelta # Add dashboard to path to import api module sys.path.insert(0, str(Path(__file__).parent)) from api import calculate_streak def test_no_completions(): """Returns 0 for no completions""" assert calculate_streak([], 'daily') == 0 assert calculate_streak([], 'weekly') == 0 print("✓ No completions returns 0") def test_daily_single_completion_today(): """Single completion today counts as streak of 1""" today = datetime.now().isoformat() assert calculate_streak([today], 'daily') == 1 print("✓ Daily: single completion today = streak 1") def test_daily_single_completion_yesterday(): """Single completion yesterday counts as streak of 1""" yesterday = (datetime.now() - timedelta(days=1)).isoformat() assert calculate_streak([yesterday], 'daily') == 1 print("✓ Daily: single completion yesterday = streak 1") def test_daily_consecutive_days(): """Multiple consecutive days count correctly""" completions = [ (datetime.now() - timedelta(days=i)).isoformat() for i in range(5) # Today, yesterday, 2 days ago, 3 days ago, 4 days ago ] assert calculate_streak(completions, 'daily') == 5 print("✓ Daily: 5 consecutive days = streak 5") def test_daily_broken_streak(): """Gap in daily completions breaks streak""" today = datetime.now() completions = [ today.isoformat(), (today - timedelta(days=1)).isoformat(), # Gap here (day 2 missing) (today - timedelta(days=3)).isoformat(), (today - timedelta(days=4)).isoformat(), ] # Should count only today and yesterday before the gap assert calculate_streak(completions, 'daily') == 2 print("✓ Daily: gap breaks streak (counts only before gap)") def test_daily_old_completion(): """Completion more than 1 day ago returns 0""" two_days_ago = (datetime.now() - timedelta(days=2)).isoformat() assert calculate_streak([two_days_ago], 'daily') == 0 print("✓ Daily: completion >1 day ago = streak 0") def test_weekly_single_completion_this_week(): """Single completion this week counts as streak of 1""" today = datetime.now().isoformat() assert calculate_streak([today], 'weekly') == 1 print("✓ Weekly: single completion this week = streak 1") def test_weekly_consecutive_weeks(): """Multiple consecutive weeks count correctly""" today = datetime.now() completions = [ today.isoformat(), (today - timedelta(days=7)).isoformat(), (today - timedelta(days=14)).isoformat(), (today - timedelta(days=21)).isoformat(), ] assert calculate_streak(completions, 'weekly') == 4 print("✓ Weekly: 4 consecutive weeks = streak 4") def test_weekly_broken_streak(): """Missing week breaks streak""" today = datetime.now() completions = [ today.isoformat(), (today - timedelta(days=7)).isoformat(), # Gap here (week 2 missing) (today - timedelta(days=21)).isoformat(), ] # Should count only current week and last week before the gap assert calculate_streak(completions, 'weekly') == 2 print("✓ Weekly: missing week breaks streak") def test_weekly_old_completion(): """Completion more than 7 days ago returns 0""" eight_days_ago = (datetime.now() - timedelta(days=8)).isoformat() assert calculate_streak([eight_days_ago], 'weekly') == 0 print("✓ Weekly: completion >7 days ago = streak 0") def test_multiple_completions_same_day(): """Multiple completions on same day count as one""" today = datetime.now() completions = [ today.isoformat(), (today - timedelta(hours=2)).isoformat(), # Same day, different time (today - timedelta(days=1)).isoformat(), ] assert calculate_streak(completions, 'daily') == 2 print("✓ Daily: multiple completions same day = 1 day") def test_todays_completion_counts(): """Today's completion counts even if yesterday was missed""" today = datetime.now() completions = [ today.isoformat(), # Yesterday missing (today - timedelta(days=2)).isoformat(), ] # Should count only today (yesterday breaks the streak to previous days) assert calculate_streak(completions, 'daily') == 1 print("✓ Daily: today counts even if yesterday missed") def test_invalid_date_format(): """Invalid date format returns 0""" assert calculate_streak(['not-a-date'], 'daily') == 0 assert calculate_streak(['2026-13-45'], 'daily') == 0 print("✓ Invalid date format returns 0") def test_weekly_multiple_in_same_week(): """Multiple completions in same week count as one week""" today = datetime.now() completions = [ today.isoformat(), (today - timedelta(days=2)).isoformat(), # Same week (today - timedelta(days=4)).isoformat(), # Same week (today - timedelta(days=7)).isoformat(), # Previous week ] assert calculate_streak(completions, 'weekly') == 2 print("✓ Weekly: multiple in same week = 1 week") def run_all_tests(): """Run all streak calculation tests""" print("\n=== Testing Streak Calculation ===\n") test_no_completions() test_daily_single_completion_today() test_daily_single_completion_yesterday() test_daily_consecutive_days() test_daily_broken_streak() test_daily_old_completion() test_weekly_single_completion_this_week() test_weekly_consecutive_weeks() test_weekly_broken_streak() test_weekly_old_completion() test_multiple_completions_same_day() test_todays_completion_counts() test_invalid_date_format() test_weekly_multiple_in_same_week() print("\n✓ All streak calculation tests passed!\n") if __name__ == '__main__': run_all_tests()