high priority medium complexity testing pending testing specialist Tier 5

Acceptance Criteria

Test: getActivityTypes() with orgId 'org-a' returns only types where org_id == 'org-a', even when the mocked repository contains types for multiple orgs
Test: getActivityTypes() with a different orgId returns a different filtered list — confirms org-scoping is enforced by the service, not assumed from the repository
Test: creating an ActivityType with triggersReimbursementWorkflow=true AND isTravelExpenseEligible=false throws ActivityTypeValidationException with code INVALID_FLAG_COMBINATION
Test: creating an ActivityType with triggersReimbursementWorkflow=false AND isTravelExpenseEligible=true throws ActivityTypeValidationException with code INVALID_FLAG_COMBINATION
Test: a valid flag combination (both true or both false) completes without throwing
Test: after calling createActivityType(), invalidateCache() is invoked exactly once on the mocked cache provider
Test: after calling updateActivityType(), invalidateCache() is invoked exactly once
Test: after calling deactivateActivityType(), invalidateCache() is invoked exactly once
Test: label resolution for a key that exists returns the correct localized string
Test: label resolution for a missing key returns the raw key string as fallback without throwing
All tests are self-contained with no shared mutable state between test cases
Test file structure groups tests by method under descriptive group() blocks

Technical Requirements

frameworks
Flutter
Riverpod
mocktail
data models
ActivityType
ActivityTypeRepository
ActivityTypeCacheProvider
performance requirements
Full unit test suite must complete in under 5 seconds
security requirements
Tests must verify that orgId filtering is enforced at the service layer, not relying solely on RLS

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Mock both ActivityTypeRepository and ActivityTypeCacheProvider using mocktail's Mock class. Register fallback values for any complex argument types using registerFallbackValue() in setUpAll(). For cache invalidation verification, use mocktail's verify(() => mockCache.invalidate(any())).called(1) pattern. For the flag combination validation, the service should validate before any repository call — verify this by asserting the repository mock was never called when validation fails.

Label resolution fallback should be implemented as a try/catch or null-coalescing pattern in the service itself, not in the repository. Keep each test focused on a single behavior — avoid multi-assertion tests that obscure failure location.

Testing Requirements

Pure Dart unit tests using flutter_test and mocktail. No widget tree required. Use setUp() to construct a fresh service instance with fresh mocks before each test. Use verify() and verifyNever() from mocktail to assert cache invalidation call counts.

Use throwsA(isA()) matchers for error path tests. Achieve 100% branch coverage on the validation and label resolution logic. Run dart test --coverage and verify coverage report.

Component
Activity Type Service
service medium
Epic Risks (2)
medium impact medium prob scope

Metadata flag combination rules differ between organisations (e.g., Blindeforbundet honorarium thresholds, HLF mutual exclusion of km and transit). Encoding these as generic service-level validation may be insufficient, forcing organisation-specific branching inside the service that becomes unmaintainable as new organisations are onboarded.

Mitigation & Contingency

Mitigation: Model flag validation as a pure function that accepts an ActivityTypeMetadata object and an org configuration record, making org-specific rules data-driven rather than hardcoded. Establish the validation contract in the foundation epic so the service just delegates to the validator.

Contingency: Defer complex cross-flag validation to a lightweight edge function that can be updated without a mobile app release, accepting that initial validation in the mobile service layer is permissive and corrected server-side.

high impact low prob technical

Blindeforbundet users rely on VoiceOver and JAWS. If the selection screen is built with non-semantic widgets that fail accessibility audit late in the sprint, a significant rework of the widget tree may be required, blocking the registration wizard integration.

Mitigation & Contingency

Mitigation: Build the selection screen against the project's established accessibility design tokens and semantics wrapper conventions from the start. Run Flutter's semantic tree inspector and a manual VoiceOver pass before marking any widget task complete.

Contingency: Wrap all tappable items in the project's SemanticsWrapperWidget and schedule a dedicated accessibility review session with a screen reader user from Blindeforbundet before the epic is closed.