Signal-iOS/SignalServiceKit/tests/Contacts/BlockingManagerTests.swift
Evan Hahn 370ff654e7
Change license to AGPL
Change license to AGPL

This commit:

- Updates the `LICENSE` file

- Start every file with something like:

      // Copyright YEAR_FIRST_PUBLISHED Signal Messenger, LLC
      // SPDX-License-Identifier: AGPL-3.0-only

---

First, I removed existing license headers with this Ruby 3.1.2 script:

    require 'set'

    EXTENSIONS_TO_CHECK = Set['.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift']

    same = 0
    different = 0

    all_files = `git ls-files`.lines.map { |line| line.strip }
    all_files.each do |relative_path|
      if relative_path == 'Pods'
        next
      end

      unless EXTENSIONS_TO_CHECK.include? File.extname(relative_path)
        next
      end

      path = File.expand_path(relative_path)

      contents = File.read(path)
      new_contents = contents.sub(/\/\/\n\/\/  Copyright .*\n\/\/\n\n/, '')

      if contents == new_contents
        same += 1
      else
        different += 1
      end

      File.write(path, new_contents)
    end

    puts "updated #{different} file(s), left #{same} untouched"

I'm sure this script could be improved, but it worked well enough.

Then, I created `Scripts/lint/lint-license-headers` and ran it to auto-
fix a lot of files. This changed the mode of some files, but I think
that's actually desirable. For example,
`SignalServiceKit/src/Util/AppContext.m` previously had a mode of
`0755/-rwxr-xr-x`, and it's now `0644/-rw-r--r--`.

Then I fixed some stragglers and updated the precommit script.

See [a similar change in the Desktop app][0].

[0]: 8bfaf598af
2022-10-13 08:25:37 -05:00

