192 lines
7.5 KiB
Swift
192 lines
7.5 KiB
Swift
import XCTest
|
|
@testable import Rosetta
|
|
|
|
@MainActor
|
|
final class SchemaParityTests: XCTestCase {
|
|
private var ctx: DBTestContext!
|
|
|
|
override func setUpWithError() throws {
|
|
ctx = DBTestContext()
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
ctx.teardown()
|
|
ctx = nil
|
|
}
|
|
|
|
func testSchemaContainsRequiredTablesColumnsAndIndexes() async throws {
|
|
try await ctx.bootstrap()
|
|
let schema = try ctx.schemaSnapshot()
|
|
|
|
let requiredTables: Set<String> = [
|
|
"messages",
|
|
"dialogs",
|
|
"accounts_sync_times",
|
|
"sync_cursors",
|
|
"groups",
|
|
"pinned_messages",
|
|
"avatar_cache",
|
|
"blacklist",
|
|
]
|
|
XCTAssertTrue(requiredTables.isSubset(of: schema.tables), "Missing required tables")
|
|
|
|
let messagesColumns = schema.columnsByTable["messages"] ?? []
|
|
let requiredMessageColumns: Set<String> = [
|
|
"account", "from_public_key", "to_public_key", "message_id", "dialog_key",
|
|
"timestamp", "is_read", "read", "delivery_status", "delivered", "text", "plain_message",
|
|
"attachments", "reply_to_message_id",
|
|
]
|
|
XCTAssertTrue(requiredMessageColumns.isSubset(of: messagesColumns), "Missing messages columns")
|
|
|
|
let dialogsColumns = schema.columnsByTable["dialogs"] ?? []
|
|
let requiredDialogColumns: Set<String> = [
|
|
"account", "opponent_key", "dialog_id", "is_request", "last_timestamp", "last_message_id",
|
|
"last_message_timestamp", "i_have_sent", "unread_count",
|
|
]
|
|
XCTAssertTrue(requiredDialogColumns.isSubset(of: dialogsColumns), "Missing dialogs columns")
|
|
|
|
let requiredIndexes: Set<String> = [
|
|
"idx_messages_account_message_id",
|
|
"idx_messages_account_dialog_key_timestamp",
|
|
"idx_messages_account_dialog_fromme_isread",
|
|
"idx_messages_account_dialog_fromme_timestamp",
|
|
"idx_dialogs_account_opponent_key",
|
|
]
|
|
XCTAssertTrue(requiredIndexes.isSubset(of: schema.indexes), "Missing required indexes")
|
|
}
|
|
|
|
func testUnreadAndSentQueriesUseParityIndexes() async throws {
|
|
try await ctx.bootstrap()
|
|
try await ctx.runScenario(FixtureScenario(name: "seed", events: [
|
|
.incoming(opponent: "02peer_a", messageId: "m1", timestamp: 1, text: "hello"),
|
|
.outgoing(opponent: "02peer_a", messageId: "m2", timestamp: 2, text: "yo"),
|
|
]))
|
|
|
|
let sqlite = try ctx.openSQLite()
|
|
let unreadPlanRows = try sqlite.query(
|
|
"""
|
|
EXPLAIN QUERY PLAN
|
|
SELECT COUNT(*) FROM messages
|
|
WHERE account = ? AND dialog_key = ? AND from_me = 0 AND is_read = 0
|
|
""",
|
|
[.text(ctx.account), .text(DatabaseManager.dialogKey(account: ctx.account, opponentKey: "02peer_a"))]
|
|
)
|
|
let unreadPlan = unreadPlanRows.compactMap { $0["detail"] }.joined(separator: " | ")
|
|
XCTAssertTrue(
|
|
unreadPlan.contains("idx_messages_account_dialog_fromme_isread"),
|
|
"Unread query plan did not use idx_messages_account_dialog_fromme_isread: \(unreadPlan)"
|
|
)
|
|
|
|
let sentPlanRows = try sqlite.query(
|
|
"""
|
|
EXPLAIN QUERY PLAN
|
|
SELECT message_id FROM messages
|
|
WHERE account = ? AND dialog_key = ? AND from_me = 1
|
|
ORDER BY timestamp DESC
|
|
LIMIT 1
|
|
""",
|
|
[.text(ctx.account), .text(DatabaseManager.dialogKey(account: ctx.account, opponentKey: "02peer_a"))]
|
|
)
|
|
let sentPlan = sentPlanRows.compactMap { $0["detail"] }.joined(separator: " | ")
|
|
XCTAssertTrue(
|
|
sentPlan.contains("idx_messages_account_dialog_fromme_timestamp"),
|
|
"Sent query plan did not use idx_messages_account_dialog_fromme_timestamp: \(sentPlan)"
|
|
)
|
|
}
|
|
|
|
func testPacketRegistrySupportsMessagingAndGroupsCoreIds() throws {
|
|
let packets: [(Int, any Packet)] = [
|
|
(0x00, PacketHandshake()),
|
|
(0x01, PacketUserInfo()),
|
|
(0x02, PacketResult()),
|
|
(0x03, PacketSearch()),
|
|
(0x04, PacketOnlineSubscribe()),
|
|
(0x05, PacketOnlineState()),
|
|
(0x06, PacketMessage()),
|
|
(0x07, PacketRead()),
|
|
(0x08, PacketDelivery()),
|
|
(0x09, PacketDeviceNew()),
|
|
(0x0A, PacketRequestUpdate()),
|
|
(0x0B, PacketTyping()),
|
|
(0x0F, PacketRequestTransport()),
|
|
(0x10, PacketPushNotification()),
|
|
(0x11, PacketCreateGroup()),
|
|
(0x12, PacketGroupInfo()),
|
|
(0x13, PacketGroupInviteInfo()),
|
|
(0x14, PacketGroupJoin()),
|
|
(0x15, PacketGroupLeave()),
|
|
(0x16, PacketGroupBan()),
|
|
(0x17, PacketDeviceList()),
|
|
(0x18, PacketDeviceResolve()),
|
|
(0x19, PacketSync()),
|
|
]
|
|
|
|
for (expectedId, packet) in packets {
|
|
let encoded = PacketRegistry.encode(packet)
|
|
guard let decoded = PacketRegistry.decode(from: encoded) else {
|
|
XCTFail("Failed to decode packet 0x\(String(expectedId, radix: 16))")
|
|
continue
|
|
}
|
|
XCTAssertEqual(decoded.packetId, expectedId)
|
|
}
|
|
}
|
|
|
|
func testAttachmentTypeCallRoundTripDecoding() throws {
|
|
var packet = PacketMessage()
|
|
packet.fromPublicKey = "02from"
|
|
packet.toPublicKey = "02to"
|
|
packet.content = ""
|
|
packet.chachaKey = ""
|
|
packet.timestamp = 123
|
|
packet.privateKey = "hash"
|
|
packet.messageId = "msg-call"
|
|
packet.attachments = [MessageAttachment(id: "call-1", preview: "", blob: "", type: .call)]
|
|
packet.aesChachaKey = ""
|
|
|
|
let encoded = PacketRegistry.encode(packet)
|
|
guard let decoded = PacketRegistry.decode(from: encoded),
|
|
let decodedMessage = decoded.packet as? PacketMessage
|
|
else {
|
|
XCTFail("Failed to decode call attachment packet")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(decoded.packetId, 0x06)
|
|
XCTAssertEqual(decodedMessage.attachments.first?.type, .call)
|
|
}
|
|
|
|
func testSessionPacketContextResolverAcceptsGroupWireShape() throws {
|
|
let own = "02my_account"
|
|
|
|
var groupMessage = PacketMessage()
|
|
groupMessage.fromPublicKey = "02group_member"
|
|
groupMessage.toPublicKey = "#group:alpha"
|
|
let messageContext = SessionManager.testResolveMessagePacketContext(groupMessage, ownKey: own)
|
|
XCTAssertEqual(messageContext?.kind, "group")
|
|
XCTAssertEqual(messageContext?.dialogKey, "#group:alpha")
|
|
XCTAssertEqual(messageContext?.fromMe, false)
|
|
|
|
var groupRead = PacketRead()
|
|
groupRead.fromPublicKey = "02group_member"
|
|
groupRead.toPublicKey = "#group:alpha"
|
|
let readContext = SessionManager.testResolveReadPacketContext(groupRead, ownKey: own)
|
|
XCTAssertEqual(readContext?.kind, "group")
|
|
XCTAssertEqual(readContext?.dialogKey, "#group:alpha")
|
|
XCTAssertEqual(readContext?.fromMe, false)
|
|
|
|
var groupTyping = PacketTyping()
|
|
groupTyping.fromPublicKey = "02group_member"
|
|
groupTyping.toPublicKey = "#group:alpha"
|
|
let typingContext = SessionManager.testResolveTypingPacketContext(groupTyping, ownKey: own)
|
|
XCTAssertEqual(typingContext?.kind, "group")
|
|
XCTAssertEqual(typingContext?.dialogKey, "#group:alpha")
|
|
XCTAssertEqual(typingContext?.fromMe, false)
|
|
}
|
|
|
|
func testStreamEncodingSmoke() {
|
|
let stream = Rosetta.Stream()
|
|
_ = stream
|
|
XCTAssertTrue(true)
|
|
}
|
|
}
|