Signal-iOS/SignalServiceKit/tests/Contacts/BlockingManagerTests.swift
2025-04-03 17:45:19 -05:00

239 lines
11 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
import XCTest
@testable import SignalServiceKit
class BlockingManagerTests: SSKBaseTest {
// Some tests will use this to simulate the state as seen by another process
private var otherBlockingManager: BlockingManager!
private var blockingManager: BlockingManager { SSKEnvironment.shared.blockingManagerRef }
override func setUp() {
super.setUp()
otherBlockingManager = BlockingManager(
appReadiness: AppReadinessMock(),
blockedGroupStore: BlockedGroupStore(),
blockedRecipientStore: BlockedRecipientStore()
)
}
override func tearDown() {
let flushTask = blockingManager.flushSyncQueueTask()
let otherFlushTask = otherBlockingManager.flushSyncQueueTask()
let flushExpectation = self.expectation(description: "flush sync queues")
Task {
try! await flushTask.value
try! await otherFlushTask.value
flushExpectation.fulfill()
}
self.wait(for: [flushExpectation], timeout: 60)
super.tearDown()
}
func testAddBlockedAddress() {
// Setup
let aci = Aci.randomForTesting()
// Test
expectation(forNotification: BlockingManager.blockListDidChange, object: nil)
SSKEnvironment.shared.databaseStorageRef.write { tx in
_ = otherBlockingManager.blockedAddresses(transaction: tx)
let oldChangeToken = blockingManager.fetchChangeToken(tx: tx)
blockingManager.addBlockedAci(aci, blockMode: .localShouldNotLeaveGroups, tx: tx)
let newChangeToken = blockingManager.fetchChangeToken(tx: tx)
// Since this was a local change, we expect to need a sync message
XCTAssertGreaterThan(newChangeToken, oldChangeToken)
}
// Verify
SSKEnvironment.shared.databaseStorageRef.read { tx in
// First, query the whole set of blocked addresses:
let blockedAddresses = blockingManager.blockedAddresses(transaction: tx)
XCTAssertEqual(blockedAddresses.map { $0.aci }, [aci])
XCTAssertTrue(blockingManager.isAddressBlocked(SignalServiceAddress(aci), transaction: tx))
// Reload the remote state and ensure it sees the up-to-date block list.
XCTAssertEqual(otherBlockingManager.blockedAddresses(transaction: tx).map { $0.aci }, [aci])
}
waitForExpectations(timeout: 3)
}
func testRemoveBlockedAddress() {
// Setup
let blockedAci = Aci.randomForTesting()
let unblockedAci = Aci.randomForTesting()
SSKEnvironment.shared.databaseStorageRef.write { tx in
blockingManager.addBlockedAci(blockedAci, blockMode: .localShouldLeaveGroups, tx: tx)
blockingManager.addBlockedAci(unblockedAci, blockMode: .localShouldLeaveGroups, tx: tx)
_ = otherBlockingManager.blockedAddresses(transaction: tx)
}
let oldChangeToken = SSKEnvironment.shared.databaseStorageRef.read { tx in
return blockingManager.fetchChangeToken(tx: tx)
}
// Test
SSKEnvironment.shared.databaseStorageRef.write { tx in
blockingManager.removeBlockedAddress(SignalServiceAddress(unblockedAci), wasLocallyInitiated: true, transaction: tx)
}
// Verify
SSKEnvironment.shared.databaseStorageRef.read { tx in
XCTAssertTrue(blockingManager.isAddressBlocked(SignalServiceAddress(blockedAci), transaction: tx))
XCTAssertFalse(blockingManager.isAddressBlocked(SignalServiceAddress(unblockedAci), transaction: tx))
// Since this was a local change, we expect to need a sync message
let newChangeToken = blockingManager.fetchChangeToken(tx: tx)
XCTAssertGreaterThan(newChangeToken, oldChangeToken)
// Reload the remote state and ensure it sees the up-to-date block list.
XCTAssertEqual(otherBlockingManager.blockedAddresses(transaction: tx).map(\.aci), [blockedAci])
}
}
func testIncomingSyncMessage() throws {
// Setup
let noLongerBlockedAci = Aci.randomForTesting()
let noLongerBlockedPhoneNumber = E164("+17635550100")!
let noLongerBlockedGroupParams = try GroupSecretParams.generate()
let stillBlockedAci = Aci.randomForTesting()
let stillBlockedPhoneNumber = E164("+17635550101")!
let stillBlockedGroupParams = try GroupSecretParams.generate()
let newlyBlockedAci = Aci.randomForTesting()
let newlyBlockedPhoneNumber = E164("+17635550101")!
let newlyBlockedGroupParams = try GroupSecretParams.generate()
try SSKEnvironment.shared.databaseStorageRef.write { tx in
blockingManager.addBlockedAddress(
SignalServiceAddress(noLongerBlockedAci),
blockMode: .localShouldNotLeaveGroups,
transaction: tx
)
blockingManager.addBlockedAddress(
SignalServiceAddress(noLongerBlockedPhoneNumber),
blockMode: .localShouldNotLeaveGroups,
transaction: tx
)
TSGroupThread.forUnitTest(
masterKey: try noLongerBlockedGroupParams.getMasterKey()
).anyInsert(transaction: tx)
blockingManager.addBlockedGroupId(
try noLongerBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData,
blockMode: .localShouldNotLeaveGroups,
transaction: tx
)
blockingManager.addBlockedAddress(
SignalServiceAddress(stillBlockedAci),
blockMode: .localShouldNotLeaveGroups,
transaction: tx
)
blockingManager.addBlockedAddress(
SignalServiceAddress(stillBlockedPhoneNumber),
blockMode: .localShouldNotLeaveGroups,
transaction: tx
)
TSGroupThread.forUnitTest(
masterKey: try stillBlockedGroupParams.getMasterKey()
).anyInsert(transaction: tx)
blockingManager.addBlockedGroupId(
try stillBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData,
blockMode: .localShouldNotLeaveGroups,
transaction: tx
)
_ = otherBlockingManager.blockedAddresses(transaction: tx)
}
// Test
try SSKEnvironment.shared.databaseStorageRef.write { tx in
blockingManager.processIncomingSync(
blockedPhoneNumbers: Set([stillBlockedPhoneNumber, newlyBlockedPhoneNumber].map(\.stringValue)),
blockedAcis: [stillBlockedAci, newlyBlockedAci],
blockedGroupIds: [
try stillBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData,
try newlyBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData,
],
tx: tx
)
}
// Verify
try SSKEnvironment.shared.databaseStorageRef.read { readTx in
// First, our incoming sync message should've cleared our "NeedsSync" flag
XCTAssertEqual(
blockingManager.fetchChangeToken(tx: readTx),
blockingManager.fetchLastSyncedChangeToken(tx: readTx)
)
// Verify our victims aren't blocked anymore
XCTAssertFalse(blockingManager.isAddressBlocked(SignalServiceAddress(noLongerBlockedAci), transaction: readTx))
XCTAssertFalse(blockingManager.isAddressBlocked(SignalServiceAddress(noLongerBlockedPhoneNumber), transaction: readTx))
XCTAssertFalse(blockingManager.isGroupIdBlocked(try noLongerBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData, transaction: readTx))
XCTAssertTrue(blockingManager.isAddressBlocked(SignalServiceAddress(stillBlockedAci), transaction: readTx))
XCTAssertTrue(blockingManager.isAddressBlocked(SignalServiceAddress(stillBlockedPhoneNumber), transaction: readTx))
XCTAssertTrue(blockingManager.isGroupIdBlocked(try stillBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData, transaction: readTx))
XCTAssertTrue(blockingManager.isAddressBlocked(SignalServiceAddress(newlyBlockedAci), transaction: readTx))
XCTAssertTrue(blockingManager.isAddressBlocked(SignalServiceAddress(newlyBlockedPhoneNumber), transaction: readTx))
XCTAssertTrue(blockingManager.isGroupIdBlocked(try newlyBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData, transaction: readTx))
// Finally, verify that any remote state agrees
let otherBlockedAddresses = otherBlockingManager.blockedAddresses(transaction: readTx)
let expectedBlockedAddresses = [
SignalServiceAddress(stillBlockedAci),
SignalServiceAddress(stillBlockedPhoneNumber),
SignalServiceAddress(newlyBlockedAci),
SignalServiceAddress(newlyBlockedPhoneNumber),
]
XCTAssertEqual(Set(otherBlockedAddresses), Set(expectedBlockedAddresses))
let otherBlockedGroupIds = try otherBlockingManager.blockedGroupIds(transaction: readTx)
let expectedBlockedGroupIds = [
try stillBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData,
try newlyBlockedGroupParams.getPublicParams().getGroupIdentifier().serialize().asData,
]
XCTAssertEqual(Set(otherBlockedGroupIds), Set(expectedBlockedGroupIds))
}
}
@MainActor
func testSendSyncMessage() async {
// Setup
// ensure local client has necessary "registered" state
let identityManager = DependenciesBridge.shared.identityManager
identityManager.generateAndPersistNewIdentityKey(for: .aci)
SSKEnvironment.shared.databaseStorageRef.write { tx in
(DependenciesBridge.shared.registrationStateChangeManager as! RegistrationStateChangeManagerImpl).registerForTests(
localIdentifiers: .forUnitTests,
tx: tx
)
}
SSKEnvironment.shared.messageSenderJobQueueRef.setUp()
let messageSender = SSKEnvironment.shared.messageSenderRef as! FakeMessageSender
messageSender.stubbedFailingErrors = [nil]
await withCheckedContinuation { continuation in
messageSender.sendMessageWasCalledBlock = { _ in continuation.resume() }
// Test
SSKEnvironment.shared.databaseStorageRef.write { tx in
blockingManager.addBlockedAci(Aci.randomForTesting(), blockMode: .localShouldLeaveGroups, tx: tx)
}
}
// Verify
XCTAssertEqual(messageSender.sentMessages.count, 1)
XCTAssert(messageSender.sentMessages.first! is OWSBlockedPhoneNumbersMessage)
}
}