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.
13 KiB
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.shpasses - 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.gradlelines 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:
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:testfrom root) - Verify test runs successfully
- Check CI compatibility: ensure
./ci/run.shcan run tests or skip gracefully
Acceptance:
./gradlew testruns 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:
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:
TestDBFactorycreated- 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 separateTestDataHelper):injectInvalidSchedule()- creates schedule with empty ID or null fieldsinjectDuplicateSchedule()- creates duplicate schedule entriesinjectPastSchedule()- creates schedule with pastnextRunAtinjectDSTBoundarySchedule()- 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
TestDBFactoryand 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:
- Create notification scheduled at DST boundary (use
ZonedDateTimewith DST transition) - Simulate duplicate delivery events (rapid succession - call delivery handler twice)
- Trigger cold start recovery (call
ReactivationManager.performRecovery()) - Verify: idempotency (running twice yields identical state)
- Verify: deduplication (only one logical delivery recorded)
- Verify: DST-consistent next time (next scheduled time accounts for DST)
- Create notification scheduled at DST boundary (use
File: android/src/test/java/com/timesafari/dailynotification/DailyNotificationRecoveryTests.kt
Acceptance:
- Test created with
@Testannotation - Test labeled with
@resilience @combined-scenarioscomment - 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:
- Create notification that was just delivered (past time)
- Trigger rollover (first delivery - mark as delivered, schedule next)
- Simulate duplicate delivery immediately (call delivery handler again)
- Trigger cold start recovery
- Verify: rollover idempotency (no double-apply of state transitions)
- Verify: duplicate delivery doesn't double-apply state transitions
- Verify: cold start reconciliation produces correct state
File: android/src/test/java/com/timesafari/dailynotification/DailyNotificationRecoveryTests.kt
Acceptance:
- Test created with
@Testannotation - Test labeled with
@resilience @combined-scenarioscomment - 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:
- Verify Room database version is observable (check
Database.getVersion()) - Test recovery with version metadata present
- Verify version doesn't interfere with recovery
- Verify recovery works identically with version metadata
- Verify Room database version is observable (check
File: android/src/test/java/com/timesafari/dailynotification/DailyNotificationRecoveryTests.kt
Acceptance:
- Test created with
@Testannotation (optional) - Test labeled with
@resilience @combined-scenarioscomment - 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 testto 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.mdwith P2.3 test run entry - Update
docs/progress/04-PARITY-MATRIX.mdto 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.mdwith P2.3 completion entry - Update
docs/progress/00-STATUS.mdto 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.shto 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.shpasses- 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.gradlechanges - 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:
- All P2.3 items completed (P2.3.1, P2.3.2, P2.3.3)
- All acceptance criteria met (per step)
- All invariants preserved (verified by CI)
- Documentation updated (progress docs, parity matrix, changelog)
- Parity achieved (Android has automated combined tests matching iOS intent)
Next Steps After P2.3
After P2.3 completion:
- Tag baseline:
v1.0.11-p2.3-complete(optional but recommended) - Consider P2.4: iOS CI automation (macOS runners) if desired
- 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