Inject notification/favorites singletons through coordinator contexts

NotificationService.shared and FavoritesPersistenceService.shared
accesses across nine coordinators/components consolidate into
ChatViewModel witnesses behind intent-named context members
(notifyPrivateMessage, notifyMention, favoriteRelationship(forNoiseKey:),
allFavoriteRelationships, postLocalNotification, ...). Singleton reach
from coordinators is now zero; nine new tests cover previously
untestable notification/favorites flows.

Also root-causes the long-flaky gift-wrap dedup test: parallel tests
share LocationChannelManager.shared, so channel-switch tests trigger
clearProcessedNostrEvents() on every live ChatViewModel, wiping the
dedup record mid-test. The invariant (forged-signature copies never
poison dedup) now lives as a deterministic NostrInboundPipeline
mock-context test; two Schnorr concurrency probes added along the way
stay as regression guards for the P256K shared-context assumptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-10 23:04:20 +02:00
co-authored by Claude Fable 5
parent 638f3f5005
commit cc76086615
19 changed files with 820 additions and 116 deletions
+11 -1
View File
@@ -20,6 +20,11 @@ protocol NostrInboundPipelineContext: AnyObject {
func isNostrBlocked(pubkeyHexLowercased: String) -> Bool
func displayNameForNostrPubkey(_ pubkeyHex: String) -> String
// MARK: Favorites bridge
/// All favorite relationships, used to bridge a Nostr pubkey back to a
/// Noise key on the inbound DM path.
func allFavoriteRelationships() -> [FavoritesPersistenceService.FavoriteRelationship]
// MARK: Presence & key mapping
func setGeoNickname(_ nickname: String, forPubkey pubkeyHex: String)
/// Records the Nostr pubkey behind a (possibly virtual) peer ID.
@@ -53,6 +58,10 @@ extension ChatViewModel: NostrInboundPipelineContext {
deduplicationService.hasProcessedNostrEvent(eventID)
}
func allFavoriteRelationships() -> [FavoritesPersistenceService.FavoriteRelationship] {
Array(FavoritesPersistenceService.shared.favorites.values)
}
func recordProcessedNostrEvent(_ eventID: String) {
deduplicationService.recordNostrEvent(eventID)
}
@@ -437,7 +446,8 @@ final class NostrInboundPipeline {
/// the favorites glue in `ChatNostrCoordinator` delegates to it.
@MainActor
func findNoiseKey(for nostrPubkey: String) -> Data? {
let favorites = FavoritesPersistenceService.shared.favorites.values
guard let context else { return nil }
let favorites = context.allFavoriteRelationships()
var npubToMatch = nostrPubkey
if !nostrPubkey.hasPrefix("npub") {