high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Integration test suite connects to a dedicated test Supabase project (not production) using environment-injected test credentials
Test verifies that UserIdentityRepository.saveIdentity() persists personnummer to Supabase and the same value is returned by getIdentity()
Test verifies BankID verification status can be toggled from unverified to verified and the new status is reflected immediately on next read
Test verifies that logout triggers local cache invalidation and getIdentity() returns null without a network call after logout
Test verifies RLS: user A cannot read or modify user B's identity row (attempt returns null or throws an unauthorized error)
Test verifies that GDPR purge (purgeLocalData or equivalent) removes all locally cached identity data and clears the SecureStorage keys associated with the user
Test verifies VippsOrgCostConfig.isVippsEnabledForOrg() returns false for an org seeded with subscription_active = false
Test verifies VippsOrgCostConfig.isVippsEnabledForOrg() returns true for an org seeded with subscription_active = true
Test verifies that after activateSubscription(), a subsequent isVippsEnabledForOrg() call returns true (state change reflected in DB)
Test database is reset to known seed state before each test (using beforeEach seed script or Supabase transaction rollback)
All tests are tagged @Tags(['integration', 'supabase']) and excluded from unit test CI runs
Test environment credentials are loaded from .env.test and never hardcoded in test files

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
flutter_test
apis
Supabase Auth API (test user sign-in)
Supabase PostgREST (user_identities table, vipps_org_cost_config table)
Supabase Admin API (for seeding and cleanup via service_role key)
data models
UserIdentity
VippsOrgCostConfig
Organization
performance requirements
Full integration test suite must complete in under 3 minutes
Each individual network-bound test must have a timeout of 10 seconds
Seed and teardown operations should be batched to minimize round-trips
security requirements
Test Supabase project must be completely isolated from production — separate project URL and anon key
service_role key used for seeding must only be present in CI secrets and .env.test (gitignored)
Personnummer values in tests must be fictitious valid-format numbers (e.g., 01010199999) — never real SSNs
All test users created during the suite must be deleted in afterAll teardown to prevent accumulation of ghost accounts

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The most complex aspect is managing test isolation with a live Supabase backend. Use a transaction-per-test pattern if supported, or implement a dedicated test seed/cleanup lifecycle. For the cross-user RLS test, you need two authenticated Supabase sessions simultaneously — use two separate Supabase client instances initialized with different user JWTs. The GDPR purge test should assert both that the remote row is deleted (or anonymized) AND that the local SecureStorage keys return null — test these as two separate assertions after calling purgeLocalData().

For personnummer, use the Norwegian SSN format (11 digits) with known test numbers that pass the mod-11 checksum algorithm. The VippsOrgCostConfig gate test is best placed after the UserIdentity tests since both use the same test Supabase project — coordinate the seed data to cover both test suites. Store test config in a .env.test file loaded via flutter_dotenv or equivalent, ensuring CI injects SUPABASE_TEST_URL and SUPABASE_TEST_ANON_KEY as environment variables.

Testing Requirements

Structure the test suite in integration_test/repositories/ with separate files: user_identity_repository_test.dart and vipps_org_cost_config_integration_test.dart. Use a shared test_supabase_helper.dart that handles: (1) initializing Supabase with test credentials, (2) signing in two distinct test users (test_user_a and test_user_b), (3) seeding vipps_org_cost_config rows for two orgs (one active, one inactive), (4) teardown that deletes seeded rows and signs out all users. Use group() blocks to organize tests by scenario. For the RLS test, sign in as test_user_b and attempt to query test_user_a's identity row using .eq('user_id', userA.id) — expect empty result or error.

For GDPR purge, verify both Supabase deletion and local SecureStorage clearance in a single atomic test scenario.

Component
User Identity Repository
data medium
Epic Risks (3)
high impact medium prob technical

Flutter Secure Storage behavior differs between iOS Keychain and Android Keystore — key accessibility attributes (kSecAttrAccessibleWhenUnlocked vs. WhenUnlockedThisDeviceOnly) may cause tokens to become inaccessible after device restart or OS upgrade, breaking session restoration for returning users.

Mitigation & Contingency

Mitigation: Define explicit Keychain accessibility attributes during implementation and write integration tests on both platforms. Follow flutter_secure_storage documentation for cross-platform accessibility configuration.

Contingency: Implement a recovery flow that detects secure storage read failures and falls back to full re-authentication rather than crashing. Add a migration utility to re-write tokens with corrected attributes if a misconfiguration is discovered post-release.

high impact medium prob security

Personnummer is a legally sensitive national identifier under Norwegian GDPR implementation. If encryption-at-rest or data minimization requirements are not met before launch, the feature could be blocked by legal/compliance review from any of the four partner organizations.

Mitigation & Contingency

Mitigation: Ensure personnummer is only persisted after explicit user consent via the personnummer confirmation widget. Use Supabase column-level encryption for the personnummer field. Document the data processing basis and retention policy before the first TestFlight release.

Contingency: If legal review blocks the personnummer write-back, implement the feature as opt-in only with a deferred sync model, allowing BankID/Vipps login to proceed without storing the personnummer until compliance is confirmed.

medium impact high prob dependency

If the VippsOrgCostConfig data is not populated in Supabase for all four partner organizations before the feature ships, users from unconfigured organizations will see no Vipps login option and may report it as broken, creating confusion and support load.

Mitigation & Contingency

Mitigation: Create a seed migration script for Vipps org configuration and include it in the deployment checklist. Implement a clear admin UI warning when an organization is missing Vipps configuration.

Contingency: Add a feature flag in VippsOrgCostConfig so individual organizations can be enabled/disabled without a code deploy, allowing rapid remediation.