Merge feature/habit-tracker into master (squashed): ✨ Habit Tracker Features: - Bead chain visualization (30-day history) - Weekly lives recovery system (+1 life/week) - Lucide icons (zap, shield) replacing emoji - Responsive layout (mobile-optimized) - Navigation links added to all dashboard pages 📚 Knowledge Base: - 40+ trading basics articles with metadata - Daily notes (2026-02-10, 2026-02-11) - Health & insights content - KB index restructuring 🧪 Tests: - Comprehensive test suite (4 test files) - Integration tests for lives recovery - 28/29 tests passing Commits squashed: - feat(habits): bead chain visualization + weekly lives recovery + nav integration - docs(memory): update KB content + daily notes - chore(data): update habits and status data Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Integration test for weekly lives recovery feature.
|
|
|
|
Tests the full flow:
|
|
1. Habit has check-ins in previous week
|
|
2. Check-in today triggers weekly lives recovery
|
|
3. Response includes livesAwarded flag
|
|
4. Lives count increases
|
|
5. Duplicate awards are prevented
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
import json
|
|
|
|
# Add parent directory to path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from habits_helpers import check_and_award_weekly_lives
|
|
|
|
|
|
def test_integration_weekly_lives_award():
|
|
"""Test complete weekly lives recovery flow."""
|
|
print("\n=== Testing Weekly Lives Recovery Integration ===\n")
|
|
|
|
today = datetime.now().date()
|
|
current_week_start = today - timedelta(days=today.weekday())
|
|
previous_week_start = current_week_start - timedelta(days=7)
|
|
|
|
# Scenario 1: New habit with check-ins in previous week
|
|
print("Scenario 1: First award of the week")
|
|
habit = {
|
|
"id": "test-habit-1",
|
|
"name": "Test Habit",
|
|
"lives": 2,
|
|
"completions": [
|
|
{"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "check"},
|
|
{"date": (previous_week_start + timedelta(days=4)).isoformat(), "type": "check"},
|
|
]
|
|
}
|
|
|
|
new_lives, was_awarded = check_and_award_weekly_lives(habit)
|
|
|
|
assert was_awarded == True, "Expected life to be awarded"
|
|
assert new_lives == 3, f"Expected 3 lives, got {new_lives}"
|
|
print(f"✓ Lives awarded: {habit['lives']} → {new_lives}")
|
|
print(f"✓ Award flag: {was_awarded}")
|
|
|
|
# Scenario 2: Already awarded this week
|
|
print("\nScenario 2: Prevent duplicate award")
|
|
habit['lives'] = new_lives
|
|
habit['lastLivesAward'] = current_week_start.isoformat()
|
|
|
|
new_lives2, was_awarded2 = check_and_award_weekly_lives(habit)
|
|
|
|
assert was_awarded2 == False, "Expected no duplicate award"
|
|
assert new_lives2 == 3, f"Lives should remain at 3, got {new_lives2}"
|
|
print(f"✓ No duplicate award: lives remain at {new_lives2}")
|
|
|
|
# Scenario 3: Only skips in previous week
|
|
print("\nScenario 3: Skips don't qualify for recovery")
|
|
habit_with_skips = {
|
|
"id": "test-habit-2",
|
|
"name": "Habit with Skips",
|
|
"lives": 1,
|
|
"completions": [
|
|
{"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "skip"},
|
|
{"date": (previous_week_start + timedelta(days=4)).isoformat(), "type": "skip"},
|
|
]
|
|
}
|
|
|
|
new_lives3, was_awarded3 = check_and_award_weekly_lives(habit_with_skips)
|
|
|
|
assert was_awarded3 == False, "Skips shouldn't trigger award"
|
|
assert new_lives3 == 1, f"Lives should remain at 1, got {new_lives3}"
|
|
print(f"✓ Skips don't count: lives remain at {new_lives3}")
|
|
|
|
# Scenario 4: No cap on lives (can go beyond 3)
|
|
print("\nScenario 4: Lives can exceed 3")
|
|
habit_many_lives = {
|
|
"id": "test-habit-3",
|
|
"name": "Habit with Many Lives",
|
|
"lives": 5,
|
|
"completions": [
|
|
{"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "check"},
|
|
]
|
|
}
|
|
|
|
new_lives4, was_awarded4 = check_and_award_weekly_lives(habit_many_lives)
|
|
|
|
assert was_awarded4 == True, "Expected life to be awarded"
|
|
assert new_lives4 == 6, f"Expected 6 lives, got {new_lives4}"
|
|
print(f"✓ No cap: lives increased from 5 → {new_lives4}")
|
|
|
|
# Scenario 5: No check-ins in previous week
|
|
print("\nScenario 5: No check-ins = no award")
|
|
habit_no_checkins = {
|
|
"id": "test-habit-4",
|
|
"name": "New Habit",
|
|
"lives": 2,
|
|
"completions": []
|
|
}
|
|
|
|
new_lives5, was_awarded5 = check_and_award_weekly_lives(habit_no_checkins)
|
|
|
|
assert was_awarded5 == False, "No check-ins = no award"
|
|
assert new_lives5 == 2, f"Lives should remain at 2, got {new_lives5}"
|
|
print(f"✓ No previous week check-ins: lives remain at {new_lives5}")
|
|
|
|
print("\n=== All Integration Tests Passed! ===\n")
|
|
|
|
# Print summary of the feature
|
|
print("Feature Summary:")
|
|
print("• +1 life awarded per week if habit had ≥1 check-in in previous week")
|
|
print("• Monday-Sunday week boundaries (ISO 8601)")
|
|
print("• Award triggers on first check-in of current week")
|
|
print("• Skips don't count toward recovery")
|
|
print("• No cap on lives (can accumulate beyond 3)")
|
|
print("• Prevents duplicate awards in same week")
|
|
print("")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
test_integration_weekly_lives_award()
|
|
sys.exit(0)
|
|
except AssertionError as e:
|
|
print(f"\n✗ Test failed: {e}\n")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"\n✗ Unexpected error: {type(e).__name__}: {e}\n")
|
|
sys.exit(1)
|