// // MockNotificationService.swift // PlantGuideTests // // Mock implementation of NotificationServiceProtocol for unit testing. // Provides configurable behavior and call tracking for verification. // import Foundation import UserNotifications @testable import PlantGuide // MARK: - MockNotificationService /// Mock implementation of NotificationServiceProtocol for testing final actor MockNotificationService: NotificationServiceProtocol { // MARK: - Storage private var scheduledReminders: [UUID: (task: CareTask, plantName: String, plantID: UUID)] = [:] private var pendingNotifications: [UNNotificationRequest] = [] // MARK: - Call Tracking private(set) var requestAuthorizationCallCount = 0 private(set) var scheduleReminderCallCount = 0 private(set) var cancelReminderCallCount = 0 private(set) var cancelAllRemindersCallCount = 0 private(set) var cancelRemindersForTypeCallCount = 0 private(set) var updateBadgeCountCallCount = 0 private(set) var getPendingNotificationsCallCount = 0 private(set) var removeAllDeliveredNotificationsCallCount = 0 // MARK: - Error Configuration var shouldThrowOnRequestAuthorization = false var shouldThrowOnScheduleReminder = false var errorToThrow: Error = NotificationError.schedulingFailed( NSError(domain: "MockError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Mock notification error"]) ) // MARK: - Return Value Configuration var authorizationGranted = true // MARK: - Captured Values private(set) var lastScheduledTask: CareTask? private(set) var lastScheduledPlantName: String? private(set) var lastScheduledPlantID: UUID? private(set) var lastCancelledTaskID: UUID? private(set) var lastCancelledAllPlantID: UUID? private(set) var lastCancelledTaskType: CareTaskType? private(set) var lastCancelledTaskTypePlantID: UUID? private(set) var lastBadgeCount: Int? // MARK: - NotificationServiceProtocol func requestAuthorization() async throws -> Bool { requestAuthorizationCallCount += 1 if shouldThrowOnRequestAuthorization { throw errorToThrow } if !authorizationGranted { throw NotificationError.permissionDenied } return authorizationGranted } func scheduleReminder(for task: CareTask, plantName: String, plantID: UUID) async throws { scheduleReminderCallCount += 1 lastScheduledTask = task lastScheduledPlantName = plantName lastScheduledPlantID = plantID if shouldThrowOnScheduleReminder { throw errorToThrow } // Validate that the scheduled date is in the future guard task.scheduledDate > Date() else { throw NotificationError.invalidTriggerDate } scheduledReminders[task.id] = (task, plantName, plantID) } func cancelReminder(for taskID: UUID) async { cancelReminderCallCount += 1 lastCancelledTaskID = taskID scheduledReminders.removeValue(forKey: taskID) } func cancelAllReminders(for plantID: UUID) async { cancelAllRemindersCallCount += 1 lastCancelledAllPlantID = plantID // Remove all reminders for this plant let keysToRemove = scheduledReminders.filter { $0.value.plantID == plantID }.map { $0.key } for key in keysToRemove { scheduledReminders.removeValue(forKey: key) } } func cancelReminders(for taskType: CareTaskType, plantID: UUID) async { cancelRemindersForTypeCallCount += 1 lastCancelledTaskType = taskType lastCancelledTaskTypePlantID = plantID // Remove all reminders matching this task type and plant let keysToRemove = scheduledReminders.filter { $0.value.plantID == plantID && $0.value.task.type == taskType }.map { $0.key } for key in keysToRemove { scheduledReminders.removeValue(forKey: key) } } func updateBadgeCount(_ count: Int) async { updateBadgeCountCallCount += 1 lastBadgeCount = count } func getPendingNotifications() async -> [UNNotificationRequest] { getPendingNotificationsCallCount += 1 return pendingNotifications } func removeAllDeliveredNotifications() async { removeAllDeliveredNotificationsCallCount += 1 } // MARK: - Helper Methods /// Resets all state for clean test setup func reset() { scheduledReminders = [:] pendingNotifications = [] requestAuthorizationCallCount = 0 scheduleReminderCallCount = 0 cancelReminderCallCount = 0 cancelAllRemindersCallCount = 0 cancelRemindersForTypeCallCount = 0 updateBadgeCountCallCount = 0 getPendingNotificationsCallCount = 0 removeAllDeliveredNotificationsCallCount = 0 shouldThrowOnRequestAuthorization = false shouldThrowOnScheduleReminder = false authorizationGranted = true lastScheduledTask = nil lastScheduledPlantName = nil lastScheduledPlantID = nil lastCancelledTaskID = nil lastCancelledAllPlantID = nil lastCancelledTaskType = nil lastCancelledTaskTypePlantID = nil lastBadgeCount = nil } /// Gets the count of scheduled reminders var scheduledReminderCount: Int { scheduledReminders.count } /// Gets scheduled reminders for a specific plant func reminders(for plantID: UUID) -> [CareTask] { scheduledReminders.values .filter { $0.plantID == plantID } .map { $0.task } } /// Checks if a reminder is scheduled for a task func hasReminder(for taskID: UUID) -> Bool { scheduledReminders[taskID] != nil } /// Gets all scheduled task IDs var scheduledTaskIDs: [UUID] { Array(scheduledReminders.keys) } /// Adds a pending notification for testing getPendingNotifications func addPendingNotification(_ request: UNNotificationRequest) { pendingNotifications.append(request) } }