Apply comprehensive enhancements to iOS prefetch plugin testing and validation system per directive requirements. Technical Correctness Improvements: - Enhanced BGTask scheduling with validation (60s minimum lead time) - Implemented one active task rule (cancel existing before scheduling) - Added graceful simulator error handling (Code=1 expected) - Follow Apple best practice: schedule next task immediately at execution - Ensure task completion even on expiration with guard flag - Improved error handling and structured logging Testing Coverage Expansion: - Added edge case scenarios table (7 scenarios: Background Refresh Off, Low Power Mode, Force-Quit, Timezone Change, DST, Multi-Day, Reboot) - Expanded failure injection tests (8 new negative-path scenarios) - Documented automated testing strategies (unit and integration tests) Validation Enhancements: - Added structured JSON logging schema for events - Provided log validation script (validate-ios-logs.sh) - Enhanced test run template with telemetry and state verification - Documented state integrity checks (content hash, schedule hash) - Added UI indicators and persistent test artifacts requirements Documentation Updates: - Enhanced IOS_PREFETCH_TESTING.md with comprehensive test strategies - Added Technical Correctness Requirements to IOS_TEST_APP_REQUIREMENTS.md - Expanded error handling test cases from 2 to 7 scenarios - Created ENHANCEMENTS_APPLIED.md summary document Files modified: - ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift: Enhanced with technical correctness improvements - doc/test-app-ios/IOS_PREFETCH_TESTING.md: Expanded testing coverage - doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md: Added technical requirements - doc/test-app-ios/ENHANCEMENTS_APPLIED.md: New summary document
803 lines
29 KiB
Markdown
803 lines
29 KiB
Markdown
# iOS Test App Requirements
|
||
|
||
**Purpose:** What the iOS test app must provide so that the testing guide can be executed with parity vs Android
|
||
|
||
**Version:** 1.0.1
|
||
**Scope:** Phase 1 Prefetch MVP
|
||
**Next Target:** Phase 2 (Rolling Window + TTL Telemetry)
|
||
**Maintainer:** Matthew Raymer
|
||
**Status:** 📋 **REQUIRED FOR PHASE 1**
|
||
**Date:** 2025-11-15
|
||
**Directive Reference:** `doc/directives/0003-iOS-Android-Parity-Directive.md`
|
||
|
||
**Note:** This app exists to support the prefetch testing scenarios in `doc/test-app-ios/IOS_PREFETCH_TESTING.md`.
|
||
|
||
**Android parity:** Behavior is aligned with `test-apps/android-test-app` where platform constraints allow. Timing and BGTask heuristics **will differ** from Android's exact alarms:
|
||
- **Android:** Exact alarms via AlarmManager / WorkManager
|
||
- **iOS:** Heuristic BGTaskScheduler (see glossary); no hard guarantee of 5-min prefetch
|
||
|
||
**Glossary:** See `doc/test-app-ios/IOS_PREFETCH_GLOSSARY.md` for complete terminology definitions.
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
This document defines the requirements for the iOS test app (`test-apps/ios-test-app/`) that must be created as part of Phase 1 implementation. The iOS test app must provide UI parity with the Android test app (`test-apps/android-test-app/`) while respecting iOS-specific constraints and capabilities.
|
||
|
||
## Non-Goals (Phase 1)
|
||
|
||
**Out of scope for Phase 1:**
|
||
|
||
- ❌ No full UX polish (color, branding)
|
||
- ❌ No localization / accessibility guarantees (text only for internal QA)
|
||
- ❌ No production signing / App Store deployment
|
||
- ❌ No advanced UI features beyond basic functionality testing
|
||
|
||
## Security & Privacy Constraints
|
||
|
||
**Critical requirements for test app implementation:**
|
||
|
||
- Test app MUST use **non-production** endpoints and credentials
|
||
- JWT / API keys used here are **test-only** and may be rotated or revoked at any time
|
||
- Logs MUST NOT include real user PII (names, emails, phone numbers)
|
||
- Any screenshots or shared logs should be scrubbed of secrets before external sharing
|
||
- Test app should clearly indicate it's a development/testing build (not production)
|
||
|
||
## If You Only Have 30 Minutes
|
||
|
||
Quick setup checklist:
|
||
|
||
1. Copy HTML/JS from Android test app (`test-apps/android-test-app/app/src/main/assets/public/index.html`)
|
||
2. Wire plugin into Capacitor (`capacitor.config.json`)
|
||
3. Add Info.plist keys (BGTask identifiers, background modes, notification permissions)
|
||
4. Build/run (`./scripts/build-ios-test-app.sh --simulator` or Xcode)
|
||
5. Press buttons: Check Plugin Status → Request Permissions → Schedule Test Notification
|
||
6. See logs with prefixes `DNP-PLUGIN`, `DNP-FETCH`, `DNP-SCHEDULER`
|
||
|
||
---
|
||
|
||
## UI Parity Requirements
|
||
|
||
### HTML/JS UI
|
||
|
||
The iOS test app **MUST** use the same HTML/JS UI as the Android test app to ensure consistent testing experience across platforms.
|
||
|
||
**Source:** Copy from `test-apps/android-test-app/app/src/main/assets/public/index.html`
|
||
|
||
**Required UI Elements:**
|
||
- Plugin registration status indicator
|
||
- Permission status display (✅/❌ indicators)
|
||
- Test notification button
|
||
- Check permissions button
|
||
- Request permissions button
|
||
- Channel management buttons (Check Channel Status, Open Channel Settings)
|
||
- Status display area
|
||
- Log output area (optional, for debugging)
|
||
|
||
### UI Functionality
|
||
|
||
The test app UI must support:
|
||
|
||
1. **Plugin Status Check**
|
||
- Display plugin availability status
|
||
- Show "Plugin is loaded and ready!" when available
|
||
|
||
2. **Permission Management**
|
||
- Display current permission status
|
||
- Request permissions button
|
||
- Check permissions button
|
||
- Show ✅/❌ indicators for each permission
|
||
|
||
3. **Channel Management** (iOS parity with Android)
|
||
- Check channel status button (iOS: checks app-wide notification authorization)
|
||
- Open channel settings button (iOS: opens app Settings, not per-channel)
|
||
- Note: iOS doesn't have per-channel control like Android; these methods provide app-wide equivalents
|
||
|
||
4. **Notification Testing**
|
||
- Schedule test notification button
|
||
- Display scheduled time
|
||
- Show notification status
|
||
|
||
5. **Status Display**
|
||
- Show last notification time
|
||
- Show pending notification count
|
||
- Display error messages if any
|
||
|
||
### UI Elements to Plugin Methods Mapping
|
||
|
||
| UI Element / Button | Plugin Method / API Call | Notes |
|
||
|---------------------|-------------------------|-------|
|
||
| "Check Plugin Status" | `DailyNotification.configure()` or status call | Verify plugin load & config |
|
||
| "Check Permissions" | `checkPermissionStatus()` | Returns current notification permission status |
|
||
| "Request Permissions" | `requestNotificationPermissions()` | Requests notification permissions (shows system dialog) |
|
||
| "Schedule Test Notification" | `scheduleDailyNotification()` | Should schedule prefetch + notify |
|
||
| "Show Last Notification" | `getLastNotification()` | Uses deterministic path (Bucket A) |
|
||
| "Cancel All Notifications" | `cancelAllNotifications()` | Uses deterministic path (Bucket A) |
|
||
| "Get Notification Status" | `getNotificationStatus()` | Uses deterministic path (Bucket A) |
|
||
| "Check Channel Status" | `isChannelEnabled(channelId?)` | Checks if notifications enabled (iOS: app-wide) |
|
||
| "Open Channel Settings" | `openChannelSettings(channelId?)` | Opens notification settings (iOS: app Settings) |
|
||
|
||
**See `IOS_PREFETCH_TESTING.md` Behavior Classification for deterministic vs heuristic methods.**
|
||
|
||
---
|
||
|
||
## iOS Permissions Configuration
|
||
|
||
### Info.plist Requirements
|
||
|
||
The test app's `Info.plist` **MUST** include:
|
||
|
||
```xml
|
||
<!-- Background Task Identifiers -->
|
||
<!-- These identifiers MUST match exactly the values used in IOS_PREFETCH_TESTING.md and the plugin Swift code, otherwise BGTaskScheduler (see glossary) will silently fail -->
|
||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||
<array>
|
||
<string>com.timesafari.dailynotification.fetch</string> <!-- Prefetch task (BGAppRefreshTask - see glossary) -->
|
||
<string>com.timesafari.dailynotification.notify</string> <!-- Notification maintenance task (if applicable) -->
|
||
</array>
|
||
|
||
<!-- Background Modes -->
|
||
<key>UIBackgroundModes</key>
|
||
<array>
|
||
<string>background-fetch</string>
|
||
<string>background-processing</string>
|
||
<string>remote-notification</string>
|
||
</array>
|
||
|
||
<!-- Notification Permissions -->
|
||
<key>NSUserNotificationsUsageDescription</key>
|
||
<string>This app uses notifications to deliver daily updates and reminders.</string>
|
||
```
|
||
|
||
### Background App Refresh
|
||
|
||
- Background App Refresh must be enabled in Settings
|
||
- Test app should check and report Background App Refresh status
|
||
- User should be guided to enable Background App Refresh if disabled
|
||
|
||
---
|
||
|
||
## Build Options
|
||
|
||
### Xcode GUI Build
|
||
|
||
1. **Open Workspace:**
|
||
```bash
|
||
cd test-apps/ios-test-app
|
||
open App.xcworkspace # or App.xcodeproj
|
||
```
|
||
|
||
2. **Select Target:**
|
||
- Choose iOS Simulator (iPhone 15, iPhone 15 Pro, etc.)
|
||
- Or physical device (requires signing)
|
||
|
||
3. **Build and Run:**
|
||
- Press Cmd+R
|
||
- Or Product → Run
|
||
|
||
### Command-Line Build
|
||
|
||
Use the build script:
|
||
|
||
```bash
|
||
# From repo root
|
||
./scripts/build-ios-test-app.sh --simulator
|
||
|
||
# Or for device
|
||
./scripts/build-ios-test-app.sh --device
|
||
```
|
||
|
||
**Phase 2 Enhancement:** Refactor into modular subcommands:
|
||
|
||
```bash
|
||
./scripts/build-ios-test-app.sh setup # pod install + sync
|
||
./scripts/build-ios-test-app.sh run-sim # build + run simulator
|
||
./scripts/build-ios-test-app.sh device # build + deploy device
|
||
```
|
||
|
||
**Copy-Paste Commands:**
|
||
|
||
```bash
|
||
# Setup (first time or after dependency changes)
|
||
cd test-apps/ios-test-app
|
||
pod install
|
||
npx cap sync ios
|
||
|
||
# Build for simulator
|
||
xcodebuild -workspace App.xcworkspace \
|
||
-scheme ios-test-app \
|
||
-configuration Debug \
|
||
-sdk iphonesimulator \
|
||
-destination 'platform=iOS Simulator,name=iPhone 15' \
|
||
build
|
||
|
||
# Run on simulator
|
||
xcodebuild -workspace App.xcworkspace \
|
||
-scheme ios-test-app \
|
||
-configuration Debug \
|
||
-sdk iphonesimulator \
|
||
-destination 'platform=iOS Simulator,name=iPhone 15' \
|
||
test
|
||
```
|
||
|
||
### Future CI Integration (Optional)
|
||
|
||
**Note for Phase 2+:** Consider adding `xcodebuild`-based CI job that:
|
||
- Builds `test-apps/ios-test-app` for simulator
|
||
- Runs a minimal UI test that:
|
||
- Launches app
|
||
- Calls `configure()` and `getNotificationStatus()`
|
||
- Validates log sequence + successful fetch simulation via LLDB trigger
|
||
- This ensures test app remains buildable as plugin evolves
|
||
|
||
**Phase 1:** Manual testing only; CI integration is out of scope.
|
||
|
||
**CI Readiness (Phase 2):**
|
||
- Add `xcodebuild` target for "Prefetch Integration Test"
|
||
- Validate log sequence + successful fetch simulation via LLDB trigger
|
||
- Use log validation script (`validate-ios-logs.sh`) for automated sequence checking
|
||
|
||
### Build Requirements
|
||
|
||
**Required Tools:**
|
||
- **Xcode:** 15.0 or later
|
||
- **macOS:** 13.0 (Ventura) or later
|
||
- **iOS Deployment Target:** iOS 15.0 or later
|
||
- **CocoaPods:** >= 1.13 (must run `pod install` before first build)
|
||
- **Node.js:** 20.x (recommended)
|
||
- **npm:** Latest stable (comes with Node.js)
|
||
- **Xcode Command Line Tools:** Must run `xcode-select --install` if not already installed
|
||
|
||
**Note:** Mismatched versions are **out of scope** for Phase 1 support. Use recommended versions to avoid compatibility issues.
|
||
|
||
---
|
||
|
||
## Capacitor Configuration
|
||
|
||
### Plugin Registration
|
||
|
||
The test app **MUST** register the DailyNotification plugin:
|
||
|
||
**`capacitor.config.json` or `capacitor.config.ts`:**
|
||
```json
|
||
{
|
||
"plugins": {
|
||
"DailyNotification": {
|
||
"enabled": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Plugin Path
|
||
|
||
The plugin must be accessible from the test app:
|
||
|
||
- **Development:** Plugin source at `../../ios/Plugin/`
|
||
- **Production:** Plugin installed via npm/CocoaPods
|
||
|
||
### Sync Command
|
||
|
||
After making changes to plugin or web assets:
|
||
|
||
```bash
|
||
cd test-apps/ios-test-app
|
||
npx cap sync ios
|
||
```
|
||
|
||
---
|
||
|
||
## Debugging Strategy
|
||
|
||
**When executing the scenarios in `IOS_PREFETCH_TESTING.md`, use the following commands while looking for logs with prefixes `DNP-PLUGIN`, `DNP-FETCH`, `DNP-SCHEDULER`.**
|
||
|
||
### Xcode Debugger
|
||
|
||
**Check Pending Notifications:**
|
||
```swift
|
||
po UNUserNotificationCenter.current().pendingNotificationRequests()
|
||
```
|
||
|
||
**Check Permission Status:**
|
||
```swift
|
||
po await UNUserNotificationCenter.current().notificationSettings()
|
||
```
|
||
|
||
**Manually Trigger BGTask (Simulator Only):**
|
||
```swift
|
||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||
```
|
||
|
||
**Copy-Paste Commands:**
|
||
|
||
```swift
|
||
// In Xcode LLDB console:
|
||
// Check pending notifications
|
||
po UNUserNotificationCenter.current().pendingNotificationRequests()
|
||
|
||
// Check permission status
|
||
po await UNUserNotificationCenter.current().notificationSettings()
|
||
|
||
// Manually trigger BGTask (simulator only)
|
||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||
|
||
// Force reschedule all tasks (test harness)
|
||
po DailyNotificationBackgroundTaskTestHarness.forceRescheduleAll()
|
||
|
||
// Simulate time warp (test harness)
|
||
po DailyNotificationBackgroundTaskTestHarness.simulateTimeWarp(minutesForward: 60)
|
||
```
|
||
|
||
### Console.app Logging
|
||
|
||
1. Open Console.app (Applications → Utilities)
|
||
2. Select device/simulator
|
||
3. Filter by: `DNP-` or `DailyNotification`
|
||
|
||
**Key Log Prefixes:**
|
||
- `DNP-PLUGIN:` - Main plugin operations
|
||
- `DNP-FETCH:` - Background fetch operations
|
||
- `DNP-SCHEDULER:` - Scheduling operations
|
||
- `DNP-STORAGE:` - Storage operations
|
||
|
||
**Structured Logging (Swift Logger):**
|
||
|
||
The plugin uses Swift `Logger` categories for structured logging:
|
||
- `com.timesafari.dailynotification.plugin` - Plugin operations
|
||
- `com.timesafari.dailynotification.fetch` - Fetch operations
|
||
- `com.timesafari.dailynotification.scheduler` - Scheduling operations
|
||
- `com.timesafari.dailynotification.storage` - Storage operations
|
||
|
||
Filter in Console.app by subsystem: `com.timesafari.dailynotification`
|
||
|
||
**Phase 2: Log Validation Script**
|
||
|
||
Add helper script (`validate-ios-logs.sh`) to grep for required sequence markers:
|
||
|
||
```bash
|
||
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-logs.sh
|
||
```
|
||
|
||
This confirms that all critical log steps occurred in proper order and flags missing or out-of-order events automatically.
|
||
|
||
### Common Debugging Scenarios
|
||
|
||
**Scenario: BGTask not running when expected → follow this checklist:**
|
||
|
||
1. **BGTask Not Running:**
|
||
- Check Info.plist has `BGTaskSchedulerPermittedIdentifiers`
|
||
- Verify identifiers match code exactly (case-sensitive)
|
||
- Verify task registered in AppDelegate before app finishes launching
|
||
- Check Background App Refresh enabled in Settings → [Your App]
|
||
- Verify app not force-quit (iOS won't run BGTask for force-quit apps)
|
||
- Use simulator-only LLDB command to manually trigger
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Log Checklist: Section 3`
|
||
|
||
2. **Notifications Not Delivering:**
|
||
- Check notification permissions: `UNUserNotificationCenter.current().getNotificationSettings()`
|
||
- Verify notification scheduled: `UNUserNotificationCenter.current().getPendingNotificationRequests()`
|
||
- Check notification category registered
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Log Checklist: Section 4`
|
||
|
||
3. **BGTaskScheduler Fails on Simulator:**
|
||
- **Expected Behavior:** BGTaskSchedulerErrorDomain Code=1 (notPermitted) is **normal on simulator**
|
||
- BGTaskScheduler doesn't work reliably on simulator - this is an iOS limitation, not a plugin bug
|
||
- Notification scheduling still works; prefetch won't run on simulator but will work on real devices
|
||
- Error handling logs clear message: "Background fetch scheduling failed (expected on simulator)"
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Known OS Limitations` for details
|
||
- **Testing:** Use Xcode → Debug → Simulate Background Fetch for simulator testing
|
||
|
||
4. **Plugin Not Discovered:**
|
||
- Check plugin class conforms to `CAPBridgedPlugin` protocol (required for Capacitor discovery)
|
||
- Verify `@objc extension DailyNotificationPlugin: CAPBridgedPlugin` exists in plugin code
|
||
- Check plugin framework is force-loaded in AppDelegate before Capacitor initializes
|
||
- Verify `pluginMethods` array includes all `@objc` methods
|
||
- **See also:** `doc/directives/0003-iOS-Android-Parity-Directive.md – Plugin Discovery Issue` for detailed troubleshooting
|
||
|
||
5. **Build Failures:**
|
||
- Run `pod install`
|
||
- Clean build folder (Cmd+Shift+K)
|
||
- Verify Capacitor plugin path
|
||
|
||
---
|
||
|
||
## Test App Implementation Checklist
|
||
|
||
### Setup
|
||
|
||
- [ ] Create `test-apps/ios-test-app/` directory
|
||
- [ ] Initialize Capacitor iOS project
|
||
- [ ] Copy HTML/JS UI from Android test app
|
||
- [ ] Configure Info.plist with BGTask identifiers
|
||
- [ ] Configure Info.plist with background modes
|
||
- [ ] Add notification permission description
|
||
|
||
### Plugin Integration
|
||
|
||
- [ ] Register DailyNotification plugin in Capacitor config
|
||
- [ ] Ensure plugin path is correct
|
||
- [ ] Run `npx cap sync ios`
|
||
- [ ] Verify plugin loads in test app
|
||
|
||
### UI Implementation
|
||
|
||
- [ ] Copy HTML/JS from Android test app
|
||
- [ ] Test plugin status display
|
||
- [ ] Test permission status display
|
||
- [ ] Test notification scheduling UI
|
||
- [ ] Test status display
|
||
|
||
### Build & Test
|
||
|
||
- [ ] Build script works (`./scripts/build-ios-test-app.sh`)
|
||
- [ ] App builds in Xcode
|
||
- [ ] App runs on simulator
|
||
- [ ] Plugin methods work from UI
|
||
- [ ] Notifications deliver correctly
|
||
- [ ] BGTask executes (with manual trigger in simulator)
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
```
|
||
test-apps/ios-test-app/
|
||
├── App.xcworkspace # Xcode workspace (if using CocoaPods)
|
||
├── App.xcodeproj # Xcode project
|
||
├── App/ # Main app directory
|
||
│ ├── App/
|
||
│ │ ├── AppDelegate.swift
|
||
│ │ ├── SceneDelegate.swift
|
||
│ │ ├── Info.plist # Must include BGTask identifiers
|
||
│ │ └── Assets.xcassets
|
||
│ └── Public/ # Web assets (HTML/JS)
|
||
│ └── index.html # Same as Android test app
|
||
├── Podfile # CocoaPods dependencies
|
||
├── capacitor.config.json # Capacitor configuration
|
||
└── package.json # npm dependencies (if any)
|
||
```
|
||
|
||
---
|
||
|
||
## Testing Scenarios
|
||
|
||
### Basic Functionality
|
||
|
||
**Happy Path Example:**
|
||
|
||
When everything is working correctly, your first run should produce logs like this:
|
||
|
||
```text
|
||
[DNP-PLUGIN] configure() called
|
||
[DNP-PLUGIN] status = ready
|
||
[DNP-PLUGIN] requestPermissions() → granted
|
||
[DNP-SCHEDULER] scheduleDailyNotification() → notificationTime=2025-11-15T05:53:00Z
|
||
[DNP-FETCH] Scheduling prefetch for notification at 2025-11-15T05:53:00Z
|
||
[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=2025-11-15T05:48:00Z)
|
||
[DNP-SCHEDULER] Scheduling notification for 2025-11-15T05:53:00Z
|
||
[DNP-STORAGE] Persisted schedule to DB (id=..., type=DAILY, ...)
|
||
```
|
||
|
||
**If your first run doesn't look roughly like this, fix that before proceeding to BGTask tests.**
|
||
|
||
1. **Plugin Registration** `[P1-Core][SIM+DEV]`
|
||
- Launch app
|
||
- Verify plugin status shows "Plugin is loaded and ready!"
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 2`
|
||
|
||
2. **Permission Management** `[P1-Core][SIM+DEV]`
|
||
- Check permissions
|
||
- Request permissions
|
||
- Verify permissions granted
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 6 (Negative-Path Tests)`
|
||
|
||
3. **Notification Scheduling** `[P1-Prefetch][SIM+DEV]`
|
||
- Schedule test notification
|
||
- Verify notification scheduled
|
||
- Wait for notification to appear
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Steps 3-5`
|
||
|
||
### Background Tasks
|
||
|
||
**Sim vs Device Behavior:**
|
||
|
||
- `[SIM+DEV]` – can be fully tested on simulator and device (logic correctness)
|
||
- `[DEV-ONLY]` – timing / heuristic behavior, must be verified on real hardware
|
||
|
||
1. **BGTask Scheduling** `[P1-Prefetch][SIM+DEV]`
|
||
- Schedule notification with prefetch
|
||
- Verify BGTask scheduled 5 minutes before notification
|
||
- Manually trigger BGTask (simulator only)
|
||
- Verify content fetched
|
||
- **Note:** Logic and logs on sim; real timing on device
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Steps 3-4`
|
||
|
||
2. **BGTask Miss Detection** `[P1-Prefetch][DEV-ONLY]`
|
||
- Schedule notification
|
||
- Wait 15+ minutes
|
||
- Launch app
|
||
- Verify BGTask rescheduled
|
||
- **Note:** Only meaningful on device (heuristic timing)
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Real Device Test Plan: Step 4`
|
||
|
||
### Error Handling
|
||
|
||
1. **Permission Denied** `[P1-Core][SIM+DEV]`
|
||
- Deny notification permissions
|
||
- Try to schedule notification
|
||
- Verify error returned
|
||
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 7 (Negative-Path Tests)`
|
||
|
||
2. **Invalid Parameters** `[P1-Core][SIM+DEV]`
|
||
- Try to schedule with invalid time format
|
||
- Verify error returned
|
||
|
||
3. **Network Failures** `[P1-Prefetch][SIM+DEV]`
|
||
- Turn off network during fetch
|
||
- Verify graceful fallback to on-demand fetch
|
||
- Test recovery: network off → fail → network on → retry succeeds
|
||
|
||
4. **Server Errors / Auth Expiry** `[P1-Prefetch][SIM+DEV]`
|
||
- Simulate HTTP 401 or 500 error
|
||
- Verify plugin logs failure with reason (AUTH, NETWORK)
|
||
- Confirm telemetry counter increments: `dnp_prefetch_failure_total{reason="AUTH"}`
|
||
- Verify fallback to live fetch at notification time
|
||
|
||
5. **Corrupted Cache** `[P1-Prefetch][SIM+DEV]`
|
||
- Manually tamper with stored cache (invalid JSON or remove entry)
|
||
- Verify plugin detects invalid cache and falls back
|
||
- Ensure no crash on reading bad cache (error handling wraps cache read)
|
||
|
||
6. **BGTask Execution Failure** `[P1-Prefetch][SIM+DEV]`
|
||
- Simulate internal failure in BGTask handler
|
||
- Verify expiration handler or completion still gets called
|
||
- Ensure task marked complete even on exception
|
||
|
||
7. **Repeated Scheduling Calls** `[P1-Prefetch][SIM+DEV]`
|
||
- Call `scheduleDailyNotification()` multiple times rapidly
|
||
- Verify no duplicate scheduling (one active task rule enforced)
|
||
- Confirm only one BGTask is actually submitted
|
||
|
||
### Advanced Features `[P2-Advanced]`
|
||
|
||
1. **Rolling Window** `[P2-Advanced]`
|
||
- Schedule multiple notifications
|
||
- Verify rolling window maintenance
|
||
- Check notification limits (64 max on iOS)
|
||
|
||
2. **TTL Enforcement** `[P2-Advanced]`
|
||
- Schedule notification with prefetch
|
||
- Wait for TTL to expire
|
||
- Verify stale content discarded at delivery
|
||
|
||
---
|
||
|
||
## Developer/Test Harness Features (Optional but Recommended)
|
||
|
||
Since this test app is for internal testing, it can expose more tools:
|
||
|
||
### Dev-Only Toggles
|
||
|
||
| Button | Action | Purpose |
|
||
|--------|--------|---------|
|
||
| "Simulate BGTask Now" | Calls LLDB trigger | Quick sanity test |
|
||
| "Schedule 1-min Notification" | Auto schedules T-Lead=1 | Edge case testing |
|
||
| "Simulate DST Shift" | Adds +1 hr offset | DST handling check |
|
||
| "Show Cached Payload" | Displays JSON cache | Prefetch validation |
|
||
| "Force Reschedule All" | Calls `forceRescheduleAll()` | BGTask recovery testing |
|
||
| "Time Warp +N Minutes" | Calls `simulateTimeWarp()` | Accelerated TTL/T-Lead tests |
|
||
|
||
1. **Force Schedule Notification N Minutes from Now**
|
||
- Bypass normal scheduling UI
|
||
- Directly call `scheduleDailyNotification()` with calculated time
|
||
- Useful for quick testing scenarios
|
||
|
||
2. **Force "Prefetch-Only" Task**
|
||
- Trigger BGTask without scheduling notification
|
||
- Useful for testing prefetch logic in isolation
|
||
- Display raw JSON returned from API (last fetched payload)
|
||
|
||
3. **Display Raw API Response**
|
||
- Show last fetched payload as JSON
|
||
- Useful for debugging API responses
|
||
- Can be referenced from `IOS_PREFETCH_TESTING.md` as shortcuts for specific scenarios
|
||
|
||
4. **Manual BGTask Trigger (Dev Build Only)**
|
||
- Button to manually trigger BGTask (simulator only)
|
||
- Wraps the LLDB command in UI for convenience
|
||
|
||
5. **UI Feedback Enhancements**
|
||
- Add persistent toast/log area showing step-by-step plugin state ("Registered", "Scheduled", "BGTask fired", etc.)
|
||
- Include color-coded state indicators for each stage (🟢 OK, 🟡 Pending, 🔴 Error)
|
||
|
||
These features can then be referenced from `IOS_PREFETCH_TESTING.md` as shortcuts for specific test scenarios.
|
||
|
||
### Persistent Schedule Snapshot
|
||
|
||
**Phase 2 Enhancement:** Store a simple JSON of the last prefetch state for post-run verification:
|
||
|
||
```json
|
||
{
|
||
"last_schedule": "2025-11-15T05:48:00Z",
|
||
"last_prefetch": "2025-11-15T05:50:00Z",
|
||
"last_notification": "2025-11-15T05:53:00Z",
|
||
"prefetch_success": true,
|
||
"cached_content_used": true,
|
||
"contentHash": "abcdef123456",
|
||
"scheduleHash": "xyz789"
|
||
}
|
||
```
|
||
|
||
This can be used for post-run verification and telemetry aggregation. Access via test app UI button "Show Schedule Snapshot" or via UserDefaults key `DNP_ScheduleSnapshot`.
|
||
|
||
### UI Indicators for Tests
|
||
|
||
**Status Display:**
|
||
- Status label/icon: 🟢 green when last notification was fetched and delivered from cache
|
||
- 🔴 red if something failed (network error, etc.)
|
||
- 🟡 yellow if pending/unknown state
|
||
|
||
**Last Operation Summary:**
|
||
- Display last fetch time
|
||
- Show whether cached content was used
|
||
- Display any error message
|
||
- Show telemetry counter values
|
||
|
||
**Dump Prefetch Status Button:**
|
||
- Triggers plugin to report all relevant info
|
||
- Shows pending tasks, cache status, telemetry snapshot
|
||
- Displays in scrollable text view
|
||
- Useful on devices where attaching debugger is inconvenient
|
||
|
||
### In-App Log Viewer (Phase 2)
|
||
|
||
**For QA Use:**
|
||
- Read app's unified logging (OSLog) for entries with subsystem `com.timesafari.dailynotification`
|
||
- Present logs on screen or allow export to file
|
||
- Capture Logger output into text buffer during app session
|
||
- **Security:** Only enable in test builds, not production
|
||
|
||
**Export Test Results:**
|
||
- Save test run summary to file (JSON format)
|
||
- Include timestamps, outcomes, telemetry counters
|
||
- Access via "Export Test Results" button
|
||
- Collect from devices via Xcode or CI pipelines
|
||
|
||
## Risks & Gotchas
|
||
|
||
**Common pitfalls when working with the test app:**
|
||
|
||
1. **Accidentally editing shared HTML/JS in iOS test app instead of Android canonical source**
|
||
- Always edit Android test app HTML/JS first, then copy to iOS
|
||
- Keep iOS test app HTML/JS as a copy, not the source of truth
|
||
|
||
2. **Forgetting `npx cap sync ios` after plugin or asset changes**
|
||
- Run `npx cap sync ios` after any plugin code changes
|
||
- Run `npx cap sync ios` after any web asset changes
|
||
- Check `capacitor.config.json` is up to date
|
||
|
||
3. **Running with stale Pods**
|
||
- Run `pod repo update` periodically
|
||
- Run `pod install` after dependency changes
|
||
- Clean build folder (Cmd+Shift+K) if Pods seem stale
|
||
|
||
4. **Changing BGTask identifiers in one place but not the other**
|
||
- Info.plist `BGTaskSchedulerPermittedIdentifiers` must match Swift code exactly (case-sensitive)
|
||
- Check both places when updating identifiers
|
||
- See `IOS_PREFETCH_TESTING.md` for identifier requirements
|
||
|
||
5. **Mismatched tooling versions**
|
||
- Use recommended versions (Node 20.x, CocoaPods >= 1.13, Xcode 15.0+)
|
||
- Mismatched versions are out of scope for Phase 1 support
|
||
|
||
## References
|
||
|
||
- **Directive:** `doc/directives/0003-iOS-Android-Parity-Directive.md`
|
||
- **Testing Guide:** `doc/test-app-ios/IOS_PREFETCH_TESTING.md` - Comprehensive prefetch testing procedures
|
||
- **Android Test App:** `test-apps/android-test-app/`
|
||
- **Build Script:** `scripts/build-ios-test-app.sh`
|
||
|
||
---
|
||
|
||
## Review & Sign-Off Checklist (Phase 1)
|
||
|
||
**Use this checklist to verify Phase 1 iOS test app is complete:**
|
||
|
||
- [ ] Test app builds on simulator with recommended toolchain (Node 20.x, CocoaPods >= 1.13, Xcode 15.0+)
|
||
- [ ] Test app builds on at least one real device
|
||
- [ ] All UI buttons map to plugin methods as per the **UI Elements to Plugin Methods Mapping** table
|
||
- [ ] Happy-path log sequence matches the example in **Testing Scenarios → Basic Functionality**
|
||
- [ ] BGTask identifiers are consistent (Info.plist ↔ Swift ↔ docs)
|
||
- [ ] Risks & Gotchas section has been read and acknowledged by the implementer
|
||
- [ ] Security & Privacy Constraints have been followed (non-production endpoints, no PII in logs)
|
||
|
||
---
|
||
|
||
## Technical Correctness Requirements
|
||
|
||
### BGTask Scheduling & Lifecycle
|
||
|
||
**Validation Requirements:**
|
||
- Verify `earliestBeginDate` is at least 60 seconds in future (iOS requirement)
|
||
- Log and handle scheduling errors gracefully (Code=1 on simulator is expected)
|
||
- Cancel existing pending task before scheduling new (one active task rule)
|
||
- Use `BGTaskScheduler.shared.getPendingTaskRequests()` in debug to verify only one task pending
|
||
|
||
**Schedule Next Task at Execution:**
|
||
- Adopt Apple's best practice: schedule next task IMMEDIATELY at start of BGTask handler
|
||
- This ensures continuity even if app is terminated shortly after
|
||
- Pattern: Schedule next → Initiate async work → Mark complete → Use expiration handler
|
||
|
||
**Expiration Handler and Completion:**
|
||
- Implement expiration handler to cancel ongoing operations if iOS terminates task (~30 seconds)
|
||
- Always call `task.setTaskCompleted(success:)` exactly once
|
||
- Use `success: false` if fetch didn't complete (system may reschedule sooner)
|
||
- Use `success: true` if all went well
|
||
- Re-schedule next task after marking completion (for recurring use cases)
|
||
|
||
**Error Handling & Retry:**
|
||
- Distinguish recoverable errors (transient network) vs permanent failures
|
||
- For network failures: log failure reason, set `success: false`, consider cached data if available
|
||
- For logic errors: log clear message, call `setTaskCompleted(success: false)`, exit cleanly
|
||
- Ensure fallback to on-demand fetch at notification time if prefetch fails
|
||
|
||
**Data Consistency & Cleanup:**
|
||
- Cross-check `notificationTime` matches payload's `scheduled_for` field
|
||
- Validate TTL on cached content at notification time (discard if expired)
|
||
- Ensure content is saved to persistent store before marking BGTask complete
|
||
- Implement cache cleanup for outdated data
|
||
- Handle permission changes gracefully (detect failure at delivery, log outcome)
|
||
|
||
### Scheduling and Notification Coordination
|
||
|
||
**Unified Scheduling Logic:**
|
||
- Atomically schedule both UNNotificationRequest and BGAppRefreshTaskRequest
|
||
- If one fails, cancel the other or report partial failure
|
||
- Return clear status/error code to JS layer
|
||
|
||
**BGTask Identifier Constants:**
|
||
- Verify identifier in code exactly matches Info.plist (case-sensitive)
|
||
- Test harness should verify on app launch (logs show successful registration)
|
||
|
||
**Concurrency Considerations:**
|
||
- Handle potentially overlapping schedules (Phase 2: multiple notifications)
|
||
- Use one BGTask to fetch for next upcoming notification only
|
||
- Store next notification's schedule ID and time in shared place
|
||
- Use locks or dispatch synchronization to avoid race conditions
|
||
|
||
**OS Limits:**
|
||
- Acknowledge force-quit prevents BGTask execution (can't circumvent)
|
||
- Tolerate running slightly later than `earliestBeginDate` (iOS heuristics)
|
||
- Log actual execution time vs scheduled time for analysis
|
||
|
||
---
|
||
|
||
## Phase 2 Forward Plan
|
||
|
||
**Planned enhancements for Phase 2:**
|
||
|
||
- Add quick scenario buttons (Simulate BGTask Now, Schedule 1-min Notification, Simulate DST Shift, Show Cached Payload)
|
||
- Implement persistent schedule snapshot (JSON of last prefetch state)
|
||
- Add color-coded UI feedback (🟢 OK, 🟡 Pending, 🔴 Error)
|
||
- Refactor build script into modular subcommands (`setup`, `run-sim`, `device`)
|
||
- Integrate CI pipeline with `xcodebuild` target for "Prefetch Integration Test"
|
||
- Add log validation script (`validate-ios-logs.sh`) for automated sequence checking
|
||
- Implement rolling window & TTL validation
|
||
- Add telemetry verification for multi-day scenarios
|
||
- Test on different device models and iOS versions
|
||
- Add in-app log viewer/export for QA use
|
||
|
||
**See also:** `doc/directives/0003-iOS-Android-Parity-Directive.md` for Phase 2 implementation details.
|
||
|
||
---
|
||
|
||
## Changelog (high-level)
|
||
|
||
- 2025-11-15 — Initial Phase 1 version (prefetch MVP, Android parity)
|
||
|
||
---
|
||
|
||
**Status:** 📋 **REQUIRED FOR PHASE 1**
|
||
**Last Updated:** 2025-11-15
|
||
|