Files
clawd/dashboard/test_habits_streak.py

180 lines
5.9 KiB
Python

#!/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()