Swiftgram/PostboxTests/ChatListTableTests.swift
2017-01-31 14:41:50 +03:00

419 lines
21 KiB
Swift

import Foundation
import UIKit
import XCTest
import Postbox
@testable import Postbox
import SwiftSignalKit
private let namespace: Int32 = 1
private let authorPeerId = PeerId(namespace: 2, id: 3)
private enum Entry: Equatable, CustomStringConvertible {
case Message(Int32, Int32, Int32, Bool)
case Hole(Int32, Int32, Int32)
var description: String {
switch self {
case let .Message(peerId, id, timestamp, exists):
return "Message(\(peerId), \(id), \(timestamp), \(exists))"
case let .Hole(peerId, id, timestamp):
return "Hole(\(peerId), \(id), \(timestamp))"
}
}
}
private func ==(lhs: Entry, rhs: Entry) -> Bool {
switch lhs {
case let .Message(lhsPeerId, lhsId, lhsTimestamp, lhsExists):
switch rhs {
case let .Message(rhsPeerId, rhsId, rhsTimestamp, rhsExists):
return lhsPeerId == rhsPeerId && lhsId == rhsId && lhsTimestamp == rhsTimestamp && lhsExists == rhsExists
case .Hole:
return false
}
case let .Hole(lhsPeerId, lhsId, lhsTimestamp):
switch rhs {
case .Message:
return false
case let .Hole(rhsPeerId, rhsId, rhsTimestamp):
return lhsPeerId == rhsPeerId && lhsId == rhsId && lhsTimestamp == rhsTimestamp
}
}
}
class ChatListTableTests: XCTestCase {
var valueBox: ValueBox?
var path: String?
var globalMessageIdsTable: GlobalMessageIdsTable?
var indexTable: MessageHistoryIndexTable?
var mediaTable: MessageMediaTable?
var historyTable: MessageHistoryTable?
var chatListIndexTable: ChatListIndexTable?
var chatListTable: ChatListTable?
var historyMetadataTable: MessageHistoryMetadataTable?
var unsentTable: MessageHistoryUnsentTable?
var tagsTable: MessageHistoryTagsTable?
var readStateTable: MessageHistoryReadStateTable?
var synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable?
var peerChatInterfaceStateTable: PeerChatInterfaceStateTable?
var peerTable: PeerTable?
var peerNameTokenIndexTable: PeerNameTokenIndexTable?
var peerNameIndexTable: PeerNameIndexTable?
var notificationSettingsTable: PeerNotificationSettingsTable?
var globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable?
override class func setUp() {
super.setUp()
}
override func setUp() {
super.setUp()
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
path = NSTemporaryDirectory() + "\(randomId)"
self.valueBox = SqliteValueBox(basePath: path!, queue: Queue.mainQueue())
let seedConfiguration = SeedConfiguration(initializeChatListWithHoles: [], initializeMessageNamespacesWithHoles: [], existingMessageTags: [])
self.globalMessageIdsTable = GlobalMessageIdsTable(valueBox: self.valueBox!, table: GlobalMessageIdsTable.tableSpec(7), namespace: namespace)
self.historyMetadataTable = MessageHistoryMetadataTable(valueBox: self.valueBox!, table: MessageHistoryMetadataTable.tableSpec(8))
self.unsentTable = MessageHistoryUnsentTable(valueBox: self.valueBox!, table: MessageHistoryUnsentTable.tableSpec(9))
self.tagsTable = MessageHistoryTagsTable(valueBox: self.valueBox!, table: MessageHistoryTagsTable.tableSpec(10))
self.indexTable = MessageHistoryIndexTable(valueBox: self.valueBox!, table: MessageHistoryIndexTable.tableSpec(1), globalMessageIdsTable: self.globalMessageIdsTable!, metadataTable: self.historyMetadataTable!, seedConfiguration: seedConfiguration)
self.mediaTable = MessageMediaTable(valueBox: self.valueBox!, table: MessageMediaTable.tableSpec(2))
self.readStateTable = MessageHistoryReadStateTable(valueBox: self.valueBox!, table: MessageHistoryReadStateTable.tableSpec(11))
self.synchronizeReadStateTable = MessageHistorySynchronizeReadStateTable(valueBox: self.valueBox!, table: MessageHistorySynchronizeReadStateTable.tableSpec(12))
self.globallyUniqueMessageIdsTable = MessageGloballyUniqueIdTable(valueBox: self.valueBox!, table: MessageGloballyUniqueIdTable.tableSpec(24))
self.historyTable = MessageHistoryTable(valueBox: self.valueBox!, table: MessageHistoryTable.tableSpec(4), messageHistoryIndexTable: self.indexTable!, messageMediaTable: self.mediaTable!, historyMetadataTable: self.historyMetadataTable!, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable!, unsentTable: self.unsentTable!, tagsTable: self.tagsTable!, readStateTable: self.readStateTable!, synchronizeReadStateTable: self.synchronizeReadStateTable!)
self.peerTable = PeerTable(valueBox: self.valueBox!, table: PeerTable.tableSpec(20))
self.peerNameTokenIndexTable = PeerNameTokenIndexTable(valueBox: self.valueBox!, table: PeerNameTokenIndexTable.tableSpec(21))
self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox!, table: PeerNameIndexTable.tableSpec(22), peerTable: self.peerTable!, peerNameTokenIndexTable: self.peerNameTokenIndexTable!)
self.notificationSettingsTable = PeerNotificationSettingsTable(valueBox: self.valueBox!, table: PeerNotificationSettingsTable.tableSpec(23))
self.chatListIndexTable = ChatListIndexTable(valueBox: self.valueBox!, table: ChatListIndexTable.tableSpec(5), peerNameIndexTable: self.peerNameIndexTable!, metadataTable: self.historyMetadataTable!, readStateTable: self.readStateTable!, notificationSettingsTable: self.notificationSettingsTable!)
self.chatListTable = ChatListTable(valueBox: self.valueBox!, table: ChatListTable.tableSpec(6), indexTable: self.chatListIndexTable!, metadataTable: self.historyMetadataTable!, seedConfiguration: seedConfiguration)
self.peerChatInterfaceStateTable = PeerChatInterfaceStateTable(valueBox: self.valueBox!, table: PeerChatInterfaceStateTable.tableSpec(20))
}
override func tearDown() {
super.tearDown()
self.historyTable = nil
self.indexTable = nil
self.mediaTable = nil
self.chatListIndexTable = nil
self.chatListTable = nil
self.valueBox = nil
let _ = try? FileManager.default.removeItem(atPath: path!)
self.path = nil
}
private func addMessage(_ peerId: Int32, _ id: Int32, _ timestamp: Int32, _ text: String = "", _ media: [Media] = []) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
var unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation] = []
var updatedPeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
let updatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?] = [:]
self.historyTable!.addMessages([StoreMessage(id: MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: id), globallyUniqueId: nil, timestamp: timestamp, flags: [], tags: [], forwardInfo: nil, authorId: authorPeerId, text: text, attributes: [], media: media)], location: .Random, operationsByPeerId: &operationsByPeerId, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations)
var operations: [ChatListOperation] = []
self.chatListTable!.replay(historyOperationsByPeerId: operationsByPeerId, updatedPeerChatListEmbeddedStates: updatedPeerChatListEmbeddedStates, updatedChatListInclusions: [:], messageHistoryTable: self.historyTable!, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable!, operations: &operations)
var updatedTotalUnreadCount: Int32?
self.chatListIndexTable?.commitWithTransactionUnreadCountDeltas([:], transactionParticipationInTotalUnreadCountUpdates: ([], []), getPeer: { _ in
return nil
}, updatedTotalUnreadCount: &updatedTotalUnreadCount)
self.chatListIndexTable?.clearMemoryCache()
}
private func updateInclusion(_ peerId: Int32, f: (PeerChatListInclusion) -> PeerChatListInclusion) {
var updatedChatListInclusions: [PeerId: PeerChatListInclusion] = [:]
self.chatListTable!.updateInclusion(peerId: PeerId(namespace: namespace, id: peerId), updatedChatListInclusions: &updatedChatListInclusions, f)
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
var unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation] = []
var updatedPeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
let updatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?] = [:]
var operations: [ChatListOperation] = []
self.chatListTable!.replay(historyOperationsByPeerId: [:], updatedPeerChatListEmbeddedStates: [:], updatedChatListInclusions: updatedChatListInclusions, messageHistoryTable: self.historyTable!, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable!, operations: &operations)
var updatedTotalUnreadCount: Int32?
self.chatListIndexTable?.commitWithTransactionUnreadCountDeltas([:], transactionParticipationInTotalUnreadCountUpdates: ([], []), getPeer: { _ in
return nil
}, updatedTotalUnreadCount: &updatedTotalUnreadCount)
self.chatListIndexTable?.clearMemoryCache()
}
private func addHole(_ peerId: Int32, _ id: Int32) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
var unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation] = []
var updatedPeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
let updatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?] = [:]
self.historyTable!.addHoles([MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: id)], operationsByPeerId: &operationsByPeerId, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations)
var operations: [ChatListOperation] = []
self.chatListTable!.replay(historyOperationsByPeerId: operationsByPeerId, updatedPeerChatListEmbeddedStates: updatedPeerChatListEmbeddedStates, updatedChatListInclusions: [:], messageHistoryTable: self.historyTable!, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable!, operations: &operations)
}
private func addChatListHole(_ peerId: Int32, _ id: Int32, _ timestamp: Int32) {
var operations: [ChatListOperation] = []
self.chatListTable!.addHole(ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: id), timestamp: timestamp)), operations: &operations)
}
private func replaceChatListHole(_ peerId: Int32, _ id: Int32, _ timestamp: Int32, _ otherPeerId: Int32, _ otherId: Int32, _ otherTimestamp: Int32) {
var operations: [ChatListOperation] = []
self.chatListTable!.replaceHole(MessageIndex(id: MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: id), timestamp: timestamp), hole: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: namespace, id: otherPeerId), namespace: namespace, id: otherId), timestamp: otherTimestamp)), operations: &operations)
}
private func removeChatListHole(_ peerId: Int32, _ id: Int32, _ timestamp: Int32) {
var operations: [ChatListOperation] = []
self.chatListTable!.replaceHole(MessageIndex(id: MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: id), timestamp: timestamp), hole: nil, operations: &operations)
}
private func removeMessages(_ peerId: Int32, _ ids: [Int32]) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
var unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation] = []
var updatedPeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
let updatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?] = [:]
self.historyTable!.removeMessages(ids.map({ MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: $0) }), operationsByPeerId: &operationsByPeerId, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations)
var operations: [ChatListOperation] = []
self.chatListTable!.replay(historyOperationsByPeerId: operationsByPeerId, updatedPeerChatListEmbeddedStates: updatedPeerChatListEmbeddedStates, updatedChatListInclusions: [:], messageHistoryTable: self.historyTable!, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable!, operations: &operations)
}
private func fillHole(_ peerId: Int32, _ id: Int32, _ fillType: HoleFill, _ messages: [(Int32, Int32, String, [Media])]) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
var unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation] = []
var updatedPeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
let updatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?] = [:]
self.historyTable!.fillHole(MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: id), fillType: fillType, tagMask: nil, messages: messages.map({ StoreMessage(id: MessageId(peerId: PeerId(namespace: namespace, id: peerId), namespace: namespace, id: $0.0), globallyUniqueId: nil, timestamp: $0.1, flags: [], tags: [], forwardInfo: nil, authorId: authorPeerId, text: $0.2, attributes: [], media: $0.3) }), operationsByPeerId: &operationsByPeerId, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations)
var operations: [ChatListOperation] = []
self.chatListTable!.replay(historyOperationsByPeerId: operationsByPeerId, updatedPeerChatListEmbeddedStates: updatedPeerChatListEmbeddedStates, updatedChatListInclusions: [:], messageHistoryTable: self.historyTable!, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable!, operations: &operations)
}
private func expectEntries(_ entries: [Entry]) {
let actualEntries = self.chatListTable!.debugList(self.historyTable!, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable!).map({ entry -> Entry in
switch entry {
case let .Message(index, message, _):
if let message = message, message.authorId != authorPeerId {
XCTFail("Expected authorId \(authorPeerId), actual \(String(describing: message.authorId))")
}
return .Message(index.messageIndex.id.peerId.id, index.messageIndex.id.id, index.messageIndex.timestamp, message != nil)
case let .Hole(hole):
return .Hole(hole.index.id.peerId.id, hole.index.id.id, hole.index.timestamp)
}
})
if entries != actualEntries {
XCTFail("Expected\n\(entries)\nActual\n\(actualEntries)")
}
}
func testEmpty() {
expectEntries([])
}
func testAddSingleMessage() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
expectEntries([.Message(1, 100, 100, true)])
}
func testInsertLaterMessage() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
addMessage(1, 200, 200)
expectEntries([.Message(1, 200, 200, true)])
}
func testInsertEarlierMessage() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
addMessage(1, 10, 20)
expectEntries([.Message(1, 100, 100, true)])
}
func testInsertTwoChatMessages() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
updateInclusion(2, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
addMessage(2, 10, 20)
expectEntries([.Message(2, 10, 20, true), .Message(1, 100, 100, true)])
}
func testMoveChatUpper() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
updateInclusion(2, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
addMessage(2, 10, 20)
addMessage(2, 120, 120)
expectEntries([.Message(1, 100, 100, true), .Message(2, 120, 120, true)])
}
func testRemoveSingleMessageInclusionIfHasMessages() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
removeMessages(1, [100])
expectEntries([])
//expectEntries([.Message(1, 100, 100, false)])
}
func testAddSingleMessageInclusionNever() {
addMessage(1, 100, 100)
expectEntries([])
}
func testEmptyWithInclusionMinIndex() {
updateInclusion(1, f: { _ in
return .ifHasMessagesOrOneOf(pinningIndex: nil, minTimestamp: 50)
})
expectEntries([.Message(1, 0, 50, false)])
addMessage(1, 20, 20)
expectEntries([.Message(1, 0, 50, true)])
updateInclusion(1, f: { _ in
return .ifHasMessages
})
expectEntries([.Message(1, 20, 20, true)])
}
func testEmptyWithInclusionPinningIndex() {
updateInclusion(1, f: { _ in
return .ifHasMessagesOrOneOf(pinningIndex: 0, minTimestamp: nil)
})
expectEntries([.Message(1, 0, 0, false)])
}
func testRemoveSingleMessageInclusionWithMinIndexUpdate() {
updateInclusion(1, f: { _ in
return .ifHasMessagesOrOneOf(pinningIndex: nil, minTimestamp: 50)
})
addMessage(1, 100, 100)
removeMessages(1, [100])
expectEntries([.Message(1, 0, 50, false)])
updateInclusion(1, f: { _ in
return .ifHasMessagesOrOneOf(pinningIndex: nil, minTimestamp: 200)
})
expectEntries([.Message(1, 0, 200, false)])
updateInclusion(1, f: { _ in
return .ifHasMessages
})
expectEntries([])
addMessage(1, 200, 200)
expectEntries([.Message(1, 200, 200, true)])
}
func testOverrideNothing() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
removeMessages(1, [100])
addMessage(1, 100, 100)
expectEntries([.Message(1, 100, 100, true)])
}
func testInsertHoleIntoEmpty() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addChatListHole(1, 10, 10)
expectEntries([.Hole(1, 10, 10)])
}
func testInsertHoleLower() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
addChatListHole(1, 10, 10)
expectEntries([.Hole(1, 10, 10), .Message(1, 100, 100, true)])
}
func testInsertHoleUpper() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addMessage(1, 100, 100)
addChatListHole(1, 200, 200)
expectEntries([.Message(1, 100, 100, true), .Hole(1, 200, 200)])
}
func testIgnoreRemoveHole() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
addChatListHole(1, 100, 100)
removeMessages(1, [100])
expectEntries([.Hole(1, 100, 100)])
addMessage(1, 100, 100)
expectEntries([.Message(1, 100, 100, true), .Hole(1, 100, 100)])
removeMessages(1, [100])
expectEntries([.Hole(1, 100, 100)])
updateInclusion(1, f: { _ in
return .ifHasMessagesOrOneOf(pinningIndex: nil, minTimestamp: 100)
})
expectEntries([.Message(1, 0, 100, false), .Hole(1, 100, 100)])
}
func testReplaceHoleWithHole() {
updateInclusion(1, f: { _ in
return .ifHasMessages
})
updateInclusion(2, f: { _ in
return .ifHasMessages
})
addChatListHole(1, 100, 100)
replaceChatListHole(1, 100, 100, 2, 200, 200)
expectEntries([.Hole(2, 200, 200)])
}
func testReplaceHoleWithNone() {
addChatListHole(1, 100, 100)
removeChatListHole(1, 100, 100)
expectEntries([])
}
func testInclusionUpdate() {
//expectEntries([.Message(1, 0, 1, false)])
addMessage(1, 100, 100)
//expectEntries([.Message(1, 100, 100, true)])
addMessage(1, 200, 200)
updateInclusion(1, f: { _ in
return .ifHasMessagesOrOneOf(pinningIndex: nil, minTimestamp: 1)
})
addMessage(1, 300, 300)
expectEntries([.Message(1, 300, 300, true)])
}
}