feat(ios): complete P2.1 schema versioning and P2.2 combined edge case tests

P2.1: iOS Schema Versioning Strategy
- Added SCHEMA_VERSION constant and checkSchemaVersion() method in PersistenceController
- Version stored in NSPersistentStore metadata (observability contract, not migration gate)
- CoreData auto-migration remains authoritative; version mismatches logged, not blocked
- Documentation added to ios/Plugin/README.md with migration contract

P2.2: Combined Edge Case Tests
- Added 3 resilience test scenarios to DailyNotificationRecoveryTests.swift:
  - test_combined_dst_boundary_duplicate_delivery_cold_start()
  - test_combined_rollover_duplicate_delivery_cold_start()
  - test_combined_schema_version_cold_start_recovery()
- All tests labeled with @resilience @combined-scenarios comments
- Tests verify idempotency and correctness under combined stressors

P2.3: Android Combined Tests Design
- Created P2.3-DESIGN.md with scope, invariants, and acceptance criteria
- Created P2.3-IMPLEMENTATION-CHECKLIST.md with step-by-step execution plan
- Design ready for implementation to achieve parity with iOS P2.2

Documentation Updates
- Fixed parity matrix: iOS invalid data handling now correctly shows " Recovery tested" with test references
- Updated progress docs (00-STATUS.md, 01-CHANGELOG-WORK.md, 03-TEST-RUNS.md, 04-PARITY-MATRIX.md)
- Updated P2-DESIGN.md to reflect P2.3 scope (Android combined tests)
- Updated SYSTEM_INVARIANTS.md baseline tag references

Baseline Tag
- Created and pushed v1.0.11-p2-complete tag
- Tag represents P2.x completion (schema versioning + combined resilience tests)

All invariants preserved. CI passes. Tests runnable via xcodebuild on macOS.
This commit is contained in:
Matthew Raymer
2025-12-22 12:59:40 +00:00
parent eb1fc9f220
commit 6b5b886951
16 changed files with 2131 additions and 72 deletions

View File

