docs(ios): add comprehensive testing guide and refine iOS parity directive

Add iOS prefetch testing guide with detailed procedures, log checklists,
and behavior classification. Enhance iOS test app requirements with
security constraints, sign-off checklists, and changelog structure.
Update main directive with testing strategy and method behavior mapping.

Changes:
- Add IOS_PREFETCH_TESTING.md with simulator/device test plans, log
  diagnostics, telemetry expectations, and test run templates
- Add DailyNotificationBackgroundTaskTestHarness.swift as reference
  implementation for BGTaskScheduler testing
- Enhance IOS_TEST_APP_REQUIREMENTS.md with security/privacy constraints,
  review checklists, CI hints, and glossary cross-links
- Update 0003-iOS-Android-Parity-Directive.md with testing strategy
  section, method behavior classification, and validation matrix updates

All documents now include changelog stubs, cross-references, and
completion criteria for Phase 1 implementation and testing.
This commit is contained in:
Matthew Raymer
2025-11-15 02:41:28 +00:00
parent 88aa34b33f
commit 6d25cdd033
4 changed files with 1133 additions and 21 deletions

View File

@@ -0,0 +1,218 @@
//
// 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
/// <key>BGTaskSchedulerPermittedIdentifiers</key>
/// <array>
/// <string>com.timesafari.dailynotification.fetch</string>
/// </array>
/// ```
///
/// **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.
*/