mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:05:20 +00:00
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:
@@ -7,11 +7,10 @@
|
||||
// `ChatViewModel`, following the `ChatDeliveryCoordinatorContextTests` /
|
||||
// `ChatPrivateConversationCoordinatorContextTests` exemplars.
|
||||
//
|
||||
// Scope note: flows that hit the `FavoritesPersistenceService.shared`
|
||||
// singleton (`isFavorite` / `toggleFavoriteForNoiseKey` / favorite
|
||||
// notifications / `nicknameForPeer` fallbacks) remain covered by the full
|
||||
// view-model tests; the session, migration, encryption-status, and nickname
|
||||
// resolution flows are covered here.
|
||||
// Scope note: favorites are injected through the context
|
||||
// (`favoriteRelationship(forNoiseKey:)` / `addFavorite` / `removeFavorite`),
|
||||
// so the favorite toggle and lookup flows are covered here alongside the
|
||||
// session, migration, encryption-status, and nickname resolution flows.
|
||||
//
|
||||
|
||||
import Testing
|
||||
@@ -171,10 +170,50 @@ private final class MockChatPeerIdentityContext: ChatPeerIdentityContext {
|
||||
func sendFavoriteNotificationViaNostr(noisePublicKey: Data, isFavorite: Bool) {
|
||||
nostrFavoriteNotifications.append((noisePublicKey, isFavorite))
|
||||
}
|
||||
|
||||
// Favorites
|
||||
var favoriteRelationshipsByNoiseKey: [Data: FavoritesPersistenceService.FavoriteRelationship] = [:]
|
||||
var favoriteRelationshipsByPeerID: [PeerID: FavoritesPersistenceService.FavoriteRelationship] = [:]
|
||||
private(set) var addedFavorites: [(noiseKey: Data, nostrPublicKey: String?, nickname: String)] = []
|
||||
private(set) var removedFavorites: [Data] = []
|
||||
|
||||
func favoriteRelationship(forNoiseKey noiseKey: Data) -> FavoritesPersistenceService.FavoriteRelationship? {
|
||||
favoriteRelationshipsByNoiseKey[noiseKey]
|
||||
}
|
||||
|
||||
func favoriteRelationship(forPeerID peerID: PeerID) -> FavoritesPersistenceService.FavoriteRelationship? {
|
||||
favoriteRelationshipsByPeerID[peerID]
|
||||
}
|
||||
|
||||
func addFavorite(noiseKey: Data, nostrPublicKey: String?, nickname: String) {
|
||||
addedFavorites.append((noiseKey, nostrPublicKey, nickname))
|
||||
}
|
||||
|
||||
func removeFavorite(noiseKey: Data) {
|
||||
removedFavorites.append(noiseKey)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func makeFavoriteRelationship(
|
||||
noiseKey: Data,
|
||||
nostrPublicKey: String? = nil,
|
||||
nickname: String = "alice",
|
||||
isFavorite: Bool = false,
|
||||
theyFavoritedUs: Bool = false
|
||||
) -> FavoritesPersistenceService.FavoriteRelationship {
|
||||
FavoritesPersistenceService.FavoriteRelationship(
|
||||
peerNoisePublicKey: noiseKey,
|
||||
peerNostrPublicKey: nostrPublicKey,
|
||||
peerNickname: nickname,
|
||||
isFavorite: isFavorite,
|
||||
theyFavoritedUs: theyFavoritedUs,
|
||||
favoritedAt: Date(timeIntervalSince1970: 0),
|
||||
lastUpdated: Date(timeIntervalSince1970: 0)
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func makePrivateMessage(
|
||||
id: String,
|
||||
@@ -355,4 +394,40 @@ struct ChatPeerIdentityCoordinatorContextTests {
|
||||
context.peerIDsByNickname["carol"] = meshPeer
|
||||
#expect(coordinator.getPeerIDForNickname("carol") == meshPeer)
|
||||
}
|
||||
@Test @MainActor
|
||||
func toggleFavorite_forNoiseKeyPeer_usesInjectedFavoritesStore() async {
|
||||
let context = MockChatPeerIdentityContext()
|
||||
let coordinator = ChatPeerIdentityCoordinator(context: context)
|
||||
let noiseKey = Data(repeating: 0xAB, count: 32)
|
||||
let peerID = PeerID(hexData: noiseKey)
|
||||
|
||||
// No prior relationship: adds a favorite, no Nostr notification yet.
|
||||
coordinator.toggleFavorite(peerID: peerID)
|
||||
#expect(context.addedFavorites.count == 1)
|
||||
#expect(context.addedFavorites.first?.noiseKey == noiseKey)
|
||||
#expect(context.addedFavorites.first?.nickname == "Unknown")
|
||||
#expect(context.nostrFavoriteNotifications.isEmpty)
|
||||
#expect(coordinator.isFavorite(peerID: peerID) == false)
|
||||
|
||||
// They already favorite us: adding sends the mutual notification.
|
||||
context.favoriteRelationshipsByNoiseKey[noiseKey] = makeFavoriteRelationship(
|
||||
noiseKey: noiseKey,
|
||||
theyFavoritedUs: true
|
||||
)
|
||||
coordinator.toggleFavorite(peerID: peerID)
|
||||
#expect(context.addedFavorites.count == 2)
|
||||
#expect(context.addedFavorites.last?.nickname == "alice")
|
||||
#expect(context.nostrFavoriteNotifications.map(\.isFavorite) == [true])
|
||||
|
||||
// Existing favorite: toggling removes it and notifies the unfavorite.
|
||||
context.favoriteRelationshipsByNoiseKey[noiseKey] = makeFavoriteRelationship(
|
||||
noiseKey: noiseKey,
|
||||
isFavorite: true
|
||||
)
|
||||
#expect(coordinator.isFavorite(peerID: peerID) == true)
|
||||
coordinator.toggleFavorite(peerID: peerID)
|
||||
#expect(context.removedFavorites == [noiseKey])
|
||||
#expect(context.nostrFavoriteNotifications.map(\.isFavorite) == [true, false])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user