high priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

Integration test file exists at integration_test/recruitment/deep_link_routing_test.dart
Test case 1 (cold-start): pumping the app with a referral deep-link URI injected via the mocked DeepLinkHandler platform channel results in the GoRouter navigating to the new-member onboarding screen
Test case 1 asserts that the token query parameter extracted from the URI matches the value in the injected link (e.g. '?token=ABC123' → onboarding screen receives 'ABC123')
Test case 2 (foreground-resume): after the app is running on the home screen, emitting a referral URI via the mocked platform channel stream causes GoRouter to push to the onboarding screen
Test case 2 asserts the correct token parameter is passed to the onboarding screen
Test case 3 (unrecognised link): emitting a URI with an unknown path (e.g. /unknown/path) does NOT navigate to the onboarding screen — current route remains unchanged
Test case 4 (missing token): emitting a valid referral URI with no token query parameter routes to an error screen or home screen — not the onboarding screen
All test cases pass on both iOS Simulator and Android Emulator
Platform channel mock is properly torn down in tearDown() to avoid bleed between tests
Tests do not make real Supabase network calls — Supabase client is overridden with a mock via Riverpod ProviderScope overrides

Technical Requirements

frameworks
flutter_test (integration_test package)
GoRouter (existing app router, use GoRouter.of(context).location for assertions)
Riverpod (ProviderScope overrides to inject mock dependencies)
mocktail for DeepLinkHandler mock
apis
Flutter MethodChannel / EventChannel mock (TestDefaultBinaryMessengerBinding) for platform channel simulation
GoRouter test utilities (routerOf, navigatorKey)
data models
DeepLinkPayload (URI, token)
ReferralCode (token string)
performance requirements
Each integration test case must complete within 10 seconds on CI (excluding app startup time)
Platform channel mock responses must be synchronous to avoid test timing flakiness
security requirements
Test token values must be obviously fake (e.g. 'TEST_TOKEN_ABC123') to avoid confusion with production tokens in CI logs
ui components
NewMemberOnboardingScreen (assert it is present in widget tree)
GoRouter location string for current route assertion

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The hardest part of this test is correctly mocking the platform channel timing for the cold-start scenario — the DeepLinkHandler typically calls getInitialLink() (a one-shot Future) on app startup. Intercept this by setting the mock method handler before calling tester.pumpWidget(). For the foreground-resume stream, use a StreamController injected into the mock DeepLinkHandler and add the test URI after pumpWidget completes. If GoRouter uses a redirect() function to handle deep links, the test needs to pump an extra frame after the redirect resolves — use tester.pump(Duration.zero) or pumpAndSettle().

If the onboarding screen widget is not yet created (depends on a future incomplete state), wrap assertions in a retry loop with tester.pump(). Keep each test case isolated by rebuilding the full ProviderScope between cases.

Testing Requirements

Use the integration_test package with IntegrationTestWidgetsFlutterBinding.ensureInitialized(). Mock the DeepLinkHandler platform channel using TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler() for cold-start initial link, and a StreamController for the foreground-resume link stream. Wrap the app under test in a ProviderScope with overrides for supabaseClientProvider and deepLinkHandlerProvider to prevent real network calls. Use pumpAndSettle() after emitting the deep link to allow GoRouter to complete navigation.

Assert the current route using GoRouter.of(navigatorKey.currentContext!).location and find the onboarding screen widget with find.byType(NewMemberOnboardingScreen). Run integration tests on both platforms via 'flutter test integration_test/ -d '.

Component
Deep Link / OAuth Redirect Handler
infrastructure medium
Dependencies (3)
Create Riverpod providers for ReferralCodeRepository, RecruitmentAttributionRepository, ShareSheetBridge, QrCodeGenerator, and DeepLinkHandler. Wire them to the existing Supabase client provider and organisation context. Ensure providers are scoped appropriately (global singletons for repositories and utilities, organisation-scoped for queries). Export a barrel file for the recruitment infrastructure layer so higher-tier epics can import cleanly. epic-membership-recruitment-foundation-task-009 Implement the DeepLinkHandler service that intercepts incoming deep links matching the referral URI scheme (e.g., app://referral?token=XYZ or https://app.example.com/referral/XYZ). Integrate with the app's existing GoRouter configuration to route matched URIs to the new-member onboarding screen, passing the extracted token as a route parameter. Handle both cold-start (app launched from link) and foreground-resume (app already running) cases using the app_links package. Ensure unrecognised links are passed through to existing handlers without interference. epic-membership-recruitment-foundation-task-007 Configure Android AndroidManifest.xml intent filters and iOS Info.plist URL schemes and associated domains to support the referral deep-link URI scheme. Add the required apple-app-site-association file configuration for Universal Links. Verify the configuration works for both cold-start and foreground-resume scenarios. Document the required Supabase redirect URL allow-list entries and any required Apple Developer portal / Google Play Console configuration steps. epic-membership-recruitment-foundation-task-008
Epic Risks (3)
high impact medium prob technical

iOS Universal Links and Android App Links have distinct configuration requirements (apple-app-site-association, assetlinks.json, entitlements). A misconfiguration causes the OS to open the referral URL in a browser instead of the app, completely breaking the onboarding funnel for new members on one platform.

Mitigation & Contingency

Mitigation: Configure both Universal Links and App Links from the start of this epic using the project's existing Supabase-hosted domain. Write an E2E test on both simulators that taps a referral URL and asserts the onboarding screen is reached. Document the required server-side JSON files alongside the migration.

Contingency: If platform deep-link configuration cannot be resolved before the UI epics need the handler, implement a fallback custom-scheme URI (e.g., likeperson://referral?code=XYZ) that works unconditionally, and schedule Universal/App Link fix as a follow-up task.

high impact high prob security

Referral click events must be writable without an authenticated session (a new member who has not yet registered is tapping the link). Standard Supabase RLS cannot grant anonymous inserts without opening a security hole. If this is not solved early it blocks the entire attribution pipeline.

Mitigation & Contingency

Mitigation: Design referral_events writes to go exclusively through a Supabase Edge Function that validates the referral code exists and is active before inserting. The Edge Function uses the service-role key server-side; the client only calls the function endpoint. This is documented in the feature spec.

Contingency: If the Edge Function approach is delayed, temporarily allow anon inserts restricted by a CHECK constraint that event_type = 'click' and new_member_id IS NULL, then tighten to Edge Function writes in a follow-up migration before the feature goes to production.

medium impact low prob dependency

The qr_flutter package version pinned in pubspec may conflict with the current Flutter SDK version or with other packages in the monorepo, causing build failures that block QR code delivery.

Mitigation & Contingency

Mitigation: Verify qr_flutter compatibility against the project's Flutter SDK version as the very first task in this epic. If a conflict exists, resolve it before any other work proceeds.

Contingency: If qr_flutter cannot be made compatible, evaluate mobile_scanner (already likely in pubspec for QR scanning) which also supports generation, or implement QR generation via a lightweight Dart port as a last resort.