@@ -0,0 +1,421 @@
# P2.3 Implementation Checklist: Android Combined Edge Case Tests
**Purpose:** Step-by-step implementation guide for P2.3, breaking down the design into actionable tasks with acceptance criteria and rollback guidance.
**Owner:** Development Team
**Last Updated:** 2025-12-22
**Status:** implementation-ready
**Baseline:** `v1.0.11-p2-complete`
**Design Reference:** `docs/progress/P2.3-DESIGN.md`
---
## Overview
**Goal:** Achieve parity with iOS P2.2 by adding automated combined edge case tests for Android.
**Scope:**
- Enable Android test infrastructure (currently disabled)
- Create test helpers (in-memory Room database, test data injection)
- Implement 2-3 combined test scenarios mirroring iOS P2.2
**Estimated Time:** 12-20 hours (can be spread over multiple sessions)
---
## Pre-Implementation Checklist
Before starting, verify:
- [ ] Baseline tag exists: `v1.0.11-p2-complete`
- [ ] CI is green: `./ci/run.sh` passes
- [ ] P2.3 design reviewed and approved
- [ ] Test framework choice decided (Robolectric vs pure unit tests)
- [ ] Android test directory structure understood
---
## P2.3.1: Enable Android Test Infrastructure
**Goal:** Re-enable Android tests with modern AndroidX testing framework.
**Estimated Time:** 2-4 hours
### Step 1.1: Review Current Test Configuration
**Action:**
- Read `android/build.gradle` lines 48-63 (test configuration)
- Understand why tests are disabled (comment: "tests reference deprecated/removed code")
- Identify what needs to be updated
**Acceptance:**
- [ ] Current test configuration understood
- [ ] Deprecated API usage identified (if any)
---
### Step 1.2: Add AndroidX Test Dependencies
**Action:**
- Add test dependencies to `android/build.gradle`:
- `junit:junit:4.13.2` (or latest)
- `androidx.test:core:1.5.0` (or latest)
- `androidx.test.ext:junit:1.1.5` (or latest)
- `org.robolectric:robolectric:4.11.1` (if using Robolectric)
- `androidx.room:room-testing:2.6.1` (for in-memory Room testing)
**File:** `android/build.gradle`
**Acceptance:**
- [ ] Test dependencies added to `dependencies {}` block
- [ ] Versions compatible with existing AndroidX versions
- [ ] No version conflicts
---
### Step 1.3: Update Test Configuration
**Action:**
- Remove or update `testOptions { unitTests.all { enabled = false } }`
- Remove or update `sourceSets { test { java { srcDirs = [] } } }`
- Enable unit tests: `testOptions { unitTests.includeAndroidResources = true }` (if using Robolectric)
**File:** `android/build.gradle`
**Acceptance:**
- [ ] Test configuration updated
- [ ] Tests are enabled (not disabled)
- [ ] Test source directory is accessible
---
### Step 1.4: Create Test Directory Structure
**Action:**
- Create `android/src/test/java/com/timesafari/dailynotification/` if it doesn't exist
- Create placeholder test file: `DailyNotificationRecoveryTests.kt` (or `.java`)
- Add minimal test to verify infrastructure works
**Example placeholder test:**
```kotlin
package com.timesafari.dailynotification
import org.junit.Test
import org.junit.Assert.*
class DailyNotificationRecoveryTests {
@Test
fun test_infrastructure_works() {
assertTrue("Test infrastructure is working", true)
}
}
```
**Acceptance:**
- [ ] Test directory structure created
- [ ] Placeholder test file created
- [ ] Test compiles without errors
---
### Step 1.5: Verify Test Infrastructure
**Action:**
- Run `cd android && ./gradlew test` (or `./gradlew :android:test` from root)
- Verify test runs successfully
- Check CI compatibility: ensure `./ci/run.sh` can run tests or skip gracefully
**Acceptance:**
- [ ] `./gradlew test` runs successfully
- [ ] Placeholder test passes
- [ ] CI compatibility verified (tests run in CI or documented as manual)
**Rollback:** If tests fail to compile/run, revert `android/build.gradle` changes and investigate dependency conflicts.
---
## P2.3.2: Create Test Infrastructure Helpers
**Goal:** Create test helpers similar to iOS `TestDBFactory.swift`.
**Estimated Time:** 4-6 hours
### Step 2.1: Create In-Memory Room Database Factory
**Action:**
- Create `android/src/test/java/com/timesafari/dailynotification/TestDBFactory.kt` (or `.java`)
- Implement factory method that creates in-memory Room database
- Use `Room.inMemoryDatabaseBuilder()` for isolation
**Example structure:**
```kotlin
package com.timesafari.dailynotification
import androidx.room.Room
import androidx.room.RoomDatabase
object TestDBFactory {
fun createInMemoryDatabase(context: Context): DailyNotificationDatabase {
return Room.inMemoryDatabaseBuilder(
context,
DailyNotificationDatabase::class.java
).allowMainThreadQueries()
.build()
}
}
```
**Acceptance:**
- [ ] `TestDBFactory` created
- [ ] Factory method creates in-memory database
- [ ] Database is isolated (each test gets fresh instance)
---
### Step 2.2: Create Test Data Injection Helpers
**Action:**
- Add helper methods to `TestDBFactory` (or separate `TestDataHelper`):
- `injectInvalidSchedule()` - creates schedule with empty ID or null fields
- `injectDuplicateSchedule()` - creates duplicate schedule entries
- `injectPastSchedule()` - creates schedule with past `nextRunAt`
- `injectDSTBoundarySchedule()` - creates schedule at DST boundary
**Acceptance:**
- [ ] Test data injection helpers created
- [ ] Helpers support invalid data scenarios
- [ ] Helpers support duplicate delivery scenarios
- [ ] Helpers support DST boundary scenarios
---
### Step 2.3: Create Mock Context Helper (if needed)
**Action:**
- If using Robolectric, create mock context helper
- If using pure unit tests, create minimal context mock
- Ensure context provides necessary services (SharedPreferences, etc.)
**Acceptance:**
- [ ] Mock context helper created (if needed)
- [ ] Context provides necessary services
- [ ] Context is isolated per test
---
### Step 2.4: Verify Test Helpers Work
**Action:**
- Create simple test that uses `TestDBFactory` and data injection helpers
- Verify database creation works
- Verify data injection works
- Verify database cleanup works (teardown)
**Acceptance:**
- [ ] Test helpers work in isolation
- [ ] Database creation verified
- [ ] Data injection verified
- [ ] Cleanup verified
**Rollback:** If helpers don't work, investigate Room in-memory database setup or mock context issues.
---
## P2.3.3: Implement Combined Test Scenarios
**Goal:** Add 2-3 combined edge case tests mirroring iOS P2.2.
**Estimated Time:** 6-10 hours
### Step 3.1: Implement Scenario A - DST Boundary + Duplicate Delivery + Cold Start
**Action:**
- Create test: `test_combined_dst_boundary_duplicate_delivery_cold_start()`
- Test steps:
1. Create notification scheduled at DST boundary (use `ZonedDateTime` with DST transition)
2. Simulate duplicate delivery events (rapid succession - call delivery handler twice)
3. Trigger cold start recovery (call `ReactivationManager.performRecovery()`)
4. Verify: idempotency (running twice yields identical state)
5. Verify: deduplication (only one logical delivery recorded)
6. Verify: DST-consistent next time (next scheduled time accounts for DST)
**File:** `android/src/test/java/com/timesafari/dailynotification/DailyNotificationRecoveryTests.kt`
**Acceptance:**
- [ ] Test created with `@Test` annotation
- [ ] Test labeled with `@resilience @combined-scenarios` comment
- [ ] Test verifies idempotency
- [ ] Test verifies deduplication
- [ ] Test verifies DST-consistent next time
- [ ] Test passes deterministically
**Reference:** iOS equivalent: `test_combined_dst_boundary_duplicate_delivery_cold_start()` in `ios/Tests/DailyNotificationRecoveryTests.swift`
---
### Step 3.2: Implement Scenario B - Rollover + Duplicate Delivery + Cold Start
**Action:**
- Create test: `test_combined_rollover_duplicate_delivery_cold_start()`
- Test steps:
1. Create notification that was just delivered (past time)
2. Trigger rollover (first delivery - mark as delivered, schedule next)
3. Simulate duplicate delivery immediately (call delivery handler again)
4. Trigger cold start recovery
5. Verify: rollover idempotency (no double-apply of state transitions)
6. Verify: duplicate delivery doesn't double-apply state transitions
7. Verify: cold start reconciliation produces correct state
**File:** `android/src/test/java/com/timesafari/dailynotification/DailyNotificationRecoveryTests.kt`
**Acceptance:**
- [ ] Test created with `@Test` annotation
- [ ] Test labeled with `@resilience @combined-scenarios` comment
- [ ] Test verifies rollover idempotency
- [ ] Test verifies duplicate delivery handling
- [ ] Test verifies cold start reconciliation
- [ ] Test passes deterministically
**Reference:** iOS equivalent: `test_combined_rollover_duplicate_delivery_cold_start()` in `ios/Tests/DailyNotificationRecoveryTests.swift`
---
### Step 3.3: Implement Scenario C - Schema Version + Cold Start Recovery (Optional)
**Action:**
- Create test: `test_combined_schema_version_cold_start_recovery()` (if time permits)
- Test steps:
1. Verify Room database version is observable (check `Database.getVersion()`)
2. Test recovery with version metadata present
3. Verify version doesn't interfere with recovery
4. Verify recovery works identically with version metadata
**File:** `android/src/test/java/com/timesafari/dailynotification/DailyNotificationRecoveryTests.kt`
**Acceptance:**
- [ ] Test created with `@Test` annotation (optional)
- [ ] Test labeled with `@resilience @combined-scenarios` comment
- [ ] Test verifies schema version observability
- [ ] Test verifies version doesn't interfere with recovery
- [ ] Test passes deterministically
**Reference:** iOS equivalent: `test_combined_schema_version_cold_start_recovery()` in `ios/Tests/DailyNotificationRecoveryTests.swift`
---
### Step 3.4: Verify All Tests Pass
**Action:**
- Run `./gradlew test` to verify all new tests pass
- Run tests multiple times to verify determinism
- Check for flaky tests and fix if needed
**Acceptance:**
- [ ] All new tests pass
- [ ] Tests are deterministic (run multiple times, same results)
- [ ] No flaky tests
---
### Step 3.5: Update Documentation
**Action:**
- Update `docs/progress/03-TEST-RUNS.md` with P2.3 test run entry
- Update `docs/progress/04-PARITY-MATRIX.md` to mark "Combined edge case tests" as ✅ for Android
- Add direct test references (file path + test names) to parity matrix
- Update `docs/progress/01-CHANGELOG-WORK.md` with P2.3 completion entry
- Update `docs/progress/00-STATUS.md` to mark P2.3 complete
**Acceptance:**
- [ ] Test run entry added to `03-TEST-RUNS.md`
- [ ] Parity matrix updated with ✅ and direct test references
- [ ] Changelog entry added
- [ ] Status doc updated
---
## Post-Implementation Verification
### CI Verification
**Action:**
- Run `./ci/run.sh` to verify all checks pass
- Verify Android tests run in CI (or are documented as manual)
- Check for any new lint/build errors
**Acceptance:**
- [ ] `./ci/run.sh` passes
- [ ] Android tests run in CI (or documented as manual)
- [ ] No new lint/build errors
---
### Parity Verification
**Action:**
- Compare Android combined tests with iOS P2.2 tests
- Verify test scenarios are equivalent in intent (not necessarily identical mechanics)
- Verify parity matrix reflects accurate status
**Acceptance:**
- [ ] Android tests mirror iOS intent
- [ ] Parity matrix accurately reflects status
- [ ] Test references are direct and traceable
---
## Rollback Plan
If P2.3 implementation encounters issues:
### Rollback P2.3.3 (Test Scenarios)
**Action:**
- Remove test scenario files
- Revert documentation updates
- Keep test infrastructure (P2.3.1, P2.3.2) for future use
### Rollback P2.3.2 (Test Helpers)
**Action:**
- Remove test helper files
- Revert to minimal test infrastructure
### Rollback P2.3.1 (Test Infrastructure)
**Action:**
- Revert `android/build.gradle` changes
- Disable tests again (restore original configuration)
- Remove test directory if created
**Full Rollback:**
- Revert all P2.3 changes
- Restore baseline: `git checkout v1.0.11-p2-complete`
---
## Success Criteria
P2.3 is complete when:
1. **All P2.3 items completed** (P2.3.1, P2.3.2, P2.3.3)
2. **All acceptance criteria met** (per step)
3. **All invariants preserved** (verified by CI)
4. **Documentation updated** (progress docs, parity matrix, changelog)
5. **Parity achieved** (Android has automated combined tests matching iOS intent)
---
## Next Steps After P2.3
After P2.3 completion:
1. **Tag baseline:** `v1.0.11-p2.3-complete` (optional but recommended)
2. **Consider P2.4:** iOS CI automation (macOS runners) if desired
3. **Consider P1.5b:** Remove iOS/App test harness from published tree
---
**Last Updated:** 2025-12-22
**Status:** Implementation-Ready
**Next Action:** Begin P2.3.1 - Enable Android Test Infrastructure