Integration smoke test: full foundation layer wired together
epic-organization-selection-and-onboarding-foundation-task-014 — Write an integration test that exercises the full foundation layer as a system: (1) SecureStorageAdapter writes and reads an org ID, (2) SupabaseRLSTenantConfigurator applies the org scope and a tenant-filtered query returns only that org's data, (3) OrgBrandingCache pre-warms from Supabase and serves cached tokens on second call, (4) FeatureFlagProvider returns correct flags for the selected org, (5) OrgCardWidget renders with branding tokens from cache, (6) OnboardingProgressIndicator renders in both auth phases. Validates all zero-inbound-dependency components interoperate correctly before dependent epics begin.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Structure the test file with a setUp block that initializes all foundation components in dependency order: (1) SecureStorageAdapter, (2) MockSupabaseClient or real client, (3) SupabaseRLSTenantConfigurator(client), (4) OrgBrandingCache(client), (5) FeatureFlagProvider(client). Use tearDown to clear secure storage: await storage.deleteAll(). For Supabase test project seed data: create a test org with id='test-org-123', branding tokens, and feature flags. For cache hit verification: inject a CallCountingSupabaseClient and assert invocationCount == 1 after both prewarm() and getBrandingTokens() calls.
This test is the gate before dependent epics — if it fails, downstream work is blocked. Consider marking it with @Tags(['smoke']) for CI pipeline filtering.
Testing Requirements
Integration tests using the flutter integration_test package (not just flutter_test). The test must run on a real device or emulator/simulator — use flutter test integration_test/ or flutter drive. For Supabase interactions: prefer a dedicated Supabase test project with seeded test data over mocking, but a MockSupabaseClient (implementing the SupabaseClient interface) is acceptable if a test project is unavailable. Verify cache behavior by wrapping the Supabase client in a SpyClient that counts method invocations.
Verify secure storage with real flutter_secure_storage on device. Each of the 6 scenarios should be its own testWidgets or test block with independent setup and teardown.
iOS Keychain and Android Keystore have meaningfully different failure modes and permission models. The secure storage plugin may throw platform-specific exceptions (e.g., biometric enrollment required, Keystore wipe after device re-enrolment) that crash higher-level flows if not caught at the adapter boundary.
Mitigation & Contingency
Mitigation: Wrap all storage plugin calls in try/catch at the adapter layer and expose a typed StorageResult<T> instead of throwing. Write integration tests on real device simulators for both platforms in CI using Fastlane. Document the exception matrix during spike.
Contingency: If a platform-specific failure cannot be handled gracefully, fall back to in-memory-only storage for the current session and surface a non-blocking warning to the user; log the event for investigation.
Setting a session-level Postgres variable (app.current_org_id) via a Supabase RPC requires that RLS policies on every table reference this variable. If the Supabase project schema has not yet defined these policies, the configurator will set the variable but queries will return unfiltered data, giving a false sense of security.
Mitigation & Contingency
Mitigation: Include a smoke-test RPC in the SupabaseRLSTenantConfigurator that verifies the variable is readable from a policy-scoped query before marking setup as complete. Coordinate with the database migration task to ensure RLS policies reference app.current_org_id before the configurator is shipped.
Contingency: If RLS policies are not in place at integration time, gate all data-fetching components behind a runtime check in SupabaseRLSTenantConfigurator.isRlsScopeVerified(); block data access and surface a developer warning until policies are confirmed.
Fetching feature flags from Supabase on every cold start adds network latency before the first branded screen renders. On slow connections this may cause a perceptible blank-screen gap or cause the app to render with default (unflagged) state before flags arrive.
Mitigation & Contingency
Mitigation: Persist the last-known flag set to disk in the FeatureFlagProvider and serve stale-while-revalidate on startup. Gate flag refresh behind a configurable TTL (default 15 minutes) so network calls are not made on every launch.
Contingency: If stale flags cause a feature to appear that should be hidden, add a post-load re-evaluation pass that reconciles the live flag set with the rendered widget tree and triggers a targeted rebuild where needed.