//
// DailyNotificationBackgroundTaskTestHarness.swift
// DailyNotificationPlugin
//
// Test harness for BGTaskScheduler prefetch functionality
// Reference implementation demonstrating task registration, scheduling, and handling
//
// See: doc/test-app-ios/IOS_PREFETCH_TESTING.md for testing procedures
//
import Foundation
import BackgroundTasks
import UIKit
/// Minimal BGTaskScheduler test harness for DailyNotificationPlugin prefetch testing
///
/// This is a reference implementation demonstrating:
/// - Task registration
/// - Task scheduling
/// - Task handler implementation
/// - Expiration handling
/// - Completion reporting
///
/// **Usage:**
/// - Reference this when implementing actual prefetch logic in `DailyNotificationBackgroundTaskManager.swift`
/// - Use in test app for debugging BGTaskScheduler behavior
/// - See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for comprehensive testing guide
///
/// **Info.plist Requirements:**
/// ```xml
/// BGTaskSchedulerPermittedIdentifiers
///
/// com.timesafari.dailynotification.fetch
///
/// ```
///
/// **Background Modes (Xcode Capabilities):**
/// - Background fetch
/// - Background processing (if using BGProcessingTask)
class DailyNotificationBackgroundTaskTestHarness {
// MARK: - Constants
static let prefetchTaskIdentifier = "com.timesafari.dailynotification.fetch"
// MARK: - Registration
/// Register BGTaskScheduler task handler
///
/// Call this in AppDelegate.application(_:didFinishLaunchingWithOptions:)
/// before app finishes launching.
static func registerBackgroundTasks() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: prefetchTaskIdentifier,
using: nil
) { task in
// This closure is called when the task is launched by the system
handlePrefetchTask(task: task as! BGAppRefreshTask)
}
print("[DNP-FETCH] Registered BGTaskScheduler with id=\(prefetchTaskIdentifier)")
}
// MARK: - Scheduling
/// Schedule a BGAppRefreshTask for prefetch
///
/// - Parameter earliestOffsetSeconds: Seconds from now when task can begin
/// - Returns: true if scheduling succeeded, false otherwise
@discardableResult
static func schedulePrefetchTask(earliestOffsetSeconds: TimeInterval) -> Bool {
let request = BGAppRefreshTaskRequest(identifier: prefetchTaskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: earliestOffsetSeconds)
do {
try BGTaskScheduler.shared.submit(request)
print("[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=\(String(describing: request.earliestBeginDate)))")
return true
} catch {
print("[DNP-FETCH] Failed to schedule BGAppRefreshTask: \(error)")
return false
}
}
// MARK: - Handler
/// Handle BGAppRefreshTask execution
///
/// This is called by the system when the background task is launched.
/// Replace PrefetchOperation with your actual prefetch logic.
private static func handlePrefetchTask(task: BGAppRefreshTask) {
print("[DNP-FETCH] BGTask handler invoked (task.identifier=\(task.identifier))")
// Schedule the next one early, so that there's always a pending task
// In real implementation, calculate next schedule based on notification time
schedulePrefetchTask(earliestOffsetSeconds: 60 * 30) // 30 minutes later, for example
// Define the work
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let operation = PrefetchOperation()
// Set expiration handler
// Called if iOS decides to end the task early (typically ~30 seconds)
task.expirationHandler = {
print("[DNP-FETCH] Task expired")
operation.cancel()
}
// Set completion handler
operation.completionBlock = {
let success = !operation.isCancelled
print("[DNP-FETCH] Task completionBlock (success=\(success))")
task.setTaskCompleted(success: success)
}
queue.addOperation(operation)
}
}
// MARK: - Prefetch Operation
/// Simple Operation example for testing
///
/// Replace this with your actual prefetch logic:
/// - HTTP fetch from TimeSafari API
/// - JWT signing
/// - ETag validation
/// - Content caching
/// - Error handling
class PrefetchOperation: Operation {
override func main() {
if isCancelled { return }
print("[DNP-FETCH] PrefetchOperation: starting fake fetch...")
// Simulate some work
// In real implementation, this would be:
// - Make HTTP request
// - Parse response
// - Cache content
// - Update database
Thread.sleep(forTimeInterval: 2)
if isCancelled { return }
print("[DNP-FETCH] PrefetchOperation: finished fake fetch.")
}
}
// MARK: - AppDelegate Integration Example
/*
Example integration in AppDelegate.swift:
import UIKit
import BackgroundTasks
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Register background tasks BEFORE app finishes launching
DailyNotificationBackgroundTaskTestHarness.registerBackgroundTasks()
// Schedule initial task (for testing)
DailyNotificationBackgroundTaskTestHarness.schedulePrefetchTask(earliestOffsetSeconds: 5 * 60) // 5 minutes
return true
}
}
*/
// MARK: - Testing in Simulator
/*
To test in simulator:
1. Run app in Xcode
2. Background the app (Home button / Cmd+Shift+H)
3. In Xcode menu:
- Debug → Simulate Background Fetch, or
- Debug → Simulate Background Refresh
4. Check console logs for [DNP-FETCH] messages
Expected logs:
- [DNP-FETCH] Registered BGTaskScheduler with id=...
- [DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=...)
- [DNP-FETCH] BGTask handler invoked (task.identifier=...)
- [DNP-FETCH] PrefetchOperation: starting fake fetch...
- [DNP-FETCH] PrefetchOperation: finished fake fetch.
- [DNP-FETCH] Task completionBlock (success=true)
*/
// MARK: - Testing on Real Device
/*
To test on real device:
1. Install app on iPhone
2. Enable Background App Refresh in Settings → [Your App]
3. Schedule a notification with prefetch
4. Lock device and leave idle (plugged in for best results)
5. Monitor logs via:
- Xcode → Devices & Simulators → device → open console
- Or os_log aggregator / remote logging
Note: Real device timing is heuristic, not deterministic.
iOS will run the task when it determines it's appropriate,
not necessarily at the exact earliestBeginDate.
*/