Signal-iOS/Signal/test/util/FTS/GRDBFullTextSearcherTest.swift
Michelle Linington 21f0fcb2ff Fixes broken tests related to the GroupMembers table
The TSGroupMember table is updated after every inserted interaction. It
asserts that the sender of the interaction is in the group membership.

Before, our tests would construct groups with mock membership and then
construct outgoing messages with the local address as the sender. This
would fail this new requirement.

The fix is to make sure that the local address is in the test group
membership if we'd like to "send" any messages.
2021-05-06 15:45:42 -07:00

679 lines
29 KiB
Swift

//
// Copyright (c) 2021 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 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 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) -> PersonNameComponents? {
PersonNameComponents()
}
func nameComponents(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> PersonNameComponents? {
PersonNameComponents()
}
func signalAccounts() -> [SignalAccount] {
[]
}
func isSystemContact(phoneNumber: String) -> Bool {
true
}
func isSystemContact(address: SignalServiceAddress) -> Bool {
true
}
func isSystemContact(withSignalAccount recipientId: String) -> Bool {
true
}
func isSystemContact(withSignalAccount phoneNumber: String, transaction: SDSAnyReadTransaction) -> Bool {
true
}
func hasNameInSystemContacts(for address: SignalServiceAddress) -> Bool {
(mockDisplayNameMap[address] ?? "").count > 0
}
func conversationColorName(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> ConversationColorName {
ConversationColorName.taupe
}
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
}
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,
forConversationList: true,
transaction: transaction)
let snackClubGroupThread = try! GroupManager.createGroupForTests(members: [self.aliceRecipient],
name: "Snack Club",
transaction: transaction)
self.snackClubThread = ThreadViewModel(thread: snackClubGroupThread,
forConversationList: true,
transaction: transaction)
let aliceContactThread = TSContactThread.getOrCreateThread(withContactAddress: self.aliceRecipient, transaction: transaction)
self.aliceThread = ThreadViewModel(thread: aliceContactThread,
forConversationList: true,
transaction: transaction)
let bobContactThread = TSContactThread.getOrCreateThread(withContactAddress: self.bobRecipient, transaction: transaction)
self.bobEmptyThread = ThreadViewModel(thread: bobContactThread,
forConversationList: 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
}
}