Files
openclaw-workspace/tests/test_habit_bot.py
Joungmin ceb52b2146 Add: Unit tests for habit_bot and stock_tracker
- tests/test_habit_bot.py: Habit tracking, food logging, keto guidance
- tests/test_stock_tracker.py: Portfolio management, P&L calculation
- pytest.ini: Pytest configuration
- Updated Jenkinsfile: Emphasized testing stages before build

Pipeline stages:
1. Code Quality Gates (lint + security)
2. Unit Tests (pytest with coverage)
3. Integration Tests (Oracle, Telegram, Gitea)
4. Build (only after tests pass)
5. Deploy to Staging
2026-02-19 03:32:43 +09:00

256 lines
8.4 KiB
Python

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