high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

RegistrationDefaultsManager: test getDefaults() returns a non-null ActivityRegistrationDefaults on first run with no persisted state
RegistrationDefaultsManager: test getDefaults() returns the last-saved activity type when RegistrationPreferencesStore has a persisted value
RegistrationDefaultsManager: test saveLastUsedActivityType() calls RegistrationPreferencesStore.save() with correct activity type id
RegistrationDefaultsManager: test saveLastUsedActivityType() does NOT throw when the underlying store throws — failure must be silent
ActivityRegistrationService: test submitRegistration() throws a typed ValidationException when required payload fields are missing or invalid
ActivityRegistrationService: test HLF compensation eligibility returns true when activityType.is_travel_expense_eligible == true and distance is within policy
ActivityRegistrationService: test HLF compensation eligibility returns false when activityType.is_travel_expense_eligible == false regardless of distance
ActivityRegistrationService: test submitRegistration() on success returns an ActivityRecord with syncStatus == pending and a local_ prefixed id
ActivityRegistrationService: test submitRegistration() propagates a NetworkException when ActivityRepository throws a network error
All mocks verify() that no unexpected interactions occurred (strict mocking)
Test file compiles and all tests pass with flutter_test runner
Branch coverage report shows 100% on RegistrationDefaultsManager and ActivityRegistrationService business logic methods

Technical Requirements

frameworks
Flutter
flutter_test
mocktail
apis
RegistrationPreferencesStore
ActivityRepository
data models
activity
activity_type
activity_preferences
performance requirements
All unit tests must complete in under 5 seconds total — no real async timers or real network calls
security requirements
Test data must not contain real PII — use synthetic names, fake UUIDs, and placeholder org IDs

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Place tests in test/business_logic/registration_defaults_manager_test.dart and test/business_logic/activity_registration_service_test.dart. Each test file should import only the class under test and its mocks — avoid importing concrete infrastructure. For 100% branch coverage, use the flutter_test --coverage flag and inspect lcov output to find missed branches before finalising. The compensation eligibility logic likely has several conditions (is_travel_expense_eligible flag, distance thresholds, absence of conflicting expense types per HLF rules) — enumerate each branch as a separate test case with a descriptive name.

For the silent failure test on saveLastUsedActivityType(), stub the store to throw and assert that no exception propagates out of the manager method.

Testing Requirements

Use flutter_test and mocktail. Create a test group per class. Use setUp() to construct fresh mock instances before each test. Use when(mock.method()).thenReturn() and when(mock.method()).thenThrow() for stubbing.

Use verify(mock.method()).called(1) to assert side effects. For async methods use expectLater with completion matchers. Parametrize compensation eligibility tests using a data table of activityType flags and expected boolean outcomes to cover all branches in a single test group.

Component
Activity Registration Service
service medium
Epic Risks (3)
medium impact medium prob technical

The wizard Cubit manages multiple concurrent state slices (current step, each field value, submission status, error state). As the number of wizard steps grows, the state class can become unwieldy, making it difficult to reason about transitions, leading to subtle bugs where advancing a step resets a previously filled field.

Mitigation & Contingency

Mitigation: Use an immutable state model (copyWith pattern) with a separate sealed class per wizard step state. Keep the Cubit's emit calls minimal and always derive the next state from the current state to prevent accidental field resets. Document the state machine transitions explicitly in code comments.

Contingency: If state complexity becomes unmanageable, split into a parent WizardCubit (owns step navigation and submission) and per-step child Cubits (own individual field state), coordinating via a shared repository layer.

high impact medium prob scope

Organisation-specific compensation eligibility rules (e.g., activity type + duration thresholds) are business logic that may change independently of the app release cycle. Hardcoding these rules in ActivityRegistrationService means rule changes require a new app deployment, causing delays and potential financial errors if the deployed version uses outdated rules.

Mitigation & Contingency

Mitigation: Model compensation rules as configuration fetched from Supabase (stored per organisation), cached locally. ActivityRegistrationService reads from cache with a fallback to hardcoded defaults for offline scenarios. Design the rule schema to be extensible without code changes.

Contingency: If dynamic rules are not ready for initial release, ship with hardcoded rules and a feature flag that enables the remote-config path. Document the rule structure clearly so coordinators can trigger a rule update via a Supabase dashboard entry rather than a code deployment.

medium impact low prob integration

The last-used activity type stored in RegistrationPreferencesStore may become invalid if the organisation administrator deactivates that activity type between sessions. The Cubit would pre-populate a deleted type, and either the UI would show a missing item or submission would fail with a foreign-key constraint error.

Mitigation & Contingency

Mitigation: In RegistrationDefaultsManager, validate the retrieved last-used activity type ID against the current list of active types fetched from the activity type repository. If the stored ID is not in the active list, fall back to the first active type alphabetically.

Contingency: If validation cannot be performed offline, surface a non-blocking warning in the activity type step ('Your previously used activity type is no longer available') and require the user to make a new selection before advancing.