Files
daily-notification-plugin/doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md
Matthew Raymer 95507c6121 test(ios-prefetch): enhance testing infrastructure and validation
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
2025-11-17 06:37:06 +00:00

803 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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