Skip to content

fix: resilient member lookup across Tharga.Team / MongoDB / Blazor / Service#66

Merged
poxet merged 2 commits into
masterfrom
feature/resilient-member-lookup
May 11, 2026
Merged

fix: resilient member lookup across Tharga.Team / MongoDB / Blazor / Service#66
poxet merged 2 commits into
masterfrom
feature/resilient-member-lookup

Conversation

@poxet
Copy link
Copy Markdown
Contributor

@poxet poxet commented May 11, 2026

Summary

Resolves #64 — the comprehensive .Single(predicate) sweep that follows up on #59. Drives ThrowMoreThanOneMatchException to zero across the Platform stack when Team.Members or ApiKey collections happen to contain duplicate-keyed rows.

  • New public helper Tharga.Team.ResilientMemberLookup with three IEnumerable<T> extensions: PickOneOrDefault (two overloads — team-member-domain and single-context) and ReplaceByReference.
  • Replaced .Single / .SingleOrDefault at 13 call sites across TeamRepository (6), TeamServiceBase (4), TeamComponent.razor / TeamInviteView.razor (2), and ApiKeyAdministrationService (2). Each site picks the first match and logs a Warning carrying the team key, lookup key, and match count so duplicates are findable for out-of-band cleanup.
  • Fixed the symmetric strip-siblings bug in TeamRepository writes: every former Where(x => x.Key != userKey).Union([member]) is now members.ReplaceByReference(target, updated), so duplicate-keyed siblings are preserved on save rather than silently dropped.
  • Promoted the existing internal TeamMemberResolver from Tharga.Team.Blazor into the new public helper; updated 5 razor callers; removed the old helper.

Behaviour change for consumers

  • TeamServiceBase and TeamRepository constructors now accept an optional ILogger<> (default null). Existing subclasses (TeamServiceRepositoryBase, custom TeamService implementations) continue to compile without change. Wire AddLogging() in your composition root to capture the new duplicate-row warnings.
  • ApiKeyAdministrationService constructor adds an optional ILogger<ApiKeyAdministrationService> parameter (default null).

Out of scope

  • Self-healing (deleting duplicate rows during read) — deferred until/unless symptoms persist.
  • UserServiceBase.GetUserAsync race (#65) — separate feature.

Test plan

  • dotnet build -c Release — 0 warnings, 0 errors
  • dotnet test -c Release — 289 / 289 passing (14 new tests)
  • ResilientMemberLookupTests (10 tests) — both PickOneOrDefault overloads and ReplaceByReference, including the strip-siblings preservation test
  • ResilientLookupCallSiteTests (3 tests) — RemoveMemberAsync + TransferOwnershipAsync with duplicate-keyed members
  • ApiKeyAdministrationServiceTests (+1 test) — two stored keys both verify true → first-match returned, no throw

poxet added 2 commits May 11, 2026 12:24
…Service

Resolves #64 — drives ThrowMoreThanOneMatchException to zero
when Team.Members or ApiKey collections contain duplicate-keyed rows.

- New ResilientMemberLookup helper in Tharga.Team exposing
  PickOneOrDefault (two overloads) and ReplaceByReference extensions.
- Replaced .Single/.SingleOrDefault at 13 call sites across
  TeamRepository (6), TeamServiceBase (4), TeamComponent.razor /
  TeamInviteView.razor (2), and ApiKeyAdministrationService (2).
- Fixed the symmetric strip-siblings bug in TeamRepository writes by
  using ReplaceByReference instead of Where(x => x.Key != userKey)
  so duplicate-keyed siblings are preserved on save.
- Promoted the existing internal TeamMemberResolver helper from
  Tharga.Team.Blazor into the new public helper; updated 5 razor
  callers; removed the old internal helper.

14 new tests, 289 / 289 passing.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

@poxet poxet merged commit 8df47a5 into master May 11, 2026
5 of 6 checks passed
@poxet poxet deleted the feature/resilient-member-lookup branch May 11, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resilient member lookup needed in TeamServiceBase.RemoveMemberAsync + TeamComponent.CopyInvitationLink (follow-up to #59)

1 participant