// // MockPlantCollectionRepository.swift // PlantGuideTests // // Mock implementation of PlantCollectionRepositoryProtocol for unit testing. // Provides configurable behavior and call tracking for verification. // import Foundation @testable import PlantGuide // MARK: - MockPlantCollectionRepository /// Mock implementation of PlantCollectionRepositoryProtocol for testing final class MockPlantCollectionRepository: PlantCollectionRepositoryProtocol, @unchecked Sendable { // MARK: - Storage var plants: [UUID: Plant] = [:] // MARK: - Call Tracking var saveCallCount = 0 var fetchByIdCallCount = 0 var fetchAllCallCount = 0 var deleteCallCount = 0 var existsCallCount = 0 var updatePlantCallCount = 0 var searchCallCount = 0 var filterCallCount = 0 var getFavoritesCallCount = 0 var setFavoriteCallCount = 0 var getStatisticsCallCount = 0 // MARK: - Error Configuration var shouldThrowOnSave = false var shouldThrowOnFetch = false var shouldThrowOnDelete = false var shouldThrowOnExists = false var shouldThrowOnUpdate = false var shouldThrowOnSearch = false var shouldThrowOnFilter = false var shouldThrowOnGetFavorites = false var shouldThrowOnSetFavorite = false var shouldThrowOnGetStatistics = false var errorToThrow: Error = NSError( domain: "MockError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Mock repository error"] ) // MARK: - Captured Values var lastSavedPlant: Plant? var lastDeletedPlantID: UUID? var lastUpdatedPlant: Plant? var lastSearchQuery: String? var lastFilter: PlantFilter? var lastSetFavoritePlantID: UUID? var lastSetFavoriteValue: Bool? // MARK: - Statistics Configuration var statisticsToReturn: CollectionStatistics? // MARK: - PlantRepositoryProtocol func save(_ plant: Plant) async throws { saveCallCount += 1 lastSavedPlant = plant if shouldThrowOnSave { throw errorToThrow } plants[plant.id] = plant } func fetch(id: UUID) async throws -> Plant? { fetchByIdCallCount += 1 if shouldThrowOnFetch { throw errorToThrow } return plants[id] } func fetchAll() async throws -> [Plant] { fetchAllCallCount += 1 if shouldThrowOnFetch { throw errorToThrow } return Array(plants.values) } func delete(id: UUID) async throws { deleteCallCount += 1 lastDeletedPlantID = id if shouldThrowOnDelete { throw errorToThrow } plants.removeValue(forKey: id) } // MARK: - PlantCollectionRepositoryProtocol Extensions func exists(id: UUID) async throws -> Bool { existsCallCount += 1 if shouldThrowOnExists { throw errorToThrow } return plants[id] != nil } func updatePlant(_ plant: Plant) async throws { updatePlantCallCount += 1 lastUpdatedPlant = plant if shouldThrowOnUpdate { throw errorToThrow } plants[plant.id] = plant } func searchPlants(query: String) async throws -> [Plant] { searchCallCount += 1 lastSearchQuery = query if shouldThrowOnSearch { throw errorToThrow } let lowercaseQuery = query.lowercased() return plants.values.filter { plant in plant.scientificName.lowercased().contains(lowercaseQuery) || plant.commonNames.contains { $0.lowercased().contains(lowercaseQuery) } || (plant.notes?.lowercased().contains(lowercaseQuery) ?? false) } } func filterPlants(by filter: PlantFilter) async throws -> [Plant] { filterCallCount += 1 lastFilter = filter if shouldThrowOnFilter { throw errorToThrow } var result = Array(plants.values) // Apply search query if let query = filter.searchQuery, !query.isEmpty { let lowercaseQuery = query.lowercased() result = result.filter { plant in plant.scientificName.lowercased().contains(lowercaseQuery) || plant.commonNames.contains { $0.lowercased().contains(lowercaseQuery) } } } // Filter by favorites if let isFavorite = filter.isFavorite { result = result.filter { $0.isFavorite == isFavorite } } // Filter by families if let families = filter.families, !families.isEmpty { result = result.filter { families.contains($0.family) } } // Filter by identification source if let source = filter.identificationSource { result = result.filter { $0.identificationSource == source } } return result } func getFavorites() async throws -> [Plant] { getFavoritesCallCount += 1 if shouldThrowOnGetFavorites { throw errorToThrow } return plants.values.filter { $0.isFavorite } .sorted { $0.dateIdentified > $1.dateIdentified } } func setFavorite(plantID: UUID, isFavorite: Bool) async throws { setFavoriteCallCount += 1 lastSetFavoritePlantID = plantID lastSetFavoriteValue = isFavorite if shouldThrowOnSetFavorite { throw errorToThrow } if var plant = plants[plantID] { plant.isFavorite = isFavorite plants[plantID] = plant } } func getCollectionStatistics() async throws -> CollectionStatistics { getStatisticsCallCount += 1 if shouldThrowOnGetStatistics { throw errorToThrow } if let statistics = statisticsToReturn { return statistics } // Calculate statistics from current plants var familyDistribution: [String: Int] = [:] var sourceBreakdown: [IdentificationSource: Int] = [:] for plant in plants.values { familyDistribution[plant.family, default: 0] += 1 sourceBreakdown[plant.identificationSource, default: 0] += 1 } return CollectionStatistics( totalPlants: plants.count, favoriteCount: plants.values.filter { $0.isFavorite }.count, familyDistribution: familyDistribution, identificationSourceBreakdown: sourceBreakdown, plantsAddedThisMonth: 0, upcomingTasksCount: 0, overdueTasksCount: 0 ) } // MARK: - Helper Methods /// Resets all state for clean test setup func reset() { plants = [:] saveCallCount = 0 fetchByIdCallCount = 0 fetchAllCallCount = 0 deleteCallCount = 0 existsCallCount = 0 updatePlantCallCount = 0 searchCallCount = 0 filterCallCount = 0 getFavoritesCallCount = 0 setFavoriteCallCount = 0 getStatisticsCallCount = 0 shouldThrowOnSave = false shouldThrowOnFetch = false shouldThrowOnDelete = false shouldThrowOnExists = false shouldThrowOnUpdate = false shouldThrowOnSearch = false shouldThrowOnFilter = false shouldThrowOnGetFavorites = false shouldThrowOnSetFavorite = false shouldThrowOnGetStatistics = false lastSavedPlant = nil lastDeletedPlantID = nil lastUpdatedPlant = nil lastSearchQuery = nil lastFilter = nil lastSetFavoritePlantID = nil lastSetFavoriteValue = nil statisticsToReturn = nil } /// Adds a plant directly to storage (bypasses save method) func addPlant(_ plant: Plant) { plants[plant.id] = plant } /// Adds multiple plants directly to storage func addPlants(_ plantsToAdd: [Plant]) { for plant in plantsToAdd { plants[plant.id] = plant } } }