critical priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test suite contains at minimum 12 test cases covering every named business rule branch
addChapterAffiliation with 0 existing affiliations succeeds and returns a list of length 1
addChapterAffiliation with 1 existing affiliation succeeds and returns a list of length 2
addChapterAffiliation with 4 existing affiliations succeeds and returns a list of length 5
addChapterAffiliation with 5 existing affiliations throws MaxChaptersExceededException
addChapterAffiliation with a chapter_id already in the contact's affiliation list throws AlreadyAssignedToChapterException
removeChapterAffiliation for an existing affiliation succeeds and returns the remaining list without the removed entry
removeChapterAffiliation for a chapter_id not in the contact's affiliation list throws AffiliationNotFoundException
After each successful mutation the returned list reflects the full current affiliation set (not just the changed item)
All tests use a mock/fake ContactChapterRepository and make no real Supabase calls
All tests pass with flutter test and produce no warnings
Test file is located at test/services/multi_chapter_membership_service_test.dart

Technical Requirements

frameworks
Flutter
flutter_test
data models
ChapterAffiliation
ContactChapter
MaxChaptersExceededException
AlreadyAssignedToChapterException
AffiliationNotFoundException
performance requirements
Full test suite executes in under 5 seconds

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Structure the test file with two top-level group blocks: 'addChapterAffiliation' and 'removeChapterAffiliation'. Within each group, name tests descriptively after the scenario being tested (e.g., 'returns updated list when adding first affiliation'). Build a helper factory function (makeAffiliations(int count)) to reduce boilerplate when setting up mock return values for N affiliations. Ensure exception types are imported from the domain layer, not re-declared in test files.

If MultiChapterMembershipService is not yet injectable (no Riverpod provider), instantiate it directly in setUp() for these unit tests — do not block on provider setup.

Testing Requirements

All tests are pure unit tests using flutter_test. Use mockito or a hand-written fake for ContactChapterRepository. Group tests using group() blocks per operation (addChapterAffiliation, removeChapterAffiliation). Use setUp() to initialise a fresh service and mock before each test.

Use throwsA() matchers for exception assertions. Verify mock interactions with verify() to confirm the repository is called with correct arguments. Do not test repository implementation here — that is covered by data-layer tests.

Component
Multi-Chapter Membership Service
service medium
Epic Risks (2)
medium impact medium prob scope

The ±1 day duplicate detection tolerance is specified in the acceptance criteria but timezone handling is not defined. A coordinator in UTC+2 submitting at 23:00 and another in UTC+0 submitting at 01:00 the next calendar day could trigger or miss a duplicate depending on which timezone the comparison uses.

Mitigation & Contingency

Mitigation: Define and document the authoritative timezone for all date comparisons (UTC stored in Supabase, all comparisons performed in UTC). Add timezone boundary unit tests covering the ambiguous ±1 day edges.

Contingency: If false positives or false negatives are reported in production, provide a coordinator-visible audit trail of duplicate detections so erroneous flags can be investigated and cleared manually.

medium impact medium prob technical

The Duplicate Activity Detection Service performs a cross-chapter join query synchronously during the activity submission flow. On slow mobile connections this could cause a perceptible stall on the submission confirmation step, degrading user experience.

Mitigation & Contingency

Mitigation: Pre-fetch the cross-chapter activity dataset for the selected contact immediately when the contact is selected in the activity wizard (not only at submit time), storing the result in the state manager for instant comparison at submission.

Contingency: If latency is still unacceptable, implement a loading indicator on the submit action and add a configurable server-side timeout with graceful degradation: if the check times out, allow submission with a logged 'check skipped' audit entry rather than blocking the user.