import unittest from unittest.mock import patch, MagicMock from fastapi.testclient import TestClient from main import app class TestMealMathLogic(unittest.TestCase): def setUp(self): self.client = TestClient(app) # Mock user authentication for the calculation endpoint self.headers = {"Authorization": "Bearer mock-token"} @patch("main.get_user_from_token") @patch("main.get_foods_by_ids") def test_math_scaling_and_aggregation(self, mock_get_foods, mock_auth): # 1. Setup mocks mock_auth.return_value = {"id": 1, "username": "testuser"} # Mock database return for two foods mock_get_foods.return_value = [ { "id": 1, "name": "Food A", "calories": 100.0, "protein_g": 10.0, "fat_g": 5.0, "carbs_g": 2.0, "fiber_g": 1.0, "sugar_g": 0.5, "cholesterol_mg": 0.0, "vitamin_a_iu": 0.0, "vitamin_c_mg": 0.0, "calcium_mg": 0.0, "iron_mg": 0.0, "potassium_mg": 0.0, "sodium_mg": 0.0 }, { "id": 2, "name": "Food B", "calories": 200.0, "protein_g": 20.0, "fat_g": 10.0, "carbs_g": 4.0, "fiber_g": 2.0, "sugar_g": 1.0, "cholesterol_mg": 5.0, "vitamin_a_iu": 100.0, "vitamin_c_mg": 10.0, "calcium_mg": 50.0, "iron_mg": 2.0, "potassium_mg": 300.0, "sodium_mg": 100.0 } ] # 2. Test Case: 50g of Food A and 150g of Food B # Expected Food A (50g): Cal=50, Pro=5 # Expected Food B (150g): Cal=300, Pro=30 # Totals: Cal=350, Pro=35, Weight=200 payload = { "items": [ {"food_id": 1, "amount_g": 50}, {"food_id": 2, "amount_g": 150} ] } response = self.client.post("/api/meal/calculate", json=payload, headers=self.headers) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data["total_weight_g"], 200.0) self.assertEqual(data["macros"]["calories"], 350.0) self.assertEqual(data["macros"]["protein_g"], 35.0) self.assertEqual(data["macros"]["fat_g"], 17.5) # (5 * 0.5) + (10 * 1.5) = 2.5 + 15 = 17.5 self.assertEqual(data["macros"]["carbs_g"], 7.0) # (2 * 0.5) + (4 * 1.5) = 1.0 + 6.0 = 7.0 @patch("main.get_user_from_token") @patch("main.get_foods_by_ids") def test_precision_rounding(self, mock_get_foods, mock_auth): mock_auth.return_value = {"id": 1, "username": "testuser"} # Mock food with irregular values to trigger rounding mock_get_foods.return_value = [ { "id": 1, "name": "Irregular Food", "calories": 123.456, "protein_g": 10.123, "fat_g": 5.555, "carbs_g": 0.0, "fiber_g": 0.0, "sugar_g": 0.0, "cholesterol_mg": 0.0, "vitamin_a_iu": 0.0, "vitamin_c_mg": 0.0, "calcium_mg": 0.0, "iron_mg": 0.0, "potassium_mg": 0.0, "sodium_mg": 0.0 } ] # 123g of Food A -> Ratio 1.23 # Calories: 123.456 * 1.23 = 151.85088 -> Rounded to 151.85 # Protein: 10.123 * 1.23 = 12.45129 -> Rounded to 12.45 # Fat: 5.555 * 1.23 = 6.83265 -> Rounded to 6.83 payload = {"items": [{"food_id": 1, "amount_g": 123}]} response = self.client.post("/api/meal/calculate", json=payload, headers=self.headers) data = response.json() self.assertEqual(data["macros"]["calories"], 151.85) self.assertEqual(data["macros"]["protein_g"], 12.45) self.assertEqual(data["macros"]["fat_g"], 6.83) @patch("main.get_user_from_token") @patch("main.get_foods_by_ids") def test_null_value_handling(self, mock_get_foods, mock_auth): mock_auth.return_value = {"id": 1, "username": "testuser"} # Mock food with NULLs mock_get_foods.return_value = [ { "id": 1, "name": "Null Food", "calories": None, "protein_g": 10.0, "fat_g": None, "carbs_g": 2.0, "fiber_g": None, "sugar_g": None, "cholesterol_mg": None, "vitamin_a_iu": None, "vitamin_c_mg": None, "calcium_mg": None, "iron_mg": None, "potassium_mg": None, "sodium_mg": None } ] payload = {"items": [{"food_id": 1, "amount_g": 100}]} response = self.client.post("/api/meal/calculate", json=payload, headers=self.headers) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data["macros"]["calories"], 0.0) self.assertEqual(data["macros"]["fat_g"], 0.0) self.assertEqual(data["extended"]["fiber_g"], 0.0) if __name__ == "__main__": unittest.main()