feat(ios): add error handling and integration tests
Implement comprehensive error handling and integration test suite: Error Handling (Section 8): - Add iOS-specific error codes to DailyNotificationErrorCodes: - NOTIFICATION_PERMISSION_DENIED - PENDING_NOTIFICATION_LIMIT_EXCEEDED - BG_TASK_NOT_REGISTERED - BG_TASK_EXECUTION_FAILED - BACKGROUND_REFRESH_DISABLED - Add helper methods for iOS-specific error responses - Enhance error handling in ReactivationManager: - Database errors handled gracefully (non-fatal) - Notification center errors handled gracefully (non-fatal) - Scheduling errors handled gracefully (non-fatal) - All errors logged, app continues normally - Partial results returned when operations fail - Update plugin methods to use iOS-specific error codes: - getNotificationPermissionStatus uses NOTIFICATION_PERMISSION_DENIED Integration Tests (Section 9.2): - Add DailyNotificationRecoveryIntegrationTests: - Full recovery flow tests (cold start, termination) - Error handling tests (database, notification center, scheduling) - App stability tests (no crashes, concurrent operations) - Partial recovery tests - Timeout handling tests - Test coverage: - 10 integration tests covering recovery scenarios - Error handling verification - App stability verification - Concurrent operation safety Completes sections 8.1, 8.2, and 9.2 of iOS implementation checklist.
This commit is contained in:
@@ -189,14 +189,32 @@ class DailyNotificationReactivationManager {
|
||||
*/
|
||||
internal func detectScenario() async throws -> RecoveryScenario {
|
||||
// Step 1: Check if database has notifications
|
||||
let allNotifications = storage.getAllNotifications()
|
||||
// Handle storage errors gracefully (non-fatal)
|
||||
let allNotifications: [NotificationContent]
|
||||
do {
|
||||
allNotifications = storage.getAllNotifications()
|
||||
} catch {
|
||||
// Non-fatal: Log error and assume empty storage
|
||||
NSLog("\(Self.TAG): Error getting notifications from storage (non-fatal): \(error.localizedDescription)")
|
||||
return .none
|
||||
}
|
||||
|
||||
if allNotifications.isEmpty {
|
||||
return .none // First launch
|
||||
}
|
||||
|
||||
// Step 2: Get pending notifications from UNUserNotificationCenter
|
||||
let pendingRequests = try await notificationCenter.pendingNotificationRequests()
|
||||
// Handle notification center errors gracefully (non-fatal)
|
||||
let pendingRequests: [UNNotificationRequest]
|
||||
do {
|
||||
pendingRequests = try await notificationCenter.pendingNotificationRequests()
|
||||
} catch {
|
||||
// Non-fatal: Log error and assume no pending notifications
|
||||
NSLog("\(Self.TAG): Error getting pending notifications (non-fatal): \(error.localizedDescription)")
|
||||
// Return cold start as safe default - will trigger recovery
|
||||
return .coldStart
|
||||
}
|
||||
|
||||
let pendingIds = Set(pendingRequests.map { $0.identifier })
|
||||
|
||||
// Step 3: Get notification IDs from storage
|
||||
@@ -317,7 +335,15 @@ class DailyNotificationReactivationManager {
|
||||
*/
|
||||
internal func detectMissedNotifications(currentTime: Date) async throws -> [NotificationContent] {
|
||||
// Get all notifications from storage
|
||||
let allNotifications = storage.getAllNotifications()
|
||||
// Handle database/storage errors gracefully (non-fatal)
|
||||
let allNotifications: [NotificationContent]
|
||||
do {
|
||||
allNotifications = storage.getAllNotifications()
|
||||
} catch {
|
||||
// Non-fatal: Log error and return empty array
|
||||
NSLog("\(Self.TAG): Error getting notifications from storage (non-fatal): \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
|
||||
// Convert currentTime to milliseconds (Int64) for comparison
|
||||
let currentTimeMs = Int64(currentTime.timeIntervalSince1970 * 1000)
|
||||
@@ -368,12 +394,46 @@ class DailyNotificationReactivationManager {
|
||||
*/
|
||||
internal func verifyFutureNotifications() async throws -> VerificationResult {
|
||||
// Get pending notifications from UNUserNotificationCenter
|
||||
let pendingRequests = try await notificationCenter.pendingNotificationRequests()
|
||||
// Handle notification center errors gracefully (non-fatal)
|
||||
let pendingRequests: [UNNotificationRequest]
|
||||
do {
|
||||
pendingRequests = try await notificationCenter.pendingNotificationRequests()
|
||||
} catch {
|
||||
// Non-fatal: Log error and assume no pending notifications
|
||||
NSLog("\(Self.TAG): Error getting pending notifications (non-fatal): \(error.localizedDescription)")
|
||||
// Return verification result indicating all are missing
|
||||
let currentTimeMs = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
let allNotifications = storage.getAllNotifications()
|
||||
let futureNotifications = allNotifications.filter { $0.scheduledTime >= currentTimeMs }
|
||||
let futureIds = Set(futureNotifications.map { $0.id })
|
||||
|
||||
return VerificationResult(
|
||||
totalSchedules: futureNotifications.count,
|
||||
notificationsFound: 0,
|
||||
notificationsMissing: futureIds.count,
|
||||
missingIds: Array(futureIds)
|
||||
)
|
||||
}
|
||||
|
||||
let pendingIds = Set(pendingRequests.map { $0.identifier })
|
||||
|
||||
// Get all notifications from storage that are scheduled for future
|
||||
// Handle storage errors gracefully (non-fatal)
|
||||
let currentTimeMs = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
let allNotifications = storage.getAllNotifications()
|
||||
let allNotifications: [NotificationContent]
|
||||
do {
|
||||
allNotifications = storage.getAllNotifications()
|
||||
} catch {
|
||||
// Non-fatal: Log error and return empty verification result
|
||||
NSLog("\(Self.TAG): Error getting notifications from storage (non-fatal): \(error.localizedDescription)")
|
||||
return VerificationResult(
|
||||
totalSchedules: 0,
|
||||
notificationsFound: pendingIds.count,
|
||||
notificationsMissing: 0,
|
||||
missingIds: []
|
||||
)
|
||||
}
|
||||
|
||||
let futureNotifications = allNotifications.filter { $0.scheduledTime >= currentTimeMs }
|
||||
let futureIds = Set(futureNotifications.map { $0.id })
|
||||
|
||||
@@ -397,14 +457,27 @@ class DailyNotificationReactivationManager {
|
||||
*/
|
||||
private func rescheduleMissingNotification(id: String) async throws {
|
||||
// Get notification content from storage
|
||||
guard let notification = storage.getNotificationContent(id: id) else {
|
||||
// Handle storage errors gracefully (non-fatal)
|
||||
let notification: NotificationContent?
|
||||
do {
|
||||
notification = storage.getNotificationContent(id: id)
|
||||
} catch {
|
||||
// Non-fatal: Log error and throw to be caught by caller
|
||||
NSLog("\(Self.TAG): Error getting notification from storage (non-fatal): \(error.localizedDescription)")
|
||||
throw ReactivationError.notificationNotFound(id: id)
|
||||
}
|
||||
|
||||
guard let notification = notification else {
|
||||
throw ReactivationError.notificationNotFound(id: id)
|
||||
}
|
||||
|
||||
// Reschedule using scheduler
|
||||
// Handle scheduling errors gracefully (non-fatal)
|
||||
let success = await scheduler.scheduleNotification(notification)
|
||||
|
||||
if !success {
|
||||
// Non-fatal: Log error and throw to be caught by caller
|
||||
NSLog("\(Self.TAG): Failed to reschedule notification \(id) (non-fatal)")
|
||||
throw ReactivationError.rescheduleFailed(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user