/ BalanceKitTests / FoodDataManagerTests.swift
FoodDataManagerTests.swift
  1  //
  2  //  FoodDataManagerTests.swift
  3  //  BalanceKitTests
  4  //
  5  //  Created by Alexander Kunau on 13.12.25.
  6  //
  7  
  8  import XCTest
  9  @testable import BalanceKit
 10  
 11  final class FoodDataManagerTests: XCTestCase {
 12      var sut: FoodDataManager!
 13      var testDefaults: UserDefaults!
 14      
 15      override func setUp() {
 16          super.setUp()
 17          // Use a separate UserDefaults suite for testing
 18          testDefaults = UserDefaults(suiteName: "TestDefaults")!
 19          testDefaults.removePersistentDomain(forName: "TestDefaults")
 20          
 21          sut = FoodDataManager()
 22          sut.foodItems = []
 23          sut.mealPresets = []
 24      }
 25      
 26      override func tearDown() {
 27          sut = nil
 28          testDefaults.removePersistentDomain(forName: "TestDefaults")
 29          testDefaults = nil
 30          super.tearDown()
 31      }
 32      
 33      func testAddFoodItem() {
 34          sut.addFoodItem(name: "Test", calories: 100, protein: 10, carbs: 20, fat: 5, date: Date(), mealType: .lunch)
 35          
 36          XCTAssertEqual(sut.foodItems.count, 1)
 37          XCTAssertEqual(sut.foodItems.first?.name, "Test")
 38          XCTAssertEqual(sut.foodItems.first?.calories, 100)
 39      }
 40      
 41      func testTotalCaloriesForDay() {
 42          let today = Date()
 43          sut.addFoodItem(name: "Breakfast", calories: 300, protein: 10, carbs: 40, fat: 10, date: today, mealType: .breakfast)
 44          sut.addFoodItem(name: "Lunch", calories: 500, protein: 20, carbs: 60, fat: 15, date: today, mealType: .lunch)
 45          
 46          XCTAssertEqual(sut.totalCaloriesForDay(today), 800)
 47      }
 48      
 49      func testTotalProteinForDay() {
 50          let today = Date()
 51          sut.addFoodItem(name: "Food1", calories: 100, protein: 15.5, carbs: 10, fat: 5, date: today, mealType: .breakfast)
 52          sut.addFoodItem(name: "Food2", calories: 200, protein: 24.5, carbs: 20, fat: 10, date: today, mealType: .lunch)
 53          
 54          XCTAssertEqual(sut.totalProteinForDay(today), 40.0, accuracy: 0.1)
 55      }
 56      
 57      func testUpdateFoodItem() {
 58          let today = Date()
 59          sut.addFoodItem(name: "Original", calories: 100, protein: 10, carbs: 20, fat: 5, date: today, mealType: .lunch)
 60          
 61          guard let id = sut.foodItems.first?.id else {
 62              XCTFail("No food item found")
 63              return
 64          }
 65          
 66          sut.updateFoodItem(id: id, name: "Updated", calories: 200, protein: 15, carbs: 25, fat: 8, date: today, mealType: .dinner)
 67          
 68          XCTAssertEqual(sut.foodItems.first?.name, "Updated")
 69          XCTAssertEqual(sut.foodItems.first?.calories, 200)
 70          XCTAssertEqual(sut.foodItems.first?.mealType, .dinner)
 71      }
 72      
 73      func testCreatePreset() {
 74          let foods = [
 75              PresetFoodItem(name: "Food1", calories: 100, protein: 10, carbs: 20, fat: 5),
 76              PresetFoodItem(name: "Food2", calories: 200, protein: 15, carbs: 30, fat: 8)
 77          ]
 78          
 79          sut.createPreset(name: "TestPreset", mealType: .breakfast, foods: foods)
 80          
 81          XCTAssertEqual(sut.mealPresets.count, 1)
 82          XCTAssertEqual(sut.mealPresets.first?.name, "TestPreset")
 83          XCTAssertEqual(sut.mealPresets.first?.totalCalories, 300)
 84      }
 85      
 86      func testDeleteFoodItem() {
 87          sut.addFoodItem(name: "Test1", calories: 100, protein: 10, carbs: 20, fat: 5, date: Date(), mealType: .lunch)
 88          sut.addFoodItem(name: "Test2", calories: 200, protein: 15, carbs: 25, fat: 8, date: Date(), mealType: .dinner)
 89          
 90          XCTAssertEqual(sut.foodItems.count, 2)
 91          
 92          sut.deleteFoodItem(at: IndexSet(integer: 0))
 93          
 94          XCTAssertEqual(sut.foodItems.count, 1)
 95          XCTAssertEqual(sut.foodItems.first?.name, "Test2")
 96      }
 97      
 98      // MARK: - UserDefaults Corruption Tests
 99      
100      func testLoadCorruptedData() {
101          // Simulate corrupted UserDefaults data
102          let corruptedData = "invalid json data".data(using: .utf8)!
103          UserDefaults.standard.set(corruptedData, forKey: "SavedFoodItems")
104          
105          let newManager = FoodDataManager()
106          
107          // Should handle gracefully and start with empty list
108          XCTAssertEqual(newManager.foodItems.count, 0)
109          XCTAssertNotNil(newManager.lastError)
110      }
111      
112      func testLoadPartiallyCorruptedData() {
113          // Create a mix of valid and invalid food items
114          let validItem = FoodItem(name: "Valid", calories: 100, protein: 10, carbs: 20, fat: 5, date: Date(), mealType: .lunch)
115          let itemWithNegativeCalories = FoodItem(name: "Invalid", calories: -100, protein: 10, carbs: 20, fat: 5, date: Date(), mealType: .lunch)
116          
117          // The manager should filter out invalid items
118          sut.foodItems = [validItem, itemWithNegativeCalories]
119          
120          XCTAssertEqual(sut.foodItems.count, 2) // Both are added initially
121          // After validation (which happens in load), invalid ones should be filtered
122      }
123      
124      func testMigrationFromLegacyFormat() {
125          // Simulate old format data (array without versioning)
126          let legacyItems = [
127              FoodItem(name: "Legacy1", calories: 100, protein: 10, carbs: 20, fat: 5, date: Date(), mealType: .breakfast),
128              FoodItem(name: "Legacy2", calories: 200, protein: 20, carbs: 30, fat: 10, date: Date(), mealType: .lunch)
129          ]
130          
131          let encoder = JSONEncoder()
132          let encoded = try! encoder.encode(legacyItems)
133          UserDefaults.standard.set(encoded, forKey: "SavedFoodItems")
134          
135          let newManager = FoodDataManager()
136          
137          // Should successfully migrate legacy data
138          XCTAssertEqual(newManager.foodItems.count, 2)
139          XCTAssertEqual(newManager.foodItems[0].name, "Legacy1")
140      }
141  }