high priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test: a valid BadgeCriteria object with all required fields and non-negative thresholds saves successfully and returns the persisted record
Test: a BadgeCriteria with a negative threshold value (e.g., assignments_required: -1) throws a typed NegativeThresholdException before any repository call
Test: a BadgeCriteria missing a required field (e.g., null badge_definition_id) throws a typed MissingRequiredFieldException identifying the missing field
Test: a BadgeCriteria referencing a badge definition that belongs to a different org (cross-org reference) throws a typed CrossOrgReferenceException
Test: after a successful updateCriteria call, a subsequent getCriteria call does NOT return the cached (stale) value — it fetches fresh data
Test: cache hit is used when getCriteria is called twice without an intervening update (mock repository is called only once)
All tests use a mocked badge-definition-repository — no Supabase calls
Test file achieves 90%+ branch coverage on BadgeConfigurationService validation and caching logic

Technical Requirements

frameworks
Flutter
flutter_test
apis
Supabase (mocked via badge-definition-repository mock)
data models
BadgeCriteria
BadgeDefinition
OrgConfiguration
performance requirements
Full test suite must complete in under 5 seconds
security requirements
Test fixtures must not include real org IDs or badge definition IDs — use constants defined in a test_constants.dart file

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

If BadgeConfigurationService currently performs validation inside the repository call, refactor to add a validateCriteria() guard method that runs before any I/O — this makes validation tests fast and I/O-free. For cache invalidation testing, expose a testable hook (e.g., a protected clearCache() method or a clock dependency for TTL-based caches) so tests can trigger invalidation without waiting for real time to pass. For cross-org tests, configure the mock repository to return a BadgeDefinition with a different orgId than the one in the criteria being saved. Define all typed exceptions (NegativeThresholdException, MissingRequiredFieldException, CrossOrgReferenceException) as Dart Exception subclasses before writing tests — coordinate with task-008 implementer.

Testing Requirements

Pure unit tests using flutter_test. Mock the badge-definition-repository with a configurable fake that can simulate: successful save, cross-org rejection, and missing definition. Test cache behaviour by inspecting mock invocation counts — assert that the repository's fetch method is called once for a cache hit and twice after an invalidation. Use setUp() to reset mock state between tests.

Group tests: validation tests, caching tests, cross-org tests. Run coverage with `flutter test --coverage`.

Component
Badge Configuration Service
service medium
Epic Risks (3)
high impact medium prob technical

peer-mentor-stats-aggregator must compute streaks and threshold counts across potentially hundreds of activity records per peer mentor. Naive queries (full table scans or N+1 patterns) will cause slow badge evaluation, especially when triggered on every activity save for all active peer mentors.

Mitigation & Contingency

Mitigation: Design aggregation queries using Supabase RPCs with window functions or materialised views from the start. Add database indexes on (peer_mentor_id, activity_date, activity_type) before writing any service code. Profile all aggregation queries against a dataset of 500+ activities during development.

Contingency: If query performance is insufficient at launch, implement incremental stat caching: maintain a peer_mentor_stats snapshot table updated on each activity insert via a database trigger, so the aggregator reads from pre-computed values rather than scanning raw activity rows.

medium impact low prob technical

badge-award-service must be idempotent, but if two concurrent edge function invocations evaluate the same peer mentor simultaneously (e.g., from a rapid double-save), both could pass the uniqueness check before either commits, resulting in duplicate badge records.

Mitigation & Contingency

Mitigation: Rely on the database-level uniqueness constraint (peer_mentor_id, badge_definition_id) as the final guard. In the service layer, use an upsert with ON CONFLICT DO NOTHING and return the existing record. Add a Postgres advisory lock or serialisable transaction for the award sequence during the edge function integration epic.

Contingency: If duplicate records are discovered in production, run a deduplication migration to remove extras (keeping earliest earned_at) and add a unique index if not already present. Alert engineering via Supabase database webhook on constraint violations.

medium impact medium prob scope

The badge-configuration-service must validate org admin-supplied criteria JSON on save, but the full range of valid criteria types (threshold, streak, training-completion, tier-based) may not be fully enumerated during development, leading to either over-permissive or over-restrictive validation that frustrates admins.

Mitigation & Contingency

Mitigation: Define a versioned Dart sealed class hierarchy for CriteriaType before writing the validation logic. Review the hierarchy with product against all known badge types across NHF, Blindeforbundet, and HLF before implementation. Build the validator against the sealed class so new criteria types require an explicit code addition.

Contingency: If admins encounter validation rejections for legitimate criteria, expose a 'criteria_raw' escape hatch (JSON passthrough, admin-only) with a product warning, and schedule a sprint to formalise the new criteria type properly.