Updated iOS test app documentation to reflect recent implementation work: channel methods, permission methods, BGTaskScheduler simulator limitation, and plugin discovery troubleshooting. Changes: - Added channel methods (isChannelEnabled, openChannelSettings) to UI mapping - Fixed permission method name (requestPermissions → requestNotificationPermissions) - Added checkPermissionStatus to UI mapping - Added Channel Management section explaining iOS limitations - Added BGTaskScheduler simulator limitation documentation (Code=1 is expected) - Added plugin discovery troubleshooting section (CAPBridgedPlugin conformance) - Added permission and channel methods to behavior classification table - Updated Known OS Limitations with simulator-specific BGTaskScheduler behavior Files modified: - doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md: UI mapping, debugging scenarios - doc/test-app-ios/IOS_PREFETCH_TESTING.md: Known limitations, behavior classification
659 lines
26 KiB
Markdown
659 lines
26 KiB
Markdown
# iOS Prefetch Testing Guide
|
||
|
||
**Purpose:** How to test background prefetch for DailyNotificationPlugin on iOS (simulator + device)
|
||
|
||
**Plugin Target:** DailyNotificationPlugin v3.x (iOS)
|
||
**Phase:** Phase 1 – Prefetch MVP
|
||
**Last Updated:** 2025-11-15
|
||
**Status:** 🎯 **ACTIVE** - Testing guide for Phase 1+ implementation
|
||
|
||
**Note:** This guide assumes the iOS test app is implemented as described in `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.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; no hard guarantee of 5-min prefetch
|
||
|
||
---
|
||
|
||
## Non-Goals (Phase 1)
|
||
|
||
**Out of scope for Phase 1 testing:**
|
||
|
||
- ❌ No testing of silent push notifications
|
||
- ❌ No testing of advanced rolling-window policies beyond "schedule one daily notification"
|
||
- ❌ No guarantees of exact N-minute timing (iOS heuristics only)
|
||
- ❌ No testing of multi-day rolling windows (deferred to Phase 2)
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Goals](#goals)
|
||
2. [Assumptions](#assumptions)
|
||
3. [Simulator Test Plan (Logic-Correctness)](#simulator-test-plan)
|
||
4. [Real Device Test Plan (Timing & Reliability)](#real-device-test-plan)
|
||
5. [Log Checklist](#log-checklist)
|
||
6. [Behavior Classification](#behavior-classification)
|
||
7. [Test Harness Reference](#test-harness-reference)
|
||
|
||
---
|
||
|
||
## Goals
|
||
|
||
Verify that the **iOS prefetch path actually runs**:
|
||
|
||
- ✅ BGTask is registered
|
||
- ✅ BGTask is scheduled
|
||
- ✅ Fetch executes
|
||
- ✅ Data is persisted & used by the notification
|
||
|
||
**Separate concerns:**
|
||
- **"Does the logic work?"** → Test in Simulator
|
||
- **"Does timing behave?"** → Test on Real Device
|
||
|
||
## Time & T-Lead Rules
|
||
|
||
**Critical:** These rules prevent bugs when tweaking prefetch timing.
|
||
|
||
### Time Storage
|
||
|
||
- **`notificationTime` is stored in UTC** (not local time)
|
||
- All internal calculations use UTC timestamps
|
||
- Display to users may convert to local time, but storage remains UTC
|
||
|
||
### Prefetch Lead Calculation
|
||
|
||
- **Prefetch lead:** `prefetchTime = notificationTime - leadMinutes`
|
||
- **Default lead:** 5 minutes (300 seconds)
|
||
- **Minimum lead:** 1 minute (60 seconds) - iOS requires at least 1 minute in future
|
||
- **Maximum lead:** No hard limit, but iOS heuristics favor shorter windows
|
||
|
||
### Edge Cases
|
||
|
||
**Negative/Too-Small Lead Times:**
|
||
- If `notificationTime - leadMinutes < now + 60 seconds`:
|
||
- Cap prefetch at `now + 60 seconds` (minimum iOS requirement)
|
||
- Log: `[DNP-FETCH] Prefetch lead too small, capping at minimum (60s)`
|
||
- Or disable prefetch if notification is too soon
|
||
|
||
**DST (Daylight Saving Time) Changes:**
|
||
- UTC storage prevents DST issues
|
||
- Test case: Schedule notification at 2:00 AM on DST change day
|
||
- Verify: Prefetch and notification times remain correct (UTC-based)
|
||
|
||
**Timezone Changes:**
|
||
- Test case: User changes timezone between schedule and fire
|
||
- Verify: Notification fires at correct UTC time (not local time)
|
||
- Log: `[DNP-SCHEDULER] Timezone changed, but UTC schedule unchanged`
|
||
|
||
### Test Cases
|
||
|
||
1. **DST Change:**
|
||
- Schedule notification for 2:00 AM on DST transition day
|
||
- Verify prefetch and notification use UTC, not local time
|
||
- Check logs show UTC timestamps
|
||
|
||
2. **Timezone Change:**
|
||
- Schedule notification
|
||
- Change device timezone
|
||
- Verify notification fires at correct UTC time
|
||
- Check logs confirm UTC-based scheduling
|
||
|
||
3. **Too-Small Lead:**
|
||
- Schedule notification 30 seconds in future
|
||
- Verify prefetch is capped at 60 seconds minimum
|
||
- Check logs show capping behavior
|
||
|
||
## If You Only Have 30 Minutes
|
||
|
||
Quick validation checklist (each step links to log verification):
|
||
|
||
1. Build test app (per `IOS_TEST_APP_REQUIREMENTS.md`)
|
||
2. Run on iOS Simulator
|
||
3. Schedule notification 5 minutes in future → **See Log Checklist → Notification Scheduled**
|
||
4. Background app (Home button / Cmd+Shift+H)
|
||
5. Xcode → Debug → Simulate Background Fetch → **See Log Checklist → BGTask Fired**
|
||
6. Confirm logs show: Registration → Scheduling → BGTask handler → Fetch success → Task completed
|
||
7. Wait for notification time or manually trigger → **See Log Checklist → Notification Delivery**
|
||
8. Verify notification uses cached content (check logs for `[DNP-FETCH] Found cached content`) → **See Log Checklist → Notification Delivery**
|
||
|
||
---
|
||
|
||
## Assumptions
|
||
|
||
- Using **BGTaskScheduler** (`BGAppRefreshTask`) for prefetch
|
||
- App has **background modes** enabled:
|
||
- Background fetch
|
||
- Background processing (if using `BGProcessingTask`)
|
||
- Info.plist has:
|
||
- `BGTaskSchedulerPermittedIdentifiers` array with task identifier: `com.timesafari.dailynotification.fetch`
|
||
- Plugin exposes:
|
||
- `scheduleDailyNotification()` method that schedules both prefetch and notification
|
||
|
||
### Architecture at a Glance
|
||
|
||
**Flow Overview:**
|
||
```
|
||
JS/HTML Test App → Capacitor Bridge → DailyNotificationPlugin (iOS)
|
||
↓
|
||
BGTaskScheduler (BGAppRefreshTask)
|
||
↓
|
||
HTTP API (JWT, ETag, etc.)
|
||
↓
|
||
Cache/DB (UserDefaults/CoreData)
|
||
↓
|
||
Notification Delivery
|
||
(uses cached content if valid,
|
||
falls back to live fetch)
|
||
```
|
||
|
||
**Key Components:**
|
||
- **Test App:** Provides UI to trigger plugin methods (see `IOS_TEST_APP_REQUIREMENTS.md`)
|
||
- **Plugin:** Handles scheduling, fetching, caching, notification delivery
|
||
- **BGTaskScheduler:** iOS background execution for prefetch
|
||
- **Storage:** Persists schedules and cached content
|
||
- **Notification System:** Delivers notifications using prefetched or live content
|
||
|
||
**Verification Points:**
|
||
- BGTask registered at app startup
|
||
- BGTask scheduled 5 minutes before notification time
|
||
- BGTask executes and fetches content
|
||
- Content persisted to cache/DB
|
||
- Notification delivery uses cached content
|
||
|
||
---
|
||
|
||
## Simulator Test Plan (Logic-Correctness)
|
||
|
||
**Objective:** Confirm that when a background task fires, your prefetch code runs end-to-end.
|
||
|
||
### 1. Harden Logging (One-Time Prep)
|
||
|
||
Add structured logs at key points:
|
||
|
||
**On app startup:**
|
||
```
|
||
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
|
||
```
|
||
|
||
**When scheduling:**
|
||
```
|
||
[DNP-FETCH] Scheduling BGAppRefreshTask (earliestBeginDate=2025-11-14T05:48:00Z)
|
||
[DNP-FETCH] Prefetch scheduled 5 minutes before notification (notificationTime=2025-11-14T05:53:00Z)
|
||
```
|
||
|
||
**When BGTask handler fires:**
|
||
```
|
||
[DNP-FETCH] BGTask handler invoked (task.identifier=com.timesafari.dailynotification.fetch)
|
||
```
|
||
|
||
**Inside prefetch logic:**
|
||
```
|
||
[DNP-FETCH] Starting fetch (notificationTime=2025-11-14T05:53:00Z, scheduleId=...)
|
||
[DNP-FETCH] Fetch success: status=200, items=1, ttl=86400
|
||
[DNP-FETCH] Fetch failed: error=..., willRetry=false
|
||
```
|
||
|
||
**When integrating with notifications:**
|
||
```
|
||
[DNP-FETCH] Using cached content for notification at 2025-11-14T05:53:00Z
|
||
[DNP-FETCH] No cached content, falling back to on-demand fetch
|
||
```
|
||
|
||
### 2. Start App in Simulator
|
||
|
||
1. Clean build, run on iPhone 15 simulator (or similar)
|
||
2. Verify console logs:
|
||
- ✅ `[DNP-FETCH] Registering BGTaskScheduler task...` at startup
|
||
|
||
### 3. Schedule a Notification with Prefetch
|
||
|
||
1. Call plugin's `scheduleDailyNotification()` from JS layer
|
||
2. Check logs:
|
||
- ✅ `[DNP-FETCH] Scheduling BGAppRefreshTask...` with sensible `earliestBeginDate`
|
||
- ✅ `[DNP-SCHEDULER] Scheduling notification for 2025-11-14T05:53:00Z`
|
||
- ✅ Any DB/state writes for the schedule
|
||
|
||
**Note:** For a full happy-path log example, see `IOS_TEST_APP_REQUIREMENTS.md – Testing Scenarios → Basic Functionality`.
|
||
|
||
### 4. Simulate Background Fetch
|
||
|
||
With app running:
|
||
|
||
1. **Background the app** (Home button / Cmd+Shift+H)
|
||
2. In Xcode menu:
|
||
- **Debug → Simulate Background Fetch** or
|
||
- **Debug → Simulate Background Refresh**
|
||
|
||
**Expected logs (in order):**
|
||
```
|
||
[DNP-FETCH] BGTask handler invoked (task.identifier=...)
|
||
[DNP-FETCH] Starting fetch (notificationTime=..., scheduleId=...)
|
||
[DNP-FETCH] API call: GET /api/notifications (JWT present, ETag=...)
|
||
[DNP-FETCH] Fetch success: status=200, bytes=1234, ttl=86400
|
||
[DNP-FETCH] Cached content for scheduleId=...
|
||
[DNP-FETCH] Task completed (success=true)
|
||
```
|
||
|
||
**See Sample Prefetch Response & Mapping below for expected API response structure.**
|
||
|
||
### 5. Trigger or Wait for Notification
|
||
|
||
**Option A: Wait for scheduled time**
|
||
- Wait until scheduled time
|
||
- Look for:
|
||
- `[DNP-FETCH] Using cached content for notification at ...`
|
||
- `[DNP-SCHEDULER] Notification delivered (id=..., title=...)`
|
||
|
||
**Option B: Manual trigger**
|
||
- Use test app UI to trigger notification test path
|
||
- Confirm notification shows content derived from prefetch, not fallback
|
||
|
||
### 6. Sample Prefetch Response & Mapping
|
||
|
||
**Example API Response:**
|
||
|
||
When prefetch succeeds, the API returns JSON like:
|
||
|
||
```json
|
||
{
|
||
"id": "notif-2025-11-15",
|
||
"title": "Your daily update",
|
||
"body": "Here is today's summary…",
|
||
"ttl": 86400,
|
||
"scheduled_for": "2025-11-15T05:53:00Z"
|
||
}
|
||
```
|
||
|
||
**Response Mapping:**
|
||
|
||
- `id` → Used for notification identification and deduplication
|
||
- `title` → Notification title (displayed to user)
|
||
- `body` → Notification body (displayed to user)
|
||
- `ttl` → Cache validity check at delivery time (seconds)
|
||
- `scheduled_for` → Cross-check with `notificationTime` to ensure alignment
|
||
|
||
**Log Verification:**
|
||
|
||
When you see `[DNP-FETCH] Fetch success (status=200, bytes=1234, ttl=86400)`, the `ttl=86400` should match the `ttl` field in the JSON response. The `scheduled_for` timestamp should match the `notificationTime` used in scheduling.
|
||
|
||
### 7. Negative-Path Tests
|
||
|
||
**Network failure:**
|
||
1. Turn off network in Simulator (Settings → Network)
|
||
2. Trigger background fetch
|
||
3. Expect:
|
||
- `[DNP-FETCH] Fetch failed: error=NetworkError, willRetry=false`
|
||
- Then either:
|
||
- Cached content used if available, or
|
||
- Fallback path logged: `[DNP-FETCH] No cached content, falling back to on-demand fetch`
|
||
|
||
**Permission denied:**
|
||
1. Deny notification permissions
|
||
2. Attempt to schedule
|
||
3. Expect:
|
||
- `[DNP-PLUGIN] notifications_denied for scheduleDailyNotification`
|
||
- Error returned: `{ error: "notifications_denied", ... }`
|
||
|
||
---
|
||
|
||
## Known OS Limitations
|
||
|
||
**Critical:** These are iOS system limitations, not plugin bugs. If these conditions occur, it is **not a plugin bug**; confirm logs and document behavior.
|
||
|
||
**BGTaskScheduler Simulator Limitation:**
|
||
- **BGTaskSchedulerErrorDomain Code=1 (notPermitted) is EXPECTED on simulator**
|
||
- BGTaskScheduler doesn't work reliably on iOS Simulator - this is a known iOS limitation
|
||
- Background fetch scheduling will fail on simulator with Code=1 error
|
||
- **This is NOT a plugin bug** - notification scheduling still works correctly
|
||
- Prefetch won't run on simulator, but will work on real devices with Background App Refresh enabled
|
||
- Error handling logs: "Background fetch scheduling failed (expected on simulator)"
|
||
- **Testing:** Use Xcode → Debug → Simulate Background Fetch for simulator testing
|
||
- **See also:** `doc/directives/0003-iOS-Android-Parity-Directive.md` for implementation details
|
||
|
||
**iOS may NOT run BGTasks if:**
|
||
- App has been force-quit by the user (iOS won't run BGTask for force-quit apps)
|
||
- Background App Refresh is disabled in Settings → [Your App]
|
||
- Device is in Low Power Mode and idle
|
||
- Device battery is critically low
|
||
- **On Simulator:** BGTaskScheduler generally doesn't work (Code=1 error is expected)
|
||
|
||
**iOS timing heuristics:**
|
||
- iOS may delay or batch tasks; prefetch might run **much later** than `earliestBeginDate` (up to 15+ minutes)
|
||
- iOS adapts BGTask frequency based on user behavior, app usage, and energy constraints
|
||
- Notification delivery can drift by ±180 seconds (this is expected, not a bug)
|
||
|
||
**What to do:**
|
||
- Check logs to confirm plugin logic executed correctly
|
||
- Document the OS-imposed delay in test results
|
||
- Verify fallback mechanisms work when prefetch is delayed
|
||
|
||
---
|
||
|
||
## Real Device Test Plan (Timing & Reliability)
|
||
|
||
**Objective:** Confirm that **prefetch happens near the intended time** (e.g., 5 minutes before) in realistic conditions.
|
||
|
||
### 1. Install Dev Build on iPhone
|
||
|
||
- Same BGTask + background modes config as simulator
|
||
- Confirm logs visible via:
|
||
- Xcode → Devices & Simulators → device → open console, or
|
||
- `os_log` aggregator / remote logging
|
||
|
||
### 2. Baseline Run
|
||
|
||
1. Open app
|
||
2. Schedule a notification for **30–60 minutes in the future** with prefetch
|
||
3. Lock the phone and leave it idle, **plugged in** (best-case for iOS)
|
||
|
||
### 3. Monitor Logs
|
||
|
||
Look for:
|
||
|
||
- BGTask scheduled with appropriate `earliestBeginDate`
|
||
- Some time before the notification:
|
||
- BGTask handler invoked
|
||
- Prefetch fetch executes
|
||
- At notification fire:
|
||
- Notification uses prefetched data
|
||
|
||
### 4. Variations
|
||
|
||
Test different conditions:
|
||
|
||
- **Device on battery** vs plugged in
|
||
- **App force-quit** vs just backgrounded
|
||
- **Multiple days in a row**: iOS will adapt its heuristics; see if prefetch becomes more or less reliable
|
||
- **Low Power Mode**: May delay/disable background tasks
|
||
- **Background App Refresh disabled**: Should fail gracefully
|
||
|
||
### 5. Success Criteria
|
||
|
||
In "good" conditions (plugged in, WiFi, not force-quit):
|
||
|
||
- ✅ Prefetch runs at least once before the notification in the majority of tests
|
||
- ✅ Failure modes are logged clearly:
|
||
- So you know when **timing failed because of iOS**, not your code
|
||
- ✅ **For at least one full test cycle, logs and telemetry counts confirm that the sequence: scheduled → executed → success → used is coherent**
|
||
|
||
**Acceptable outcomes:**
|
||
- Prefetch runs within 15 minutes of `earliestBeginDate` (iOS heuristic window)
|
||
- If prefetch misses, fallback to on-demand fetch works
|
||
- All failures are logged with clear reasons
|
||
|
||
### Test Run Result Template
|
||
|
||
Use this template when recording test runs (especially on real devices):
|
||
|
||
```text
|
||
Date:
|
||
Device model:
|
||
iOS version:
|
||
App build / commit:
|
||
Lead time (T-Lead - see glossary):
|
||
Scenario ID(s) tested:
|
||
Conditions: (plugged vs battery, Wi-Fi vs LTE, Low Power Mode on/off, Background App Refresh on/off)
|
||
Outcome summary:
|
||
- Prefetch scheduled at:
|
||
- BGTask executed at:
|
||
- Notification fired at:
|
||
- Cached content used: yes/no
|
||
Failures observed (if any) and key log lines:
|
||
Notes / follow-ups:
|
||
```
|
||
|
||
---
|
||
|
||
## Log Checklist
|
||
|
||
When everything is wired correctly, one full cycle should produce:
|
||
|
||
### 1. App Launch
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
|
||
[DNP-PLUGIN] Startup complete (hasPendingSchedules=true|false)
|
||
```
|
||
|
||
**If you see registration but not startup:**
|
||
- Check plugin initialization in AppDelegate
|
||
- Verify Capacitor plugin registration
|
||
- Check for initialization errors in logs
|
||
|
||
**If you don't see registration:**
|
||
- Check Info.plist has `BGTaskSchedulerPermittedIdentifiers`
|
||
- Verify task registered in AppDelegate before app finishes launching
|
||
- Check for registration errors in logs
|
||
|
||
### 2. Notification Scheduled (from JS / plugin)
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] Scheduling prefetch for notification at 2025-11-14T05:53:00Z
|
||
[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=2025-11-14T05:48:00Z)
|
||
[DNP-SCHEDULER] Scheduling notification for 2025-11-14T05:53:00Z
|
||
[DNP-STORAGE] Persisted schedule to DB (id=..., type=DAILY, ...)
|
||
```
|
||
|
||
**Note:** For a full happy-path log example, see `IOS_TEST_APP_REQUIREMENTS.md – Testing Scenarios → Basic Functionality`.
|
||
|
||
**If you see scheduling but not BGTask scheduled:**
|
||
- Check BGTaskScheduler submission succeeded
|
||
- Verify `earliestBeginDate` is at least 1 minute in future
|
||
- Check for BGTaskScheduler errors in logs
|
||
- **On Simulator:** BGTaskSchedulerErrorDomain Code=1 (notPermitted) is expected - see "Known OS Limitations"
|
||
|
||
**If you see BGTask scheduled but not persisted:**
|
||
- Check database write operations
|
||
- Verify storage layer is initialized
|
||
- Check for storage errors in logs
|
||
|
||
### 3. BGTask Fired (simulator or device)
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] BGTask handler invoked (id=com.timesafari.dailynotification.fetch)
|
||
[DNP-FETCH] Resolved next notification needing content (time=..., scheduleId=...)
|
||
[DNP-FETCH] Starting fetch from <URL> (notificationTime=..., jwtPresent=true)
|
||
[DNP-FETCH] Fetch success (status=200, bytes=1234, ttl=86400)
|
||
[DNP-FETCH] Cached content for scheduleId=...
|
||
[DNP-FETCH] Task completed (success=true)
|
||
```
|
||
|
||
**If you see "Registering BGTaskScheduler task" but never see "BGTask handler invoked" on device:**
|
||
- Check Background App Refresh enabled in Settings → [Your App]
|
||
- Verify app not force-quit (iOS won't run BGTask for force-quit apps)
|
||
- Check Info.plist identifiers match code exactly (case-sensitive)
|
||
- Verify task was actually scheduled (check `earliestBeginDate` in logs)
|
||
- On Simulator: Use Xcode → Debug → Simulate Background Fetch
|
||
|
||
**If you see handler invoked but not fetch starting:**
|
||
- Check notification resolution logic
|
||
- Verify schedule exists in database
|
||
- Check for resolution errors in logs
|
||
|
||
**If you see fetch starting but not success:**
|
||
- Check network connectivity
|
||
- Verify API endpoint is accessible
|
||
- Check JWT/authentication
|
||
- Look for HTTP error codes in logs
|
||
|
||
### 4. Notification Delivery
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-SCHEDULER] Preparing content for notification at 2025-11-14T05:53:00Z
|
||
[DNP-FETCH] Found cached content (age=300s, source=prefetch)
|
||
[DNP-SCHEDULER] Notification built (title=..., body=..., id=...)
|
||
[DNP-SCHEDULER] Notification scheduled/delivered
|
||
```
|
||
|
||
**If you see "Preparing content" but not "Found cached content":**
|
||
- Check if prefetch actually ran (see BGTask logs)
|
||
- Verify cache/DB read operations
|
||
- Check TTL validation (content may have expired)
|
||
- Look for fallback to on-demand fetch
|
||
|
||
**If you see "Found cached content" but not "Notification built":**
|
||
- Check notification building logic
|
||
- Verify notification permissions granted
|
||
- Check for notification construction errors
|
||
|
||
### 5. Failure Cases
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] Fetch failed (error=NetworkError, httpStatus=0, willRetry=false)
|
||
[DNP-FETCH] No cached content available, falling back to on-demand fetch
|
||
[DNP-FETCH] Task completed (success=false, reason=NETWORK)
|
||
```
|
||
|
||
**If you see fetch failures repeatedly:**
|
||
- Check network connectivity
|
||
- Verify API endpoint is correct
|
||
- Check authentication (JWT) is valid
|
||
- Look for specific error reasons in logs (NETWORK, AUTH, SYSTEM)
|
||
|
||
**If you see "No cached content" but prefetch should have run:**
|
||
- Check if BGTask actually executed (see BGTask logs)
|
||
- Verify cache/DB write operations succeeded
|
||
- Check for cache expiration (TTL may have passed)
|
||
- Verify fallback to on-demand fetch works
|
||
|
||
**If you can walk through a log and trace all steps like this, you're in good shape.**
|
||
|
||
---
|
||
|
||
## Behavior Classification
|
||
|
||
Testable matrix of deterministic vs heuristic behavior:
|
||
|
||
| Bucket | Component / Method | Deterministic? | Test on | Notes |
|
||
|--------|-------------------|----------------|---------|-------|
|
||
| A | `BGTaskScheduler.shared.register` | Yes | Sim + Dev | Registration must always log & succeed/fail deterministically |
|
||
| A | `configure()`, `getLastNotification()`, `cancelAllNotifications()`, `getNotificationStatus()`, `updateSettings()` | Yes | Sim + Dev | Logic & I/O-only, no timing dependencies |
|
||
| A | `checkPermissionStatus()`, `requestNotificationPermissions()` | Yes | Sim + Dev | Permission state reading and requests, deterministic |
|
||
| A | `isChannelEnabled(channelId?)`, `openChannelSettings(channelId?)` | Yes | Sim + Dev | Channel status and settings (iOS: app-wide, not per-channel) |
|
||
| A | `getBatteryStatus()`, `getPowerState()`, `getRollingWindowStats()` | Yes | Sim + Dev | State reading, deterministic |
|
||
| A | `testJWTGeneration()`, `testEndorserAPI()` | Yes | Sim + Dev | API call logic, deterministic |
|
||
| A | Fetch function logic (HTTP calls, DB writes, JSON parsing) | Yes | Sim + Dev | Code path is deterministic |
|
||
| A | Error handling, retry logic, fallbacks | Yes | Sim + Dev | Logic is deterministic |
|
||
| A | Log emission / structured logging | Yes | Sim + Dev | Logging is deterministic |
|
||
| A | Notification building logic (given data) | Yes | Sim + Dev | Title/body/payload construction is deterministic |
|
||
| A | TTL validation at delivery time | Yes | Sim + Dev | Validation logic is deterministic |
|
||
| B | `scheduleDailyNotification()` | Logic yes, timing no | Sim + Dev | Use logs to verify scheduling but not run time |
|
||
| B | `maintainRollingWindow()` | Logic yes, timing no | Sim + Dev | Logic deterministic, but when iOS allows execution is heuristic |
|
||
| B | BGTaskScheduler scheduling (`earliestBeginDate`) | Logic yes, timing no | Sim + Dev | `earliestBeginDate` is a hint, not a guarantee |
|
||
| B | Notification trigger + prefetch relationship | Logic yes, timing no | Sim + Dev | "If cached content exists, use it" is deterministic; whether prefetch ran in time is heuristic |
|
||
| C | Time between `earliestBeginDate` and task execution | No | Device | Purely heuristic, iOS controls when tasks run |
|
||
| C | Background task execution timing (BGTaskScheduler) | No | Device | iOS heuristics control execution timing |
|
||
| C | Notification delivery timing (UNUserNotificationCenter) | No | Device | iOS controls delivery timing (±180s tolerance) |
|
||
| C | Reboot recovery detection (uptime comparison) | No | Device | May vary based on system state |
|
||
|
||
**Bucket Summary:**
|
||
- **🟢 Bucket A (Deterministic):** Test in Simulator and Device - Logic correctness
|
||
- **🟡 Bucket B (Partially Deterministic):** Test flow in Simulator, timing on Device
|
||
- **🔴 Bucket C (Heuristic):** Test on Real Device only - Timing and reliability
|
||
|
||
---
|
||
|
||
## Test Harness Reference
|
||
|
||
**File:** `ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift`
|
||
|
||
**Purpose:** Use this harness if the plugin code is broken or being refactored; it isolates BGTask behavior from plugin wiring.
|
||
|
||
**What it demonstrates:**
|
||
- Task registration
|
||
- Task scheduling
|
||
- Task handler implementation
|
||
- Expiration handling
|
||
- Completion reporting
|
||
|
||
**Usage:**
|
||
- Reference implementation when building actual prefetch logic
|
||
- Standalone testing of BGTaskScheduler behavior
|
||
- Debugging BGTask registration/scheduling issues
|
||
- See file for detailed usage examples and testing instructions
|
||
|
||
---
|
||
|
||
## Integration with Main Directive
|
||
|
||
This testing guide supports:
|
||
|
||
- **Phase 1:** Core prefetch functionality testing
|
||
- **Phase 2:** Advanced features testing (rolling window, TTL)
|
||
- **Phase 3:** TimeSafari integration testing (JWT, ETag)
|
||
- **Validation Matrix:** Cross-platform feature validation
|
||
|
||
## Telemetry and Monitoring
|
||
|
||
**Expected Counters (even if not yet implemented):**
|
||
|
||
Define counters you expect the runtime to emit:
|
||
|
||
- `dnp_prefetch_scheduled_total` - Total prefetch tasks scheduled
|
||
- `dnp_prefetch_executed_total` - Total prefetch tasks executed
|
||
- `dnp_prefetch_success_total` - Total successful prefetch executions
|
||
- `dnp_prefetch_failure_total{reason="NETWORK|AUTH|SYSTEM"}` - Total failed prefetch executions by reason
|
||
- `dnp_prefetch_used_for_notification_total` - Total notifications using prefetched content
|
||
|
||
**Telemetry Pipeline:**
|
||
|
||
These counters MUST be emitted via the same pipeline as Android (e.g., structlog → rsyslog → Prometheus/Influx/Loki). If telemetry is not yet wired on iOS, mark tests that rely on counters as **P2** and fall back to log inspection for Phase 1.
|
||
|
||
**Success Criteria:**
|
||
- For at least one full test cycle, logs and telemetry counts confirm that the sequence: scheduled → executed → success → used is coherent
|
||
- Counters increment as expected through the prefetch lifecycle
|
||
- Failure counters provide clear reason categorization
|
||
|
||
---
|
||
|
||
## Test Campaign Sign-Off Checklist (Phase 1)
|
||
|
||
**Use this checklist to verify Phase 1 prefetch testing is complete:**
|
||
|
||
- [ ] Simulator: one full happy-path cycle verified (Schedule → BGTask → Fetch → Delivery)
|
||
- [ ] Device: at least one "best-case" run (plugged in, Wi-Fi, not force-quit) where prefetch runs before notification and cached content is used
|
||
- [ ] At least one failure path logged (NETWORK or PERMISSIONS) and handled gracefully
|
||
- [ ] Time & T-Lead edge-case tests run (too-small lead, timezone change) or explicitly deferred
|
||
- [ ] Telemetry counters either wired and validated, or marked **P2** with log-based fallback documented
|
||
|
||
**See also:**
|
||
- `doc/directives/0003-iOS-Android-Parity-Directive.md` - Main implementation directive
|
||
- `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md` - Test app setup requirements
|
||
|
||
---
|
||
|
||
## Glossary
|
||
|
||
**BGTaskScheduler** – iOS framework for scheduling background tasks (BGAppRefreshTask / BGProcessingTask). Provides heuristic-based background execution, not exact timing guarantees.
|
||
|
||
**UNUserNotificationCenter** – iOS notification framework for scheduling and delivering user notifications. Handles permission requests and notification delivery.
|
||
|
||
**T-Lead** – The lead time between prefetch and notification fire, e.g., 5 minutes. Prefetch is scheduled at `notificationTime - T-Lead`.
|
||
|
||
**Bucket A/B/C** – Deterministic vs heuristic classification used in Behavior Classification:
|
||
- **Bucket A (Deterministic):** Test in Simulator and Device - Logic correctness
|
||
- **Bucket B (Partially Deterministic):** Test flow in Simulator, timing on Device
|
||
- **Bucket C (Heuristic):** Test on Real Device only - Timing and reliability
|
||
|
||
**UTC** – Coordinated Universal Time. All internal timestamps are stored in UTC to avoid DST and timezone issues.
|
||
|
||
**earliestBeginDate** – The earliest time iOS may execute a BGTask. This is a hint, not a guarantee; iOS may run the task later based on heuristics.
|
||
|
||
---
|
||
|
||
**Status:** 🎯 **READY FOR USE**
|
||
**Next Steps:** Use this guide when implementing and testing Phase 1+ prefetch functionality
|
||
|
||
---
|
||
|
||
## Changelog (high-level)
|
||
|
||
- 2025-11-15 — Initial Phase 1 version (prefetch MVP, Android parity)
|
||
|