End-to-end scheduler and preference gate integration test
epic-scenario-push-engagement-core-engine-task-015 — Write an end-to-end integration test that simulates a full scheduler run: seed active peer mentors with varying states (inactive, milestone-approaching, cert-expiring, opted-out), invoke the scheduler, and assert that only the expected notifications are dispatched and recorded, opted-out users receive nothing, and cooldown prevents duplicate notifications across consecutive runs.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 10 - 11 tasks
Can start after Tier 9 completes
Implementation Notes
The scheduler must accept an injectable clock (DateTime or a Clock abstraction) so tests can advance time without sleeping. If the scheduler is a Supabase Edge Function, invoke it via the Supabase Functions client with a testMode flag that bypasses push delivery and uses a mock push provider that records calls in-memory. Structure seeding as a helper method seedPeerMentorScenarios() that returns a map of scenario_type → user_id so assertions can reference the correct users without hardcoding UUIDs. Pay special attention to the cooldown advancement: update the cooldown record's last_triggered_at directly in the database to simulate time passing rather than relying on sleep().
This test is the contract test for the scheduler — if it passes, the scheduler is correct by definition.
Testing Requirements
Single end-to-end test file with three sequential phases in one test: Phase 1 — seed and first scheduler run with assertions on record count and opted-out/cooldown exclusions. Phase 2 — immediate second scheduler run asserting zero new records (cooldown active). Phase 3 — advance clock abstraction past cooldown window, run scheduler again, assert new records created for previously suppressed users. Use group() blocks in flutter_test to separate phases for readable output.
Assert both positive cases (records exist with correct fields) and negative cases (opted-out and cooldown users have no records) via direct Supabase queries. Include a comprehensive tearDown that deletes all rows matching test user IDs from all three tables.
The scenario-edge-function-scheduler must evaluate all active peer mentors within the 30-second Supabase Edge Function timeout. For large organisations, a sequential evaluation loop may exceed this limit, causing partial runs and missed notifications.
Mitigation & Contingency
Mitigation: Design the trigger engine to batch mentor evaluations using database-side SQL queries (bulk inactivity check via a single query rather than per-mentor calls), and add a performance test against 500 mentors during development. Document the evaluated mentor count per scenario type in scenario-evaluation-config to allow selective scenario execution per run.
Contingency: If single-run execution is insufficient, split evaluation into per-scenario-type scheduled functions (inactivity check, milestone check, expiry check) on separate cron schedules, dividing the computational load across multiple invocations.
A race condition between concurrent scheduler invocations or retried cron triggers could cause the same scenario notification to be dispatched multiple times to a mentor, severely degrading trust in the feature.
Mitigation & Contingency
Mitigation: Implement cooldown enforcement using a database-level upsert with a unique constraint on (user_id, scenario_type, cooldown_window_start) so that a second invocation within the same window is rejected at the persistence layer rather than the application layer.
Contingency: Add an idempotency key derived from (user_id, scenario_type, evaluation_date) to the notification record insert; if a duplicate key violation is caught, log it as a warning and skip dispatch without error.
The trigger engine queries peer mentor activity history across potentially multiple organisations and chapters. RLS policies configured for app-user roles may block the Edge Function's service-role queries, or query performance may degrade on large activity tables.
Mitigation & Contingency
Mitigation: Confirm the Edge Function runs with the Supabase service role key (bypassing RLS) and add composite indexes on (user_id, activity_date) to the activity tables before implementing the inactivity detection query.
Contingency: If service-role access is restricted by organisational policy, implement a dedicated database function (SECURITY DEFINER) that performs the inactivity aggregation and is callable by the Edge Function with limited scope.