Cover mute store keys and notification skip paths

This commit is contained in:
Taksh 2026-08-29 16:50:07 +05:30
parent e7051a431d
commit bef6f0106f
2 changed files with 126 additions and 0 deletions

View File

@ -0,0 +1,75 @@
//
// ConversationNotificationMuteStoreTests.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import Foundation
import Testing
@testable import bitchat
struct ConversationNotificationMuteStoreTests {
private func isolatedDefaults() -> UserDefaults {
let suite = "ConversationNotificationMuteStoreTests-\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
return defaults
}
@Test func directMuteKeysOnTheFingerprint() throws {
let defaults = isolatedDefaults()
let scope = try #require(ConversationNotificationMuteStore.Scope.direct(fingerprint: "ABCD1234"))
ConversationNotificationMuteStore.setMuted(true, for: scope, in: defaults)
#expect(ConversationNotificationMuteStore.isMuted(scope, in: defaults))
#expect(ConversationNotificationMuteStore.mutedKeys(in: defaults) == ["dm:abcd1234"])
}
/// There is no routing-peer-ID fallback on purpose: peer IDs rotate, so a
/// mute keyed on one would silently stop applying while the UI still
/// showed the conversation muted.
@Test func directMuteIsUnavailableWithoutAFingerprint() {
#expect(ConversationNotificationMuteStore.Scope.direct(fingerprint: nil) == nil)
#expect(ConversationNotificationMuteStore.Scope.direct(fingerprint: "") == nil)
#expect(ConversationNotificationMuteStore.Scope.direct(fingerprint: " ") == nil)
}
/// The bug this replaced: writing `dm:<fingerprint>` on the toggle side and
/// looking up `dm:<peerid>` on the notification side left the mute silently
/// ineffective. One resolver means one key.
@Test func aFingerprintKeyIsNeverConfusedWithAPeerIDKey() throws {
let defaults = isolatedDefaults()
let byFingerprint = try #require(ConversationNotificationMuteStore.Scope.direct(fingerprint: "ABCD1234"))
ConversationNotificationMuteStore.setMuted(true, for: byFingerprint, in: defaults)
// A scope built from what used to be the peer-ID fallback must not read
// as muted.
let byPeerID = try #require(ConversationNotificationMuteStore.Scope.direct(fingerprint: "deadbeef"))
#expect(!ConversationNotificationMuteStore.isMuted(byPeerID, in: defaults))
}
@Test func geohashMuteIsCaseInsensitive() {
let defaults = isolatedDefaults()
let scope = ConversationNotificationMuteStore.Scope.geohash("U4PRUY")
ConversationNotificationMuteStore.setMuted(true, for: scope, in: defaults)
#expect(
ConversationNotificationMuteStore.isMuted(
.geohash("u4pruy"),
in: defaults
)
)
}
@Test func unmuteRemovesKeyAndResetClearsAll() throws {
let defaults = isolatedDefaults()
let dm = try #require(ConversationNotificationMuteStore.Scope.direct(fingerprint: "fp"))
let geo = ConversationNotificationMuteStore.Scope.geohash("u4pru")
ConversationNotificationMuteStore.setMuted(true, for: dm, in: defaults)
ConversationNotificationMuteStore.setMuted(true, for: geo, in: defaults)
ConversationNotificationMuteStore.setMuted(false, for: dm, in: defaults)
#expect(!ConversationNotificationMuteStore.isMuted(dm, in: defaults))
#expect(ConversationNotificationMuteStore.isMuted(geo, in: defaults))
ConversationNotificationMuteStore.reset(in: defaults)
#expect(ConversationNotificationMuteStore.mutedKeys(in: defaults).isEmpty)
}
}

View File

@ -116,6 +116,57 @@ final class NotificationServiceTests: XCTestCase {
XCTAssertEqual(deliverer.requests[1].content.interruptionLevel, .timeSensitive)
XCTAssertEqual(deliverer.requests[1].content.body, "2 people around")
}
func test_sendPrivateMessageNotification_skipsWhenConversationMuted() {
let deliverer = RecordingNotificationRequestDeliverer()
let peerID = PeerID(str: "deadbeefdeadbeef")
let scope = ConversationNotificationMuteStore.Scope.direct(fingerprint: "fp1")
let service = NotificationService(
isRunningTestsProvider: { false },
authorizer: RecordingNotificationAuthorizer(),
requestDeliverer: deliverer,
conversationMutedProvider: { $0 == scope }
)
service.sendPrivateMessageNotification(from: "Alice", message: "hi", peerID: peerID, fingerprint: "fp1")
XCTAssertTrue(deliverer.requests.isEmpty)
}
/// No fingerprint means no mute can have been keyed (the toggle is disabled
/// until one exists), so the notification must still be delivered rather
/// than suppressed by a peer-ID guess that rotation would invalidate.
func test_sendPrivateMessageNotification_deliversWhenNoFingerprintIsKnown() {
let deliverer = RecordingNotificationRequestDeliverer()
let peerID = PeerID(str: "deadbeefdeadbeef")
let service = NotificationService(
isRunningTestsProvider: { false },
authorizer: RecordingNotificationAuthorizer(),
requestDeliverer: deliverer,
conversationMutedProvider: { _ in true }
)
service.sendPrivateMessageNotification(from: "Alice", message: "hi", peerID: peerID, fingerprint: nil)
XCTAssertEqual(deliverer.requests.count, 1)
}
func test_sendGeohashActivityNotification_skipsWhenChannelMuted() {
let deliverer = RecordingNotificationRequestDeliverer()
let service = NotificationService(
isRunningTestsProvider: { false },
authorizer: RecordingNotificationAuthorizer(),
requestDeliverer: deliverer,
conversationMutedProvider: {
if case .geohash("u4pru") = $0 { return true }
return false
}
)
service.sendGeohashActivityNotification(geohash: "u4pru", bodyPreview: "hello")
XCTAssertTrue(deliverer.requests.isEmpty)
}
}
private final class RecordingNotificationAuthorizer: NotificationAuthorizing {