226 lines
11 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import XCTest
@testable import SignalServiceKit
class BlockingManagerTests: SSKBaseTestSwift {
// Some tests will use this to simulate the state as seen by another process
// If working correctly, state should be reloaded
var remoteState = BlockingManager.State._testing_createEmpty()
override func setUp() {
super.setUp()
databaseStorage.read { remoteState.reloadIfNecessary($0) }
}
override func tearDown() {
super.tearDown()
BlockingManager.TestingFlags.optimisticallyCommitSyncToken = false
}
func testAddBlockedAddress() {
// Setup
let generatedAddresses = generateAddresses(count: 30)
let generatedThread = ContactThreadFactory().create()
// Test
expectation(forNotification: BlockingManager.blockListDidChange, object: nil)
databaseStorage.write { writeTx in
blockingManager.addBlockedThread(generatedThread, blockMode: .localShouldNotLeaveGroups, transaction: writeTx)
generatedAddresses.forEach {
blockingManager.addBlockedAddress($0, blockMode: .localShouldNotLeaveGroups, transaction: writeTx)
}
}
// Verify
databaseStorage.read { readTx in
// First, query the whole set of blocked addresses:
// Because these are made up generated addresses, a UUID+e164 that goes in may be low trust
// If that happens, the addresses that come out will be two separate SignalServiceAddresses
// To work around this, we compactMap the result sets to e164/UUID to compare apples to apples
let allFetchedBlockedAddresses = blockingManager.blockedAddresses(transaction: readTx)
let allExpectedBlockedAddresses = generatedAddresses.union([generatedThread.contactAddress])
XCTAssertEqual(
Set(allFetchedBlockedAddresses.compactMap { $0.phoneNumber }),
Set(allExpectedBlockedAddresses.compactMap { $0.phoneNumber })
)
XCTAssertEqual(
Set(allFetchedBlockedAddresses.compactMap { $0.uuidString }),
Set(allExpectedBlockedAddresses.compactMap { $0.uuidString })
)
// Next, ensure that querying an individual address or thread works properly
generatedAddresses.forEach {
XCTAssertTrue(blockingManager.isAddressBlocked($0, transaction: readTx))
}
XCTAssertTrue(blockingManager.isAddressBlocked(generatedThread.contactAddress, transaction: readTx))
XCTAssertTrue(blockingManager.isThreadBlocked(generatedThread, transaction: readTx))
// Since this was a local change, we expect to need a sync message
XCTAssertTrue(blockingManager._testingOnly_needsSyncMessage(readTx))
// Reload the remote state and ensure it sees the up-to-date block list.
let oldToken = remoteState.changeToken
remoteState.reloadIfNecessary(readTx)
let newToken = remoteState.changeToken
XCTAssertNotEqual(oldToken, newToken)
XCTAssertEqual(remoteState.blockedPhoneNumbers, Set(allExpectedBlockedAddresses.compactMap { $0.phoneNumber }))
XCTAssertEqual(remoteState.blockedUUIDStrings, Set(allExpectedBlockedAddresses.compactMap { $0.uuidString }))
XCTAssertEqual(remoteState.blockedGroupMap, [:])
}
waitForExpectations(timeout: 3)
}
func testRemoveBlockedAddress() {
// Setup
let blockedContact = CommonGenerator.address()
let unblockedContact = CommonGenerator.address()
let blockedThread = ContactThreadFactory().create()
let unblockedThread = ContactThreadFactory().create()
databaseStorage.write {
blockingManager.addBlockedAddress(blockedContact, blockMode: .localShouldLeaveGroups, transaction: $0)
blockingManager.addBlockedAddress(blockedContact, blockMode: .localShouldLeaveGroups, transaction: $0)
blockingManager.addBlockedThread(blockedThread, blockMode: .localShouldLeaveGroups, transaction: $0)
blockingManager.addBlockedThread(unblockedThread, blockMode: .localShouldLeaveGroups, transaction: $0)
}
databaseStorage.write {
remoteState.reloadIfNecessary($0)
blockingManager._testingOnly_clearNeedsSyncMessage($0)
}
// Test
databaseStorage.write {
blockingManager.removeBlockedAddress(unblockedContact, wasLocallyInitiated: true, transaction: $0)
blockingManager.removeBlockedThread(unblockedThread, wasLocallyInitiated: true, transaction: $0)
}
// Verify
databaseStorage.read { readTx in
XCTAssertTrue(blockingManager.isAddressBlocked(blockedContact, transaction: readTx))
XCTAssertTrue(blockingManager.isThreadBlocked(blockedThread, transaction: readTx))
XCTAssertFalse(blockingManager.isAddressBlocked(unblockedContact, transaction: readTx))
XCTAssertFalse(blockingManager.isThreadBlocked(unblockedThread, transaction: readTx))
// Since this was a local change, we expect to need a sync message
XCTAssertTrue(blockingManager._testingOnly_needsSyncMessage(readTx))
// Reload the remote state and ensure it sees the up-to-date block list.
let oldToken = remoteState.changeToken
remoteState.reloadIfNecessary(readTx)
let newToken = remoteState.changeToken
XCTAssertNotEqual(oldToken, newToken)
let expectedRemainingBlocks = [blockedContact, blockedThread.contactAddress]
XCTAssertEqual(remoteState.blockedUUIDStrings, Set(expectedRemainingBlocks.compactMap { $0.uuidString}))
XCTAssertEqual(remoteState.blockedPhoneNumbers, Set(expectedRemainingBlocks.compactMap { $0.phoneNumber}))
XCTAssertEqual(remoteState.blockedGroupMap, [:])
}
}
func testIncomingSyncMessage() {
// Setup
let victimBlockedAddress = CommonGenerator.address()
let victimGroupId = TSGroupModel.generateRandomV1GroupId()
let blockedUUIDs = Set((0..<10).map { _ in UUID() })
let blockedE164s = Set((0..<10).map { _ in CommonGenerator.e164() })
let blockedGroupIds = Set((0..<10).map { _ in TSGroupModel.generateRandomV1GroupId() })
databaseStorage.write { writeTx in
// For our initial state, we insert the victims that we expect to see removed
// and a single item out of our block sets that we expect to see persisted.
blockingManager.addBlockedAddress(
victimBlockedAddress,
blockMode: .localShouldNotLeaveGroups,
transaction: writeTx)
blockingManager.addBlockedGroup(
groupId: victimGroupId,
blockMode: .localShouldNotLeaveGroups,
transaction: writeTx)
blockingManager.addBlockedGroup(
groupId: blockedGroupIds.randomElement()!,
blockMode: .localShouldNotLeaveGroups,
transaction: writeTx)
blockingManager.addBlockedAddress(
SignalServiceAddress(uuid: blockedUUIDs.randomElement()!),
blockMode: .localShouldNotLeaveGroups,
transaction: writeTx)
blockingManager.addBlockedAddress(
SignalServiceAddress(phoneNumber: blockedE164s.randomElement()!),
blockMode: .localShouldNotLeaveGroups,
transaction: writeTx)
}
// Test
databaseStorage.write { writeTx in
blockingManager.processIncomingSync(
blockedPhoneNumbers: blockedE164s,
blockedUUIDs: blockedUUIDs,
blockedGroupIds: blockedGroupIds,
transaction: writeTx)
}
// Verify
databaseStorage.read { readTx in
// First, our incoming sync message should've cleared our "NeedsSync" flag
XCTAssertFalse(blockingManager._testingOnly_needsSyncMessage(readTx))
// Verify our victims aren't blocked anymore
XCTAssertFalse(blockingManager.isAddressBlocked(victimBlockedAddress, transaction: readTx))
XCTAssertFalse(blockingManager.isGroupIdBlocked(victimGroupId, transaction: readTx))
// Verify everything that came through our sync message is now blocked
XCTAssertTrue(blockedE164s
.map { SignalServiceAddress(phoneNumber: $0) }
.allSatisfy { blockingManager.isAddressBlocked($0, transaction: readTx) })
XCTAssertTrue(blockedUUIDs
.map { SignalServiceAddress(uuid: $0) }
.allSatisfy { blockingManager.isAddressBlocked($0, transaction: readTx) })
XCTAssertTrue(blockedGroupIds
.allSatisfy { blockingManager.isGroupIdBlocked($0, transaction: readTx) })
// Finally, verify that any remote state agrees
remoteState.reloadIfNecessary(readTx)
XCTAssertEqual(Set(remoteState.blockedGroupMap.keys), blockedGroupIds)
XCTAssertEqual(remoteState.blockedUUIDStrings, Set(blockedUUIDs.map { $0.uuidString }))
XCTAssertEqual(remoteState.blockedPhoneNumbers, blockedE164s)
}
}
func testSendSyncMessage() {
// Setup
// ensure local client has necessary "registered" state
identityManager.generateNewIdentityKey(for: .aci)
tsAccountManager.registerForTests(withLocalNumber: CommonGenerator.e164(), uuid: UUID())
BlockingManager.TestingFlags.optimisticallyCommitSyncToken = true
// Test
expectation(forNotification: BlockingManager.blockListDidChange, object: nil)
let neededSyncMessage = databaseStorage.write { writeTx -> Bool in
blockingManager.addBlockedAddress(CommonGenerator.address(), blockMode: .localShouldLeaveGroups, transaction: writeTx)
return blockingManager._testingOnly_needsSyncMessage(writeTx)
}
waitForExpectations(timeout: 3)
// Verify
databaseStorage.read {
XCTAssertTrue(neededSyncMessage)
XCTAssertFalse(blockingManager._testingOnly_needsSyncMessage($0))
}
}
// MARK: - Helpers
func generateAddresses(count: UInt) -> Set<SignalServiceAddress> {
Set((0..<count).map { _ in
let hasPhoneNumber = Int.random(in: 0...2) == 0
let hasUUID = !hasPhoneNumber || Bool.random()
return CommonGenerator.address(hasUUID: hasUUID, hasPhoneNumber: hasPhoneNumber)
})
}
}