feat: 6.0 - Backend API - GET /api/habits with streaks
This commit is contained in:
@@ -789,7 +789,7 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_habits_get(self):
|
||||
"""Get all habits from habits.json."""
|
||||
"""Get all habits from habits.json with calculated streaks."""
|
||||
try:
|
||||
habits_file = KANBAN_DIR / 'habits.json'
|
||||
|
||||
@@ -816,8 +816,26 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
habits = data.get('habits', [])
|
||||
last_updated = data.get('lastUpdated', datetime.now().isoformat())
|
||||
|
||||
# Get today's date in YYYY-MM-DD format
|
||||
today = datetime.now().date().isoformat()
|
||||
|
||||
# Enhance each habit with streak and checkedToday
|
||||
enhanced_habits = []
|
||||
for habit in habits:
|
||||
# Calculate streak using the utility function
|
||||
completions = habit.get('completions', [])
|
||||
frequency = habit.get('frequency', 'daily')
|
||||
streak = calculate_streak(completions, frequency)
|
||||
|
||||
# Check if habit was completed today
|
||||
checked_today = today in completions
|
||||
|
||||
# Add calculated fields to habit
|
||||
enhanced_habit = {**habit, 'streak': streak, 'checkedToday': checked_today}
|
||||
enhanced_habits.append(enhanced_habit)
|
||||
|
||||
self.send_json({
|
||||
'habits': habits,
|
||||
'habits': enhanced_habits,
|
||||
'lastUpdated': last_updated
|
||||
})
|
||||
except Exception as e:
|
||||
|
||||
252
dashboard/test_habits_get_enhanced.py
Normal file
252
dashboard/test_habits_get_enhanced.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for enhanced GET /api/habits endpoint with streak and checkedToday fields.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
BASE_URL = 'http://localhost:8088'
|
||||
KANBAN_DIR = Path(__file__).parent
|
||||
|
||||
|
||||
def test_habits_get_includes_streak_field():
|
||||
"""Test that each habit includes a 'streak' field."""
|
||||
# Create test habit with completions
|
||||
today = datetime.now().date()
|
||||
yesterday = today - timedelta(days=1)
|
||||
two_days_ago = today - timedelta(days=2)
|
||||
|
||||
test_data = {
|
||||
'habits': [
|
||||
{
|
||||
'id': 'habit-test1',
|
||||
'name': 'Test Habit',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': [
|
||||
two_days_ago.isoformat(),
|
||||
yesterday.isoformat(),
|
||||
today.isoformat()
|
||||
]
|
||||
}
|
||||
],
|
||||
'lastUpdated': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
habits_file = KANBAN_DIR / 'habits.json'
|
||||
habits_file.write_text(json.dumps(test_data, indent=2), encoding='utf-8')
|
||||
|
||||
# Test GET
|
||||
req = urllib.request.Request(f'{BASE_URL}/api/habits')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
assert 'habits' in result, "Response should contain habits array"
|
||||
assert len(result['habits']) == 1, "Should have one habit"
|
||||
habit = result['habits'][0]
|
||||
assert 'streak' in habit, "Habit should include 'streak' field"
|
||||
assert isinstance(habit['streak'], int), "Streak should be an integer"
|
||||
assert habit['streak'] == 3, f"Expected streak of 3, got {habit['streak']}"
|
||||
|
||||
print("✓ Each habit includes 'streak' field")
|
||||
|
||||
|
||||
def test_habits_get_includes_checked_today_field():
|
||||
"""Test that each habit includes a 'checkedToday' field."""
|
||||
today = datetime.now().date().isoformat()
|
||||
|
||||
test_data = {
|
||||
'habits': [
|
||||
{
|
||||
'id': 'habit-test1',
|
||||
'name': 'Checked Today',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': [today]
|
||||
},
|
||||
{
|
||||
'id': 'habit-test2',
|
||||
'name': 'Not Checked Today',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': ['2026-02-01']
|
||||
}
|
||||
],
|
||||
'lastUpdated': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
habits_file = KANBAN_DIR / 'habits.json'
|
||||
habits_file.write_text(json.dumps(test_data, indent=2), encoding='utf-8')
|
||||
|
||||
# Test GET
|
||||
req = urllib.request.Request(f'{BASE_URL}/api/habits')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
assert len(result['habits']) == 2, "Should have two habits"
|
||||
|
||||
habit1 = result['habits'][0]
|
||||
assert 'checkedToday' in habit1, "Habit should include 'checkedToday' field"
|
||||
assert isinstance(habit1['checkedToday'], bool), "checkedToday should be boolean"
|
||||
assert habit1['checkedToday'] is True, "Habit checked today should have checkedToday=True"
|
||||
|
||||
habit2 = result['habits'][1]
|
||||
assert 'checkedToday' in habit2, "Habit should include 'checkedToday' field"
|
||||
assert habit2['checkedToday'] is False, "Habit not checked today should have checkedToday=False"
|
||||
|
||||
print("✓ Each habit includes 'checkedToday' boolean field")
|
||||
|
||||
|
||||
def test_habits_get_calculates_streak_correctly():
|
||||
"""Test that streak is calculated using the streak utility function."""
|
||||
today = datetime.now().date()
|
||||
yesterday = today - timedelta(days=1)
|
||||
two_days_ago = today - timedelta(days=2)
|
||||
three_days_ago = today - timedelta(days=3)
|
||||
four_days_ago = today - timedelta(days=4)
|
||||
|
||||
test_data = {
|
||||
'habits': [
|
||||
{
|
||||
'id': 'habit-daily',
|
||||
'name': 'Daily Habit',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': [
|
||||
four_days_ago.isoformat(),
|
||||
three_days_ago.isoformat(),
|
||||
two_days_ago.isoformat(),
|
||||
yesterday.isoformat(),
|
||||
today.isoformat()
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'habit-broken',
|
||||
'name': 'Broken Streak',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': [
|
||||
four_days_ago.isoformat(),
|
||||
three_days_ago.isoformat()
|
||||
# Missing two_days_ago - streak broken
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'habit-weekly',
|
||||
'name': 'Weekly Habit',
|
||||
'frequency': 'weekly',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': [
|
||||
today.isoformat(),
|
||||
(today - timedelta(days=7)).isoformat(),
|
||||
(today - timedelta(days=14)).isoformat()
|
||||
]
|
||||
}
|
||||
],
|
||||
'lastUpdated': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
habits_file = KANBAN_DIR / 'habits.json'
|
||||
habits_file.write_text(json.dumps(test_data, indent=2), encoding='utf-8')
|
||||
|
||||
# Test GET
|
||||
req = urllib.request.Request(f'{BASE_URL}/api/habits')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
assert len(result['habits']) == 3, "Should have three habits"
|
||||
|
||||
daily_habit = result['habits'][0]
|
||||
assert daily_habit['streak'] == 5, f"Expected daily streak of 5, got {daily_habit['streak']}"
|
||||
|
||||
broken_habit = result['habits'][1]
|
||||
assert broken_habit['streak'] == 0, f"Expected broken streak of 0, got {broken_habit['streak']}"
|
||||
|
||||
weekly_habit = result['habits'][2]
|
||||
assert weekly_habit['streak'] == 3, f"Expected weekly streak of 3, got {weekly_habit['streak']}"
|
||||
|
||||
print("✓ Streak is calculated correctly using utility function")
|
||||
|
||||
|
||||
def test_habits_get_empty_habits_array():
|
||||
"""Test GET with empty habits array."""
|
||||
test_data = {
|
||||
'habits': [],
|
||||
'lastUpdated': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
habits_file = KANBAN_DIR / 'habits.json'
|
||||
habits_file.write_text(json.dumps(test_data, indent=2), encoding='utf-8')
|
||||
|
||||
# Test GET
|
||||
req = urllib.request.Request(f'{BASE_URL}/api/habits')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
assert result['habits'] == [], "Should return empty array"
|
||||
assert 'lastUpdated' in result, "Should include lastUpdated"
|
||||
|
||||
print("✓ Empty habits array handled correctly")
|
||||
|
||||
|
||||
def test_habits_get_preserves_original_fields():
|
||||
"""Test that all original habit fields are preserved."""
|
||||
today = datetime.now().date().isoformat()
|
||||
|
||||
test_data = {
|
||||
'habits': [
|
||||
{
|
||||
'id': 'habit-test1',
|
||||
'name': 'Test Habit',
|
||||
'frequency': 'daily',
|
||||
'createdAt': '2026-02-01T10:00:00Z',
|
||||
'completions': [today]
|
||||
}
|
||||
],
|
||||
'lastUpdated': '2026-02-10T10:00:00Z'
|
||||
}
|
||||
|
||||
habits_file = KANBAN_DIR / 'habits.json'
|
||||
habits_file.write_text(json.dumps(test_data, indent=2), encoding='utf-8')
|
||||
|
||||
# Test GET
|
||||
req = urllib.request.Request(f'{BASE_URL}/api/habits')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
habit = result['habits'][0]
|
||||
assert habit['id'] == 'habit-test1', "Original id should be preserved"
|
||||
assert habit['name'] == 'Test Habit', "Original name should be preserved"
|
||||
assert habit['frequency'] == 'daily', "Original frequency should be preserved"
|
||||
assert habit['createdAt'] == '2026-02-01T10:00:00Z', "Original createdAt should be preserved"
|
||||
assert habit['completions'] == [today], "Original completions should be preserved"
|
||||
assert 'streak' in habit, "Should add streak field"
|
||||
assert 'checkedToday' in habit, "Should add checkedToday field"
|
||||
|
||||
print("✓ All original habit fields are preserved")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
print("\n=== Testing Enhanced GET /api/habits ===\n")
|
||||
|
||||
test_habits_get_includes_streak_field()
|
||||
test_habits_get_includes_checked_today_field()
|
||||
test_habits_get_calculates_streak_correctly()
|
||||
test_habits_get_empty_habits_array()
|
||||
test_habits_get_preserves_original_fields()
|
||||
|
||||
print("\n=== All Enhanced GET Tests Passed ✓ ===\n")
|
||||
sys.exit(0)
|
||||
except AssertionError as e:
|
||||
print(f"\n❌ Test failed: {e}\n")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}\n")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user