/ 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 }