#!/usr/bin/env python3 """ Unit tests for Habit Bot Tests: habit tracking, food logging, data persistence """ import pytest import sys import os import json from datetime import datetime, timedelta from unittest.mock import Mock, patch, MagicMock # Add parent directory to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Test data directory TEST_DATA_DIR = '/tmp/test_habit_bot' os.makedirs(TEST_DATA_DIR, exist_ok=True) @pytest.fixture def mock_data(): """Create mock data for testing""" return { 'users': {}, 'habits': {}, 'habit_logs': {}, 'food_logs': {}, 'sessions': {} } @pytest.fixture def app_with_mock_data(mock_data): """Create app with mock data""" with patch('builtins.open', side_effect=lambda f, *args, **kwargs: (MagicMock() if 'write' in str(f) else (MagicMock() if any(x in str(f) for x in ['users.json', 'habits.json', 'habit_logs.json', 'food_logs.json', 'sessions.json']) else open(f, *args, **kwargs)))): pass # Mock load_json and save_json def mock_load_json(f): if 'users' in str(f): return mock_data['users'] elif 'habits' in str(f): return mock_data['habits'] elif 'habit_logs' in str(f): return mock_data['habit_logs'] elif 'food_logs' in str(f): return mock_data['food_logs'] elif 'sessions' in str(f): return mock_data['sessions'] return {} with patch('builtins.open', side_effect=lambda f, mode='r', *args, **kwargs: (MagicMock(__enter__=MagicMock(return_value=StringIO(json.dumps(mock_data.get(f.split('/')[-1], {}))), __exit__=MagicMock(return_value=False)) if any(x in str(f) for x in ['users', 'habits', 'habit_logs', 'food_logs', 'sessions']) else open(f, mode, *args, **kwargs))): with patch('habit_bot.load_json', side_effect=mock_load_json): yield mock_data class TestHabitBot: """Test habit tracking functionality""" def test_add_habit(self, mock_data): """Test adding a new habit""" habit_name = "morning workout" # Simulate adding habit user_id = "12345" if user_id not in mock_data['habits']: mock_data['habits'][user_id] = {} mock_data['habits'][user_id][habit_name] = { 'name': habit_name, 'streak': 0, 'created_at': datetime.now().isoformat(), 'is_active': True } assert habit_name in mock_data['habits'][user_id] assert mock_data['habits'][user_id][habit_name]['streak'] == 0 print(f"✅ Added habit: {habit_name}") def test_log_habit_completion(self, mock_data): """Test logging habit completion""" habit_name = "read books" user_id = "12345" today = datetime.now().strftime('%Y-%m-%d') # Initialize data if user_id not in mock_data['habits']: mock_data['habits'][user_id] = {} mock_data['habits'][user_id][habit_name] = {'streak': 5} if user_id not in mock_data['habit_logs']: mock_data['habit_logs'][user_id] = {} if today not in mock_data['habit_logs'][user_id]: mock_data['habit_logs'][user_id][today] = [] # Log completion mock_data['habit_logs'][user_id][today].append({ 'habit_name': habit_name, 'status': 'completed', 'notes': '30 minutes reading', 'timestamp': datetime.now().isoformat() }) # Update streak mock_data['habits'][user_id][habit_name]['streak'] += 1 assert len(mock_data['habit_logs'][user_id][today]) == 1 assert mock_data['habits'][user_id][habit_name]['streak'] == 6 print(f"✅ Logged habit: {habit_name} (streak: 6)") def test_habit_streak_calculation(self, mock_data): """Test streak calculation""" user_id = "12345" habit_name = "exercise" # Simulate 7-day streak mock_data['habits'][user_id] = { habit_name: {'streak': 7} } assert mock_data['habits'][user_id][habit_name]['streak'] == 7 print(f"✅ Streak calculated: 7 days") class TestFoodLogging: """Test food/nutrition logging functionality""" def test_analyze_simple_food(self, mock_data): """Test basic food analysis""" from habit_bot import analyze_food_text # Test chicken analysis result = analyze_food_text("chicken breast 200g") assert 'calories' in result assert 'carbs' in result assert 'protein' in result assert 'fat' in result assert result['protein'] > 0 print(f"✅ Food analyzed: {result}") def test_analyze_multiple_foods(self, mock_data): """Test multi-food analysis""" from habit_bot import analyze_food_text # Test multiple items result = analyze_food_text("2 eggs and 1 banana") assert result['calories'] > 0 assert result['protein'] > 0 assert 'egg' in result or result['protein'] > 0 # Eggs contribute protein print(f"✅ Multi-food analyzed: {result}") def test_food_log_entry(self, mock_data): """Test food log entry creation""" user_id = "12345" today = datetime.now().strftime('%Y-%m-%d') # Create food log if user_id not in mock_data['food_logs']: mock_data['food_logs'][user_id] = {} if today not in mock_data['food_logs'][user_id]: mock_data['food_logs'][user_id][today] = [] mock_data['food_logs'][user_id][today].append({ 'meal_type': 'lunch', 'food_name': 'grilled chicken', 'time': '12:30', 'calories': 300, 'carbs': 0, 'protein': 50, 'fat': 8, 'timestamp': datetime.now().isoformat() }) assert len(mock_data['food_logs'][user_id][today]) == 1 assert mock_data['food_logs'][user_id][today][0]['calories'] == 300 print("✅ Food log entry created") class TestKetoGuidance: """Test keto diet guidance""" def test_keto_calorie_targets(self, mock_data): """Test keto calorie calculation""" # Keto guidelines protein_per_kg = 1.3 # 1.3g per kg body weight body_weight_kg = 70 # Example weight protein_target = protein_per_kg * body_weight_kg max_net_carbs = 25 # 25g per day assert protein_target == 91 # 1.3 * 70 assert max_net_carbs == 25 print(f"✅ Keto targets: Protein {protein_target}g, Carbs {max_net_carbs}g") def test_calorie_remaining(self, mock_data): """Test remaining calorie calculation""" daily_target = 2000 consumed = 750 remaining = daily_target - consumed assert remaining == 1250 print(f"✅ Calories remaining: {remaining}") class TestDataPersistence: """Test data save/load functionality""" def test_save_and_load_habits(self, mock_data, tmp_path): """Test habit data persistence""" test_file = tmp_path / "test_habits.json" # Save mock_data['habits']['user1'] = { 'workout': {'streak': 10}, 'meditation': {'streak': 5} } with open(test_file, 'w') as f: json.dump(mock_data['habits'], f) # Load with open(test_file, 'r') as f: loaded = json.load(f) assert 'user1' in loaded assert 'workout' in loaded['user1'] assert loaded['user1']['workout']['streak'] == 10 print("✅ Data persistence verified") class TestMotivationalQuotes: """Test motivational quote system""" def test_quotes_available(self, mock_data): """Test that quotes are available""" from habit_bot import MOTIVATIONAL_QUOTES assert len(MOTIVATIONAL_QUOTES) > 0 assert all(isinstance(q, str) for q in MOTIVATIONAL_QUOTES) assert len(q) > 10 for q in MOTIVATIONAL_QUOTES) # Quotes should have content print(f"✅ {len(MOTIVATIONAL_QUOTES)} motivational quotes available") # Pytest configuration if __name__ == '__main__': pytest.main([__file__, '-v'])