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,12 @@
|
||||
// `ChatViewModel`, following the `ChatDeliveryCoordinatorContextTests` /
|
||||
// `ChatPrivateConversationCoordinatorContextTests` exemplars.
|
||||
//
|
||||
// Scope note: `handleVerifyResponsePayload` requires a real Ed25519 signature
|
||||
// and posts via `NotificationService.shared`; it remains covered by the full
|
||||
// view-model/integration tests. Challenge handling, QR kickoff, fingerprint
|
||||
// verification, and verified-set loading are covered here
|
||||
// (`VerificationService.shared` is only used for pure payload build/parse).
|
||||
// Scope note: `handleVerifyResponsePayload` requires a real Ed25519
|
||||
// signature; it remains covered by the full view-model/integration tests.
|
||||
// Challenge handling, QR kickoff, fingerprint verification, verified-set
|
||||
// loading, and the mutual-verification notification (posted through the
|
||||
// injected context) are covered here (`VerificationService.shared` is only
|
||||
// used for pure payload build/parse).
|
||||
//
|
||||
|
||||
import Testing
|
||||
@@ -117,6 +118,13 @@ private final class MockChatVerificationContext: ChatVerificationContext {
|
||||
func sendVerifyResponse(to peerID: PeerID, noiseKeyHex: String, nonceA: Data) {
|
||||
sentResponses.append((peerID, noiseKeyHex, nonceA))
|
||||
}
|
||||
|
||||
// Notifications
|
||||
private(set) var postedLocalNotifications: [(title: String, body: String, identifier: String)] = []
|
||||
|
||||
func postLocalNotification(title: String, body: String, identifier: String) {
|
||||
postedLocalNotifications.append((title, body, identifier))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
@@ -272,6 +280,35 @@ struct ChatVerificationCoordinatorContextTests {
|
||||
await waitForMainQueue()
|
||||
#expect(context.encryptionStatuses[peerID] == .noiseHandshaking)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func handleVerifyChallengePayload_postsMutualVerificationToastOncePerMinute() async {
|
||||
let context = MockChatVerificationContext()
|
||||
let coordinator = ChatVerificationCoordinator(context: context)
|
||||
let peerID = PeerID(str: "1122334455667788")
|
||||
let myHex = context.myNoiseStaticKey.hexEncodedString()
|
||||
context.fingerprintsByPeerID[peerID] = "fp-mutual"
|
||||
context.verifiedFingerprints = ["fp-mutual"]
|
||||
|
||||
coordinator.handleVerifyChallengePayload(
|
||||
from: peerID,
|
||||
payload: makeVerifyChallengeTLV(noiseKeyHex: myHex, nonceA: Data(repeating: 0x07, count: 16))
|
||||
)
|
||||
|
||||
// Already-verified peer challenging us: mutual-verification toast.
|
||||
#expect(context.postedLocalNotifications.count == 1)
|
||||
#expect(context.postedLocalNotifications.first?.title == "Mutual verification")
|
||||
#expect(context.postedLocalNotifications.first?.body.hasSuffix("verified each other") == true)
|
||||
#expect(context.postedLocalNotifications.first?.identifier.hasPrefix("verify-mutual-") == true)
|
||||
|
||||
// A fresh nonce inside the per-fingerprint toast cooldown stays silent.
|
||||
coordinator.handleVerifyChallengePayload(
|
||||
from: peerID,
|
||||
payload: makeVerifyChallengeTLV(noiseKeyHex: myHex, nonceA: Data(repeating: 0x08, count: 16))
|
||||
)
|
||||
#expect(context.postedLocalNotifications.count == 1)
|
||||
#expect(context.sentResponses.count == 2)
|
||||
}
|
||||
}
|
||||
|
||||
/// The installed callbacks hop through `DispatchQueue.main.async`; tests must
|
||||
|
||||
Reference in New Issue
Block a user