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
666 lines
30 KiB
Swift
666 lines
30 KiB
Swift
//
|
|
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import XCTest
|
|
|
|
@testable import SignalServiceKit
|
|
|
|
class SignalRecipientTest: SSKBaseTestSwift {
|
|
|
|
lazy var localAddress = CommonGenerator.address()
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
tsAccountManager.registerForTests(withLocalNumber: localAddress.phoneNumber!, uuid: localAddress.uuid!)
|
|
}
|
|
|
|
func testSelfRecipientWithExistingRecord() {
|
|
write { transaction in
|
|
SignalRecipient.mark(asRegisteredAndGet: self.localAddress, trustLevel: .high, transaction: transaction)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(self.localAddress, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testRecipientWithExistingRecord() {
|
|
let recipient = CommonGenerator.address()
|
|
write { transaction in
|
|
SignalRecipient.mark(asRegisteredAndGet: recipient, trustLevel: .high, transaction: transaction)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(recipient, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
// MARK: - Low Trust
|
|
|
|
func testLowTrustPhoneNumberOnly() {
|
|
// Phone number only recipients are recorded
|
|
let recipient = CommonGenerator.address(hasUUID: false)
|
|
write { transaction in
|
|
SignalRecipient.mark(asRegisteredAndGet: recipient, trustLevel: .low, transaction: transaction)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(recipient, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testLowTrustUUIDOnly() {
|
|
// UUID only recipients are recorded
|
|
let recipient = CommonGenerator.address(hasPhoneNumber: false)
|
|
write { transaction in
|
|
SignalRecipient.mark(asRegisteredAndGet: recipient, trustLevel: .low, transaction: transaction)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(recipient, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testLowTrustFullyQualified() {
|
|
// Fully qualified addresses only record their UUID
|
|
|
|
let recipientAddress = CommonGenerator.address()
|
|
let recipientAddressWithoutUUID = SignalServiceAddress(phoneNumber: recipientAddress.phoneNumber!)
|
|
|
|
XCTAssertNil(recipientAddressWithoutUUID.uuid)
|
|
|
|
write { transaction in
|
|
let recipient = SignalRecipient.mark(
|
|
asRegisteredAndGet: recipientAddress,
|
|
trustLevel: .low,
|
|
transaction: transaction
|
|
)
|
|
|
|
// The impartial address is *not* automatically filled
|
|
// after marking the complete address as registered.
|
|
XCTAssertNil(recipientAddressWithoutUUID.uuid)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(
|
|
recipientAddress,
|
|
transaction: transaction
|
|
))
|
|
XCTAssertFalse(SignalRecipient.isRegisteredRecipient(
|
|
recipientAddressWithoutUUID,
|
|
transaction: transaction
|
|
))
|
|
|
|
XCTAssertEqual(recipient.recipientUUID, recipientAddress.uuidString)
|
|
XCTAssertNil(recipient.recipientPhoneNumber)
|
|
}
|
|
}
|
|
|
|
// MARK: - High Trust
|
|
|
|
func testHighTrustPhoneNumberOnly() {
|
|
// Phone number only recipients are recorded
|
|
let recipient = CommonGenerator.address(hasUUID: false)
|
|
write { transaction in
|
|
SignalRecipient.mark(asRegisteredAndGet: recipient, trustLevel: .high, transaction: transaction)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(recipient, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testHighTrustUUIDOnly() {
|
|
// UUID only recipients are recorded
|
|
let recipient = CommonGenerator.address(hasPhoneNumber: false)
|
|
write { transaction in
|
|
SignalRecipient.mark(asRegisteredAndGet: recipient, trustLevel: .high, transaction: transaction)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(recipient, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testHighTrustFullyQualified() {
|
|
// Fully qualified addresses are recorded in their entirety
|
|
|
|
let recipientAddress = CommonGenerator.address()
|
|
let recipientAddressWithoutUUID = SignalServiceAddress(phoneNumber: recipientAddress.phoneNumber!)
|
|
|
|
XCTAssertNil(recipientAddressWithoutUUID.uuid)
|
|
|
|
write { transaction in
|
|
let recipient = SignalRecipient.mark(
|
|
asRegisteredAndGet: recipientAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// The impartial address is automatically filled
|
|
// after marking the complete address as registered.
|
|
XCTAssertEqual(recipientAddressWithoutUUID.uuid, recipientAddress.uuid)
|
|
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(
|
|
recipientAddress,
|
|
transaction: transaction
|
|
))
|
|
XCTAssertTrue(SignalRecipient.isRegisteredRecipient(
|
|
recipientAddressWithoutUUID,
|
|
transaction: transaction
|
|
))
|
|
|
|
XCTAssertEqual(recipient.recipientUUID, recipientAddress.uuidString)
|
|
XCTAssertEqual(recipient.recipientPhoneNumber, recipientAddress.phoneNumber)
|
|
}
|
|
}
|
|
|
|
func testHighTrustMergeWithInvestedPhoneNumber() {
|
|
// If there is a UUID only contact and a phone number only contact,
|
|
// and then we later find out they are the same user we must merge
|
|
// the two recipients together.
|
|
let uuidOnlyAddress = CommonGenerator.address(hasPhoneNumber: false)
|
|
let phoneNumberOnlyAddress = CommonGenerator.address(hasUUID: false)
|
|
let address = SignalServiceAddress(uuid: uuidOnlyAddress.uuid!, phoneNumber: phoneNumberOnlyAddress.phoneNumber!)
|
|
|
|
write { transaction in
|
|
let uuidRecipient = SignalRecipient.mark(
|
|
asRegisteredAndGet: uuidOnlyAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
let phoneNumberRecipient = SignalRecipient.mark(
|
|
asRegisteredAndGet: phoneNumberOnlyAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
let mergedRecipient = SignalRecipient.mark(
|
|
asRegisteredAndGet: address,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// TODO: test this more thoroughly. right now just confirming we prefer
|
|
// the UUID recipient when no other info is available
|
|
|
|
XCTAssertEqual(mergedRecipient.uniqueId, uuidRecipient.uniqueId)
|
|
}
|
|
}
|
|
|
|
func testHighTrustPhoneNumberChange() {
|
|
let oldAddress = CommonGenerator.address()
|
|
|
|
write { transaction in
|
|
let oldThread = TSContactThread.getOrCreateThread(
|
|
withContactAddress: oldAddress,
|
|
transaction: transaction
|
|
)
|
|
|
|
let messageBuilder = TSIncomingMessageBuilder(
|
|
thread: oldThread,
|
|
authorAddress: oldAddress,
|
|
messageBody: "Test 123"
|
|
)
|
|
let oldMessage = messageBuilder.build()
|
|
oldMessage.anyInsert(transaction: transaction)
|
|
|
|
let oldProfile = OWSUserProfile.getOrBuild(
|
|
for: oldAddress,
|
|
transaction: transaction
|
|
)
|
|
// TODO: It's weird to me that getOrBuild doesn't
|
|
// save the profile if it builds it. Maybe this is
|
|
// a bug?
|
|
oldProfile.anyInsert(transaction: transaction)
|
|
|
|
let oldAccount = SignalAccount(address: oldAddress)
|
|
oldAccount.anyInsert(transaction: transaction)
|
|
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: oldAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
let newAddress = SignalServiceAddress(uuid: oldAddress.uuid!, phoneNumber: CommonGenerator.e164())
|
|
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: newAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
let newThread = TSContactThread.getOrCreateThread(
|
|
withContactAddress: newAddress,
|
|
transaction: transaction
|
|
)
|
|
let newMessage = TSIncomingMessage.anyFetchIncomingMessage(
|
|
uniqueId: oldMessage.uniqueId,
|
|
transaction: transaction
|
|
)!
|
|
let newProfile = OWSUserProfile.getOrBuild(
|
|
for: newAddress,
|
|
transaction: transaction
|
|
)
|
|
let newAccount = SignalAccount.anyFetch(
|
|
uniqueId: oldAccount.uniqueId,
|
|
transaction: transaction
|
|
)!
|
|
|
|
// We maintain the same thread, profile, interactions, etc.
|
|
// after the phone number change. They are updated to reflect
|
|
// the new address.
|
|
XCTAssertEqual(oldAddress.phoneNumber, newAddress.phoneNumber)
|
|
XCTAssertEqual(oldAddress.uuid, newAddress.uuid)
|
|
|
|
XCTAssertEqual(oldThread.uniqueId, newThread.uniqueId)
|
|
XCTAssertNotEqual(oldThread.contactPhoneNumber, newThread.contactPhoneNumber)
|
|
XCTAssertEqual(newAddress, newThread.contactAddress)
|
|
|
|
XCTAssertEqual(oldMessage.uniqueId, newMessage.uniqueId)
|
|
XCTAssertNotEqual(oldMessage.authorPhoneNumber, newMessage.authorPhoneNumber)
|
|
XCTAssertEqual(newAddress, newMessage.authorAddress)
|
|
|
|
XCTAssertEqual(oldProfile.uniqueId, newProfile.uniqueId)
|
|
XCTAssertNotEqual(oldProfile.recipientPhoneNumber, newProfile.recipientPhoneNumber)
|
|
XCTAssertEqual(newAddress, newProfile.address)
|
|
|
|
XCTAssertEqual(oldAccount.uniqueId, newAccount.uniqueId)
|
|
XCTAssertNotEqual(oldAccount.recipientPhoneNumber, newAccount.recipientPhoneNumber)
|
|
XCTAssertEqual(newAddress, newAccount.recipientAddress)
|
|
}
|
|
}
|
|
|
|
func testHighTrustUUIDChange() {
|
|
let oldAddress = CommonGenerator.address()
|
|
|
|
write { transaction in
|
|
let oldThread = TSContactThread.getOrCreateThread(
|
|
withContactAddress: oldAddress,
|
|
transaction: transaction
|
|
)
|
|
|
|
let messageBuilder = TSIncomingMessageBuilder(
|
|
thread: oldThread,
|
|
authorAddress: oldAddress,
|
|
messageBody: "Test 123"
|
|
)
|
|
let oldMessage = messageBuilder.build()
|
|
oldMessage.anyInsert(transaction: transaction)
|
|
|
|
let oldProfile = OWSUserProfile.getOrBuild(
|
|
for: oldAddress,
|
|
transaction: transaction
|
|
)
|
|
// TODO: It's weird to me that getOrBuild doesn't
|
|
// save the profile if it builds it. Maybe this is
|
|
// a bug?
|
|
oldProfile.anyInsert(transaction: transaction)
|
|
|
|
let oldAccount = SignalAccount(address: oldAddress)
|
|
oldAccount.anyInsert(transaction: transaction)
|
|
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: oldAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
let newAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: oldAddress.phoneNumber!)
|
|
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: newAddress,
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
let newThread = TSContactThread.getOrCreateThread(
|
|
withContactAddress: newAddress,
|
|
transaction: transaction
|
|
)
|
|
let newMessage = TSIncomingMessage.anyFetchIncomingMessage(
|
|
uniqueId: oldMessage.uniqueId,
|
|
transaction: transaction
|
|
)!
|
|
let newProfile = OWSUserProfile.getOrBuild(
|
|
for: newAddress,
|
|
transaction: transaction
|
|
)
|
|
let newAccount = SignalAccount.anyFetch(
|
|
uniqueId: oldAccount.uniqueId,
|
|
transaction: transaction
|
|
)!
|
|
|
|
// When the UUID changes, we treat it as a new account. Old data
|
|
// should remain associated with the old UUID, but have the phone
|
|
// number stripped.
|
|
XCTAssertNil(oldAddress.phoneNumber)
|
|
XCTAssertNotEqual(oldAddress.phoneNumber, newAddress.phoneNumber)
|
|
XCTAssertNotEqual(oldAddress.uuid, newAddress.uuid)
|
|
|
|
oldThread.anyReload(transaction: transaction)
|
|
XCTAssertNotEqual(oldThread.uniqueId, newThread.uniqueId)
|
|
XCTAssertNil(oldThread.contactPhoneNumber)
|
|
XCTAssertEqual(newAddress, newThread.contactAddress)
|
|
XCTAssertNotEqual(newAddress, oldThread.contactAddress)
|
|
|
|
XCTAssertEqual(oldMessage.uniqueId, newMessage.uniqueId)
|
|
XCTAssertNil(newMessage.authorPhoneNumber)
|
|
XCTAssertNotEqual(newAddress, newMessage.authorAddress)
|
|
|
|
oldProfile.anyReload(transaction: transaction)
|
|
XCTAssertNotEqual(oldProfile.uniqueId, newProfile.uniqueId)
|
|
XCTAssertNil(oldProfile.recipientPhoneNumber)
|
|
XCTAssertEqual(newAddress, newProfile.address)
|
|
XCTAssertNotEqual(newAddress, oldProfile.address)
|
|
|
|
XCTAssertEqual(oldAccount.uniqueId, newAccount.uniqueId)
|
|
XCTAssertNil(newAccount.recipientPhoneNumber)
|
|
XCTAssertNotEqual(newAddress, newAccount.recipientAddress)
|
|
}
|
|
}
|
|
|
|
// This tests an edge case around uuid<->phone number mapping changes.
|
|
//
|
|
// * There _is_ a SignalRecipient with (u1, p1).
|
|
// * There _is no_ SignalRecipient with u1 or p2.
|
|
// * There is a group g1.
|
|
// * There is a TSGroupMember with (u1, p1, g1).
|
|
// * There is a TSGroupMember with (u2, p2, g1).
|
|
// * u1 becomes associated with p2.
|
|
//
|
|
// Therefore:
|
|
//
|
|
// * p2 must be mapped from u2 -> u1, but there is no existing phoneNumberInstance for p2.
|
|
// * SignalRecipient.clearDBMappings(forPhoneNumber:) must clean up p2 before
|
|
// SignalRecipient.changePhoneNumber() is called on uuidInstance for u1.
|
|
// * Otherwise we'll end up with two TSGroupMembers with (p2, g1) which violates
|
|
// a uniqueness constraint.
|
|
func testDBMappingsEdgeCase1() {
|
|
let uuid1 = UUID()
|
|
let phoneNumber1 = CommonGenerator.e164()
|
|
let uuid2 = UUID()
|
|
let phoneNumber2 = CommonGenerator.e164()
|
|
|
|
write { transaction in
|
|
// There are TSGroupMember instances indicating that both users 1 & 2 are members of the same group.
|
|
let groupThreadFactory = GroupThreadFactory()
|
|
groupThreadFactory.memberAddressesBuilder = { return [] }
|
|
let groupThread = groupThreadFactory.create(transaction: transaction)
|
|
// We construct the TSGroupMember members using specific addresses/mappings.
|
|
let groupMember1 = TSGroupMember(address: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember1.anyInsert(transaction: transaction)
|
|
// NOTE: This member has a uuid.
|
|
let groupMember2 = TSGroupMember(address: SignalServiceAddress(uuid: uuid2,
|
|
phoneNumber: phoneNumber2),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember2.anyInsert(transaction: transaction)
|
|
|
|
// We should have two group members: (u1, p1) and (u2, p2).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
transaction: transaction)
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid2,
|
|
phoneNumber: phoneNumber2),
|
|
transaction: transaction)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
|
|
// User 1 has a SignalRecipient with a high-trust mapping; user 2 does not.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid1, phoneNumber: phoneNumber1),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// uuid1 becomes associated with phoneNumber2.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid1, phoneNumber: phoneNumber2),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// We should still have two group members: (u1, p2) and (u2, nil).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
}
|
|
}
|
|
|
|
// This tests an edge case around uuid<->phone number mapping changes.
|
|
//
|
|
// * There _is_ a SignalRecipient with (u1, p1).
|
|
// * There _is no_ SignalRecipient with u1 or p2.
|
|
// * There is a group g1.
|
|
// * There is a TSGroupMember with (u1, p1, g1).
|
|
// * There is a TSGroupMember with (nil, p2, g1).
|
|
// * u1 becomes associated with p2.
|
|
//
|
|
// Therefore:
|
|
//
|
|
// * p2 must be mapped to u1, but there is no existing phoneNumberInstance for p2.
|
|
// * SignalRecipient.clearDBMappings(forPhoneNumber:) must clean up p2 before
|
|
// SignalRecipient.changePhoneNumber() is called on uuidInstance for u1.
|
|
// * Otherwise we'll end up with two TSGroupMembers with (p2, g1) which violates
|
|
// a uniqueness constraint.
|
|
func testDBMappingsEdgeCase2() {
|
|
let uuid1 = UUID()
|
|
let phoneNumber1 = CommonGenerator.e164()
|
|
let phoneNumber2 = CommonGenerator.e164()
|
|
|
|
write { transaction in
|
|
// There are TSGroupMember instances indicating that both users 1 & 2 are members of the same group.
|
|
let groupThreadFactory = GroupThreadFactory()
|
|
groupThreadFactory.memberAddressesBuilder = { return [] }
|
|
let groupThread = groupThreadFactory.create(transaction: transaction)
|
|
// We construct the TSGroupMember members using specific addresses/mappings.
|
|
let groupMember1 = TSGroupMember(address: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember1.anyInsert(transaction: transaction)
|
|
// NOTE: This member has no uuid.
|
|
let groupMember2 = TSGroupMember(address: SignalServiceAddress(phoneNumber: phoneNumber2),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember2.anyInsert(transaction: transaction)
|
|
|
|
// We should have two group members: (u1, p1) and (nil, p2).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
transaction: transaction)
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(phoneNumber: phoneNumber2),
|
|
transaction: transaction)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
|
|
// User 1 has a SignalRecipient with a high-trust mapping; user 2 does not.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid1, phoneNumber: phoneNumber1),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// uuid1 becomes associated with phoneNumber2.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid1, phoneNumber: phoneNumber2),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// We should now have two group members: (u1, p2), (fake uuid, nil).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
}
|
|
}
|
|
|
|
// This tests an edge case around uuid<->phone number mapping changes.
|
|
//
|
|
// * There _is_ a SignalRecipient with (u1, p1).
|
|
// * There _is no_ SignalRecipient with u1 or p2.
|
|
// * There is a group g1.
|
|
// * There is a TSGroupMember with (u1, p1, g1).
|
|
// * There is a TSGroupMember with (u2, p2, g1).
|
|
// * u2 becomes associated with p1.
|
|
//
|
|
// Therefore:
|
|
//
|
|
// * p1 must be mapped from u1 -> u2, but there is no existing uuidInstance for u2.
|
|
// * SignalRecipient.clearDBMappings(forUuid:) must clean up u2 before
|
|
// SignalRecipient.changePhoneNumber() is called on phoneNumberInstance for p1.
|
|
// * Otherwise we'll end up with two TSGroupMembers with (p1, g1) which violates
|
|
// a uniqueness constraint.
|
|
func testDBMappingsEdgeCase3() {
|
|
let uuid1 = UUID()
|
|
let phoneNumber1 = CommonGenerator.e164()
|
|
let uuid2 = UUID()
|
|
let phoneNumber2 = CommonGenerator.e164()
|
|
|
|
write { transaction in
|
|
// There are TSGroupMember instances indicating that both users 1 & 2 are members of the same group.
|
|
let groupThreadFactory = GroupThreadFactory()
|
|
groupThreadFactory.memberAddressesBuilder = { return [] }
|
|
let groupThread = groupThreadFactory.create(transaction: transaction)
|
|
// We construct the TSGroupMember members using specific addresses/mappings.
|
|
let groupMember1 = TSGroupMember(address: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember1.anyInsert(transaction: transaction)
|
|
// NOTE: This member has a phone number.
|
|
let groupMember2 = TSGroupMember(address: SignalServiceAddress(uuid: uuid2,
|
|
phoneNumber: phoneNumber2),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember2.anyInsert(transaction: transaction)
|
|
|
|
// We should have two group members: (u1, p1) and (u2, p2).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
transaction: transaction)
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid2,
|
|
phoneNumber: phoneNumber2),
|
|
transaction: transaction)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
|
|
// User 1 has a SignalRecipient with a high-trust mapping; user 2 does not.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid1, phoneNumber: phoneNumber1),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// uuid2 becomes associated with phoneNumber1.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid2, phoneNumber: phoneNumber1),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// We should still have two group members: (u2, p1) and (u1, nil).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
}
|
|
}
|
|
|
|
// This tests an edge case around uuid<->phone number mapping changes.
|
|
//
|
|
// * There _is_ a SignalRecipient with (u1, p1).
|
|
// * There _is no_ SignalRecipient with u1 or p2.
|
|
// * There is a group g1.
|
|
// * There is a TSGroupMember with (u1, p1, g1).
|
|
// * There is a TSGroupMember with (u2, p2, g1).
|
|
// * u2 becomes associated with p1.
|
|
//
|
|
// Therefore:
|
|
//
|
|
// * p1 must be mapped from u1 -> u2, but there is no existing uuidInstance for u2.
|
|
// * SignalRecipient.clearDBMappings(forUuid:) must clean up u2 before
|
|
// SignalRecipient.changePhoneNumber() is called on phoneNumberInstance for p1.
|
|
// * Otherwise we'll end up with two TSGroupMembers with (p1, g1) which violates
|
|
// a uniqueness constraint.
|
|
func testDBMappingsEdgeCase4() {
|
|
let uuid1 = UUID()
|
|
let phoneNumber1 = CommonGenerator.e164()
|
|
let uuid2 = UUID()
|
|
|
|
write { transaction in
|
|
// There are TSGroupMember instances indicating that both users 1 & 2 are members of the same group.
|
|
let groupThreadFactory = GroupThreadFactory()
|
|
groupThreadFactory.memberAddressesBuilder = { return [] }
|
|
let groupThread = groupThreadFactory.create(transaction: transaction)
|
|
// We construct the TSGroupMember members using specific addresses/mappings.
|
|
let groupMember1 = TSGroupMember(address: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember1.anyInsert(transaction: transaction)
|
|
// NOTE: This member has no phone number.
|
|
let groupMember2 = TSGroupMember(address: SignalServiceAddress(uuid: uuid2),
|
|
groupThreadId: groupThread.uniqueId,
|
|
lastInteractionTimestamp: NSDate.ows_millisecondTimeStamp())
|
|
groupMember2.anyInsert(transaction: transaction)
|
|
|
|
// We should have two group members: (u1, p1) and (u2, nil).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid1,
|
|
phoneNumber: phoneNumber1),
|
|
transaction: transaction)
|
|
TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(uuid: uuid2),
|
|
transaction: transaction)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
|
|
// User 1 has a SignalRecipient with a high-trust mapping; user 2 does not.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid1, phoneNumber: phoneNumber1),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// uuid2 becomes associated with phoneNumber1.
|
|
SignalRecipient.mark(
|
|
asRegisteredAndGet: SignalServiceAddress(uuid: uuid2, phoneNumber: phoneNumber1),
|
|
trustLevel: .high,
|
|
transaction: transaction
|
|
)
|
|
|
|
// We should now have two group members: (u2, p1), (fake uuid, nil).
|
|
XCTAssertEqual(2, TSGroupMember.groupMembers(in: groupThread.uniqueId, transaction: transaction).count)
|
|
|
|
// We should have three threads.
|
|
XCTAssertEqual(3, TSThread.anyCount(transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testUnregisteredTimestamps() {
|
|
let address = CommonGenerator.address()
|
|
|
|
write {
|
|
let registeredRecipient = SignalRecipient.mark(asRegisteredAndGet: address, trustLevel: .high, transaction: $0)
|
|
XCTAssertNil(registeredRecipient.unregisteredAtTimestamp)
|
|
|
|
SignalRecipient.mark(asUnregistered: address, transaction: $0)
|
|
|
|
let unregisteredRecipient = AnySignalRecipientFinder().signalRecipient(for: address, transaction: $0)
|
|
XCTAssert(unregisteredRecipient!.unregisteredAtTimestamp!.uint64Value > 0)
|
|
|
|
let reregisteredRecipient = SignalRecipient.mark(asRegisteredAndGet: address, trustLevel: .high, transaction: $0)
|
|
XCTAssertNil(reregisteredRecipient.unregisteredAtTimestamp)
|
|
}
|
|
}
|
|
}
|