690 lines
30 KiB
Swift
690 lines
30 KiB
Swift
//
|
|
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import XCTest
|
|
import Contacts
|
|
@testable import Signal
|
|
@testable import SignalMessaging
|
|
@testable import SignalServiceKit
|
|
import Contacts
|
|
|
|
// TODO: We might be able to merge this with OWSFakeContactsManager.
|
|
@objc
|
|
class GRDBFullTextSearcherContactsManager: NSObject, ContactsManagerProtocol {
|
|
func isSystemContactWithSignalAccount(_ address: SignalServiceAddress) -> Bool {
|
|
false
|
|
}
|
|
|
|
func isSystemContactWithSignalAccount(_ address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
|
false
|
|
}
|
|
|
|
func hasNameInSystemContacts(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
|
false
|
|
}
|
|
|
|
private var mockDisplayNameMap = [SignalServiceAddress: String]()
|
|
|
|
func setMockDisplayName(_ name: String, for address: SignalServiceAddress) {
|
|
mockDisplayNameMap[address] = name
|
|
}
|
|
|
|
func comparableName(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> String {
|
|
self.displayName(for: address)
|
|
}
|
|
|
|
func comparableName(for signalAccount: SignalAccount, transaction: SDSAnyReadTransaction) -> String {
|
|
self.displayName(for: signalAccount.recipientAddress)
|
|
}
|
|
|
|
func displayName(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> String {
|
|
self.displayName(for: address)
|
|
}
|
|
|
|
func displayNames(forAddresses addresses: [SignalServiceAddress], transaction: SDSAnyReadTransaction) -> [String] {
|
|
return addresses.map { displayName(for: $0) }
|
|
}
|
|
|
|
func shortDisplayName(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> String {
|
|
self.displayName(for: address)
|
|
}
|
|
|
|
func displayName(for address: SignalServiceAddress) -> String {
|
|
mockDisplayNameMap[address] ?? ""
|
|
}
|
|
|
|
public func displayName(for signalAccount: SignalAccount) -> String {
|
|
"Fake name"
|
|
}
|
|
|
|
public func displayName(for thread: TSThread, transaction: SDSAnyReadTransaction) -> String {
|
|
"Fake name"
|
|
}
|
|
|
|
public func displayNameWithSneakyTransaction(thread: TSThread) -> String {
|
|
"Fake name"
|
|
}
|
|
|
|
func nameComponents(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> PersonNameComponents? {
|
|
PersonNameComponents()
|
|
}
|
|
|
|
func signalAccounts() -> [SignalAccount] {
|
|
[]
|
|
}
|
|
|
|
func isSystemContactWithSneakyTransaction(phoneNumber: String) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func isSystemContact(phoneNumber: String, transaction: SDSAnyReadTransaction) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func isSystemContactWithSneakyTransaction(address: SignalServiceAddress) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func isSystemContact(address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func isSystemContact(withSignalAccount recipientId: String) -> Bool {
|
|
true
|
|
}
|
|
|
|
func isSystemContact(withSignalAccount phoneNumber: String, transaction: SDSAnyReadTransaction) -> Bool {
|
|
true
|
|
}
|
|
|
|
func compare(signalAccount left: SignalAccount, with right: SignalAccount) -> ComparisonResult {
|
|
owsFailDebug("if this method ends up being used by the tests, we should provide a better implementation.")
|
|
|
|
return .orderedAscending
|
|
}
|
|
|
|
public func sortSignalServiceAddresses(_ addresses: [SignalServiceAddress],
|
|
transaction: SDSAnyReadTransaction) -> [SignalServiceAddress] {
|
|
addresses
|
|
}
|
|
|
|
func cnContact(withId contactId: String?) -> CNContact? {
|
|
nil
|
|
}
|
|
|
|
func avatarData(forCNContactId contactId: String?) -> Data? {
|
|
nil
|
|
}
|
|
|
|
func avatarImage(forCNContactId contactId: String?) -> UIImage? {
|
|
nil
|
|
}
|
|
|
|
func leaseCacheSize(_ size: Int) -> ModelReadCacheSizeLease? {
|
|
return nil
|
|
}
|
|
|
|
var unknownUserLabel: String = "unknown"
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
class GRDBFullTextSearcherTest: SignalBaseTest {
|
|
|
|
// MARK: - Dependencies
|
|
|
|
var searcher: FullTextSearcher {
|
|
FullTextSearcher.shared
|
|
}
|
|
|
|
// MARK: - Test Life Cycle
|
|
|
|
override func tearDown() {
|
|
super.tearDown()
|
|
|
|
SDSDatabaseStorage.shouldLogDBQueries = DebugFlags.logSQLQueries
|
|
}
|
|
|
|
private var bobRecipient: SignalServiceAddress!
|
|
private var aliceRecipient: SignalServiceAddress!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
// We need to create new instances of SignalServiceAddress
|
|
// for each test because we're using a new
|
|
// SignalServiceAddressCache for each test and we need
|
|
// consistent backingHashValue.
|
|
aliceRecipient = SignalServiceAddress(phoneNumber: "+12345678900")
|
|
bobRecipient = SignalServiceAddress(phoneNumber: "+49030183000")
|
|
|
|
// Replace this singleton.
|
|
let fakeContactsManager = GRDBFullTextSearcherContactsManager()
|
|
fakeContactsManager.setMockDisplayName("Alice", for: aliceRecipient)
|
|
fakeContactsManager.setMockDisplayName("Bob Barker", for: bobRecipient)
|
|
SSKEnvironment.shared.contactsManagerRef = fakeContactsManager
|
|
|
|
// ensure local client has necessary "registered" state
|
|
let localE164Identifier = "+13235551234"
|
|
let localUUID = UUID()
|
|
tsAccountManager.registerForTests(withLocalNumber: localE164Identifier, uuid: localUUID)
|
|
|
|
self.write { transaction in
|
|
let bookClubGroupThread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Book Club",
|
|
transaction: transaction)
|
|
self.bookClubThread = ThreadViewModel(thread: bookClubGroupThread,
|
|
forChatList: true,
|
|
transaction: transaction)
|
|
|
|
let snackClubGroupThread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient],
|
|
name: "Snack Club",
|
|
transaction: transaction)
|
|
self.snackClubThread = ThreadViewModel(thread: snackClubGroupThread,
|
|
forChatList: true,
|
|
transaction: transaction)
|
|
|
|
let aliceContactThread = TSContactThread.getOrCreateThread(withContactAddress: self.aliceRecipient, transaction: transaction)
|
|
self.aliceThread = ThreadViewModel(thread: aliceContactThread,
|
|
forChatList: true,
|
|
transaction: transaction)
|
|
|
|
let bobContactThread = TSContactThread.getOrCreateThread(withContactAddress: self.bobRecipient, transaction: transaction)
|
|
self.bobEmptyThread = ThreadViewModel(thread: bobContactThread,
|
|
forChatList: true,
|
|
transaction: transaction)
|
|
|
|
let helloAlice = TSOutgoingMessage(in: aliceContactThread, messageBody: "Hello Alice", attachmentId: nil)
|
|
helloAlice.anyInsert(transaction: transaction)
|
|
|
|
let goodbyeAlice = TSOutgoingMessage(in: aliceContactThread, messageBody: "Goodbye Alice", attachmentId: nil)
|
|
goodbyeAlice.anyInsert(transaction: transaction)
|
|
|
|
let helloBookClub = TSOutgoingMessage(in: bookClubGroupThread, messageBody: "Hello Book Club", attachmentId: nil)
|
|
helloBookClub.anyInsert(transaction: transaction)
|
|
|
|
let goodbyeBookClub = TSOutgoingMessage(in: bookClubGroupThread, messageBody: "Goodbye Book Club", attachmentId: nil)
|
|
goodbyeBookClub.anyInsert(transaction: transaction)
|
|
|
|
let bobsPhoneNumber = TSOutgoingMessage(in: bookClubGroupThread, messageBody: "My phone number is: 321-321-4321", attachmentId: nil)
|
|
bobsPhoneNumber.anyInsert(transaction: transaction)
|
|
|
|
let bobsFaxNumber = TSOutgoingMessage(in: bookClubGroupThread, messageBody: "My fax is: 222-333-4444", attachmentId: nil)
|
|
bobsFaxNumber.anyInsert(transaction: transaction)
|
|
}
|
|
}
|
|
|
|
// MARK: - Fixtures
|
|
|
|
var bookClubThread: ThreadViewModel!
|
|
var snackClubThread: ThreadViewModel!
|
|
|
|
var aliceThread: ThreadViewModel!
|
|
var bobEmptyThread: ThreadViewModel!
|
|
|
|
// MARK: Tests
|
|
|
|
private func AssertEqualThreadLists(_ left: [ThreadViewModel], _ right: [ThreadViewModel], file: StaticString = #file, line: UInt = #line) {
|
|
XCTAssertEqual(left.count, right.count, file: file, line: line)
|
|
guard left.count != right.count else {
|
|
return
|
|
}
|
|
// Only bother comparing uniqueIds.
|
|
let leftIds = left.map { $0.threadRecord.uniqueId }
|
|
let rightIds = right.map { $0.threadRecord.uniqueId }
|
|
XCTAssertEqual(leftIds, rightIds, file: file, line: line)
|
|
}
|
|
|
|
func testSearchByGroupName() {
|
|
var threads: [ThreadViewModel] = []
|
|
|
|
// No Match
|
|
threads = searchConversations(searchText: "asdasdasd")
|
|
XCTAssert(threads.isEmpty)
|
|
|
|
// Partial Match
|
|
threads = searchConversations(searchText: "Book")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([bookClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "Snack")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([snackClubThread], threads)
|
|
|
|
// Multiple Partial Matches
|
|
threads = searchConversations(searchText: "Club")
|
|
XCTAssertEqual(2, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, snackClubThread], threads)
|
|
|
|
// Match Name Exactly
|
|
threads = searchConversations(searchText: "Book Club")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([bookClubThread], threads)
|
|
}
|
|
|
|
func testSearchContactByNumber() {
|
|
var threads: [ThreadViewModel] = []
|
|
|
|
// No match
|
|
threads = searchConversations(searchText: "+5551239999")
|
|
XCTAssertEqual(0, threads.count)
|
|
|
|
// Exact match
|
|
threads = searchConversations(searchText: aliceRecipient.phoneNumber!)
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
// Partial match
|
|
threads = searchConversations(searchText: "+123456")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
// Prefixes
|
|
threads = searchConversations(searchText: "12345678900")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "49")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([bookClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "1-234-56")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "123456")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "1.234.56")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "1 234 56")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
}
|
|
|
|
func testSearchContactByNumberWithoutCountryCode() {
|
|
var threads: [ThreadViewModel] = []
|
|
// Phone Number formatting should be forgiving
|
|
threads = searchConversations(searchText: "234.56")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "234 56")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
}
|
|
|
|
func testSearchConversationByContactByName() {
|
|
var threads: [ThreadViewModel] = []
|
|
|
|
threads = searchConversations(searchText: "Alice")
|
|
XCTAssertEqual(3, threads.count)
|
|
AssertEqualThreadLists([bookClubThread, aliceThread, snackClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "Bob")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([bookClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "Barker")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([bookClubThread], threads)
|
|
|
|
threads = searchConversations(searchText: "Bob B")
|
|
XCTAssertEqual(1, threads.count)
|
|
AssertEqualThreadLists([bookClubThread], threads)
|
|
}
|
|
|
|
func testSearchMessageByBodyContent() {
|
|
var resultSet: HomeScreenSearchResultSet = .empty
|
|
|
|
resultSet = getResultSet(searchText: "Hello Alice")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
AssertEqualThreadLists([aliceThread], resultSet.messages.map { $0.thread })
|
|
|
|
resultSet = getResultSet(searchText: "Hello")
|
|
XCTAssertEqual(2, resultSet.messages.count)
|
|
AssertEqualThreadLists([aliceThread, bookClubThread], resultSet.messages.map { $0.thread })
|
|
}
|
|
|
|
func testSearchEdgeCases() {
|
|
var resultSet: HomeScreenSearchResultSet = .empty
|
|
|
|
resultSet = getResultSet(searchText: "Hello Alice")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "hello alice")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "Hel")
|
|
XCTAssertEqual(2, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice", "Hello Book Club"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "Hel Ali")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "Hel Ali Alic")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "Ali Hel")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "CLU")
|
|
XCTAssertEqual(2, resultSet.messages.count)
|
|
XCTAssertEqual(["Goodbye Book Club", "Hello Book Club"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "hello !@##!@#!$^@!@#! alice")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["Hello Alice"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "3213 phone")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["My phone number is: 321-321-4321"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "PHO 3213")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["My phone number is: 321-321-4321"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "fax")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["My fax is: 222-333-4444"], bodies(forMessageResults: resultSet.messages))
|
|
|
|
resultSet = getResultSet(searchText: "fax 2223")
|
|
XCTAssertEqual(1, resultSet.messages.count)
|
|
XCTAssertEqual(["My fax is: 222-333-4444"], bodies(forMessageResults: resultSet.messages))
|
|
}
|
|
|
|
// MARK: - More Tests
|
|
|
|
func testModelLifecycle1() {
|
|
|
|
var thread: TSGroupThread! = nil
|
|
self.write { transaction in
|
|
thread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Lifecycle",
|
|
transaction: transaction)
|
|
}
|
|
|
|
let message1 = TSOutgoingMessage(in: thread, messageBody: "This world contains glory and despair.", attachmentId: nil)
|
|
let message2 = TSOutgoingMessage(in: thread, messageBody: "This world contains hope and despair.", attachmentId: nil)
|
|
|
|
XCTAssertEqual(0, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
|
|
self.write { transaction in
|
|
message1.anyInsert(transaction: transaction)
|
|
message2.anyInsert(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(1, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(2, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
|
|
self.write { transaction in
|
|
message1.update(withMessageBody: "This world contains glory and defeat.", transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(1, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "DEFEAT").messages.count)
|
|
|
|
self.write { transaction in
|
|
message1.anyRemove(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(0, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
|
|
self.write { transaction in
|
|
message2.anyRemove(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(0, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
}
|
|
|
|
func testModelLifecycle2() {
|
|
|
|
self.write { transaction in
|
|
let thread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Lifecycle",
|
|
transaction: transaction)
|
|
|
|
let message1 = TSOutgoingMessage(in: thread, messageBody: "This world contains glory and despair.", attachmentId: nil)
|
|
let message2 = TSOutgoingMessage(in: thread, messageBody: "This world contains hope and despair.", attachmentId: nil)
|
|
|
|
message1.anyInsert(transaction: transaction)
|
|
message2.anyInsert(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(1, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(2, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
|
|
self.write { transaction in
|
|
TSInteraction.anyRemoveAllWithInstantation(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(0, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
}
|
|
|
|
func testModelLifecycle3() {
|
|
|
|
self.write { transaction in
|
|
let thread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Lifecycle",
|
|
transaction: transaction)
|
|
|
|
let message1 = TSOutgoingMessage(in: thread, messageBody: "This world contains glory and despair.", attachmentId: nil)
|
|
let message2 = TSOutgoingMessage(in: thread, messageBody: "This world contains hope and despair.", attachmentId: nil)
|
|
|
|
message1.anyInsert(transaction: transaction)
|
|
message2.anyInsert(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(1, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(2, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
|
|
self.write { transaction in
|
|
TSInteraction.anyRemoveAllWithoutInstantation(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(0, getResultSet(searchText: "GLORY").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "HOPE").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DESPAIR").messages.count)
|
|
XCTAssertEqual(0, getResultSet(searchText: "DEFEAT").messages.count)
|
|
}
|
|
|
|
func testDiacritics() {
|
|
|
|
self.write { transaction in
|
|
let thread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Lifecycle",
|
|
transaction: transaction)
|
|
|
|
TSOutgoingMessage(in: thread, messageBody: "NOËL and SØRINA and ADRIÁN and FRANÇOIS and NUÑEZ and Björk.", attachmentId: nil).anyInsert(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertEqual(1, getResultSet(searchText: "NOËL").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "noel").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "SØRINA").messages.count)
|
|
// I guess Ø isn't a diacritical mark but a separate letter.
|
|
XCTAssertEqual(0, getResultSet(searchText: "sorina").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "ADRIÁN").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "adrian").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "FRANÇOIS").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "francois").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "NUÑEZ").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "nunez").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "Björk").messages.count)
|
|
XCTAssertEqual(1, getResultSet(searchText: "Bjork").messages.count)
|
|
}
|
|
|
|
private func AssertValidResultSet(query: String, expectedResultCount: Int, file: StaticString = #file, line: UInt = #line) {
|
|
// For these simple test cases, the snippet should contain the entire query.
|
|
let expectedSnippetContent: String = query
|
|
|
|
let resultSet = getResultSet(searchText: query)
|
|
XCTAssertEqual(expectedResultCount, resultSet.messages.count, file: file, line: line)
|
|
for result in resultSet.messages {
|
|
guard let snippet = result.snippet else {
|
|
XCTFail("Missing snippet.", file: file, line: line)
|
|
continue
|
|
}
|
|
XCTAssertTrue(snippet.lowercased().contains(expectedSnippetContent.lowercased()), file: file, line: line)
|
|
}
|
|
}
|
|
|
|
func testSnippets() {
|
|
|
|
var thread: TSGroupThread! = nil
|
|
self.write { transaction in
|
|
thread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Lifecycle",
|
|
transaction: transaction)
|
|
}
|
|
|
|
let message1 = TSOutgoingMessage(in: thread, messageBody: "This world contains glory and despair.", attachmentId: nil)
|
|
let message2 = TSOutgoingMessage(in: thread, messageBody: "This world contains hope and despair.", attachmentId: nil)
|
|
|
|
AssertValidResultSet(query: "GLORY", expectedResultCount: 0)
|
|
AssertValidResultSet(query: "HOPE", expectedResultCount: 0)
|
|
AssertValidResultSet(query: "DESPAIR", expectedResultCount: 0)
|
|
AssertValidResultSet(query: "DEFEAT", expectedResultCount: 0)
|
|
|
|
self.write { transaction in
|
|
message1.anyInsert(transaction: transaction)
|
|
message2.anyInsert(transaction: transaction)
|
|
}
|
|
|
|
AssertValidResultSet(query: "GLORY", expectedResultCount: 1)
|
|
AssertValidResultSet(query: "HOPE", expectedResultCount: 1)
|
|
AssertValidResultSet(query: "DESPAIR", expectedResultCount: 2)
|
|
AssertValidResultSet(query: "DEFEAT", expectedResultCount: 0)
|
|
}
|
|
|
|
// MARK: - Perf
|
|
|
|
func testPerf() {
|
|
// Logging queries is expensive and affects the results of this test.
|
|
// This is restored in tearDown().
|
|
SDSDatabaseStorage.shouldLogDBQueries = false
|
|
|
|
let aliceE164 = "+13213214321"
|
|
let aliceUuid = UUID()
|
|
tsAccountManager.registerForTests(withLocalNumber: aliceE164, uuid: aliceUuid)
|
|
|
|
let string1 = "krazy"
|
|
let string2 = "kat"
|
|
let messageCount: UInt = 100
|
|
|
|
Bench(title: "Populate Index", memorySamplerRatio: 1) { _ in
|
|
self.write { transaction in
|
|
let thread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient, self.bobRecipient, self.tsAccountManager.localAddress!],
|
|
name: "Perf",
|
|
transaction: transaction)
|
|
|
|
TSOutgoingMessage(in: thread, messageBody: string1, attachmentId: nil).anyInsert(transaction: transaction)
|
|
|
|
for _ in 0...messageCount {
|
|
let message = TSOutgoingMessage(in: thread, messageBody: UUID().uuidString, attachmentId: nil)
|
|
message.anyInsert(transaction: transaction)
|
|
message.update(withMessageBody: UUID().uuidString, transaction: transaction)
|
|
}
|
|
|
|
TSOutgoingMessage(in: thread, messageBody: string2, attachmentId: nil).anyInsert(transaction: transaction)
|
|
}
|
|
}
|
|
|
|
Bench(title: "Search", memorySamplerRatio: 1) { _ in
|
|
self.read { transaction in
|
|
let finder = FullTextSearchFinder()
|
|
|
|
let getMatchCount = { (searchText: String) -> UInt in
|
|
var count: UInt = 0
|
|
finder.enumerateObjects(
|
|
searchText: searchText,
|
|
collections: [TSMessage.collection()],
|
|
maxResults: 500,
|
|
transaction: transaction
|
|
) { (match, snippet, _) in
|
|
Logger.verbose("searchText: \(searchText), match: \(match), snippet: \(snippet)")
|
|
count += 1
|
|
}
|
|
return count
|
|
}
|
|
XCTAssertEqual(1, getMatchCount(string1))
|
|
XCTAssertEqual(1, getMatchCount(string2))
|
|
XCTAssertEqual(0, getMatchCount(UUID().uuidString))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
func bodies<T>(forMessageResults messageResults: [ConversationSearchResult<T>]) -> [String] {
|
|
var result = [String]()
|
|
|
|
self.read { transaction in
|
|
for messageResult in messageResults {
|
|
guard let messageId = messageResult.messageId else {
|
|
owsFailDebug("message result missing message id")
|
|
continue
|
|
}
|
|
guard let interaction = TSInteraction.anyFetch(uniqueId: messageId, transaction: transaction) else {
|
|
owsFailDebug("couldn't load interaction for message result")
|
|
continue
|
|
}
|
|
guard let message = interaction as? TSMessage else {
|
|
owsFailDebug("invalid message for message result")
|
|
continue
|
|
}
|
|
guard let messageBody = message.body else {
|
|
owsFailDebug("message result missing message body")
|
|
continue
|
|
}
|
|
result.append(messageBody)
|
|
}
|
|
}
|
|
|
|
return result.sorted()
|
|
}
|
|
|
|
private func searchConversations(searchText: String) -> [ThreadViewModel] {
|
|
let results = getResultSet(searchText: searchText)
|
|
let contactThreads = results.contactThreads.map { $0.thread }
|
|
let groupThreads = results.groupThreads.map { $0.thread }
|
|
return contactThreads + groupThreads
|
|
}
|
|
|
|
private func getResultSet(searchText: String) -> HomeScreenSearchResultSet {
|
|
var results: HomeScreenSearchResultSet!
|
|
self.read { transaction in
|
|
results = self.searcher.searchForHomeScreen(searchText: searchText, transaction: transaction)
|
|
}
|
|
return results
|
|
}
|
|
}
|