high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

fetchEarnedBadgesForUser with a valid user_id returns only badges whose organisation_id matches the calling user's organisation — badges from other organisations are not returned
fetchEarnedBadgesForUser returns an empty list (not an error) when the user has no earned badges
awardBadge successfully inserts a UserBadge record and the returned object contains the correct user_id, badge_definition_id, and awarded_at timestamp
Calling awardBadge a second time for the same user_id + badge_definition_id combination throws a DuplicateBadgeException (or equivalent domain error) — the uniqueness constraint is enforced at the repository layer
revokeBadge removes or soft-deletes the target record; subsequent fetchEarnedBadgesForUser does not return the revoked badge
revokeBadge called with a non-existent record throws a BadgeNotFoundException (or returns a typed error)
An attempt to fetch badges for a user in a different organisation (simulating an RLS violation) results in an empty list or a PermissionDeniedException — never in data leakage
All tests are isolated — no test depends on state set up by another test
Test suite achieves at least 80% line coverage on BadgeRepository as reported by flutter test --coverage
All tests pass in CI without requiring a live Supabase instance (mocked via mocktail or a local Supabase Docker instance)

Technical Requirements

frameworks
Flutter
flutter_test
apis
Supabase REST API
Supabase RLS policies
data models
UserBadge
BadgeDefinition
Organisation
performance requirements
Each unit test must complete in under 500 ms
Total test suite for BadgeRepository must complete in under 30 seconds
security requirements
RLS cross-organisation isolation must be tested explicitly — this is a security requirement not just a data correctness requirement
Test credentials and connection strings must not be committed to the repository; use environment variables or Flutter test fixtures

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

When mocking Supabase with mocktail, mock at the SupabaseClient level rather than the http layer — this keeps tests closer to the repository's actual API surface. Set up a MockSupabaseClient that returns controlled responses for the specific table queries BadgeRepository issues. For the RLS test, simulate the policy by configuring the mock to return an empty list when the queried user_id belongs to a different organisation — the actual RLS enforcement is a Supabase concern, but the repository must handle the empty/error response gracefully. For the uniqueness constraint test, configure the mock to throw a PostgrestException with code '23505' (unique_violation) on the duplicate insert, then assert BadgeRepository wraps this into a domain-level DuplicateBadgeException.

Follow the existing test file structure already established in the project (check test/ directory for conventions).

Testing Requirements

Use flutter_test as the test runner and mocktail for mocking the Supabase client. Organise tests in group() blocks by method name (fetchEarnedBadgesForUser, awardBadge, revokeBadge). For each group write: one happy-path test, at least one error/edge-case test, and one organisation-scoping/RLS test. If integration tests against a real Supabase instance are included, place them in a separate test/integration/ directory and gate them behind an environment variable (RUN_INTEGRATION_TESTS=true) so they do not block CI by default.

Generate and inspect the LCOV report to confirm the 80% line coverage threshold is met before marking the task complete.

Component
Badge Repository
data low
Epic Risks (2)
high impact medium prob scope

Badge criteria are stored as structured JSON in badge_definitions. If the JSON schema for criteria (threshold counts, streak lengths, training completion flags) is not well-defined upfront, the evaluation service will be built against a moving target, requiring costly migrations and refactors.

Mitigation & Contingency

Mitigation: Define and document the criteria JSON schema in a shared type file before any repository code is written. Review the schema with all three organisations' badge requirements — especially Blindeforbundet's honorar thresholds — and version the JSON schema using a 'criteria_version' field from day one.

Contingency: If the criteria schema must change after services are built, write a Supabase migration to backfill existing rows and add a migration version column. Keep the evaluation service criteria parser isolated behind an interface so only one function needs updating.

medium impact medium prob dependency

Badge icon assets may not yet exist or may fail WCAG 2.2 AA contrast validation (minimum 3:1 for graphical objects) when rendered over design-token backgrounds. Missing or non-compliant icons could block UI epic delivery for Blindeforbundet, for whom screen reader and visual accessibility is non-negotiable.

Mitigation & Contingency

Mitigation: During this epic, implement the contrast-ratio validator in badge-icon-asset-manager and run it as a Flutter test against all candidate icon assets early. Coordinate with the design team to provide WCAG-compliant SVG icons in both locked and unlocked variants before the UI epic begins.

Contingency: If assets are late or fail contrast checks, ship placeholder icons that are guaranteed compliant (solid design-token colour fills with text labels) and swap in final assets post-QA without requiring a code change.