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 = [ "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 = [ "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 = [ "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 = [ "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) XCTAssertFalse(decodedMessage.isMalformed) } func testPacketMessageDecodeSupportsAttachmentMeta4Compatibility() throws { let encoded = makePacketMessageData(attachmentMetaFieldCount: 4) guard let decoded = PacketRegistry.decode(from: encoded), let message = decoded.packet as? PacketMessage else { XCTFail("Failed to decode packet with 4 attachment meta fields") return } XCTAssertEqual(decoded.packetId, 0x06) XCTAssertFalse(message.isMalformed) XCTAssertEqual(message.fromPublicKey, "02from") XCTAssertEqual(message.toPublicKey, "02to") XCTAssertEqual(message.messageId, "msg-compat") XCTAssertEqual(message.aesChachaKey, "aes-key") XCTAssertEqual(message.attachments.count, 1) XCTAssertEqual(message.attachments[0].transportTag, "tag-1") XCTAssertEqual(message.attachments[0].transportServer, "cdn.rosetta.im") } func testPacketMessageDecodeSupportsAttachmentMeta0Compatibility() throws { let encoded = makePacketMessageData(attachmentMetaFieldCount: 0) guard let decoded = PacketRegistry.decode(from: encoded), let message = decoded.packet as? PacketMessage else { XCTFail("Failed to decode packet with 0 attachment meta fields") return } XCTAssertEqual(decoded.packetId, 0x06) XCTAssertFalse(message.isMalformed) XCTAssertEqual(message.messageId, "msg-compat") XCTAssertEqual(message.aesChachaKey, "aes-key") XCTAssertEqual(message.attachments.count, 1) XCTAssertEqual(message.attachments[0].transportTag, "") XCTAssertEqual(message.attachments[0].transportServer, "") } func testPacketMessageDecodeMarksMalformedForTruncatedOrMisalignedPayload() throws { let canonical = makePacketMessageData(attachmentMetaFieldCount: 2) let truncated = canonical.dropLast(3) let withTrailingByte = canonical + Data([0x00]) guard let decodedTruncated = PacketRegistry.decode(from: Data(truncated)), let messageTruncated = decodedTruncated.packet as? PacketMessage else { XCTFail("Failed to decode truncated packet wrapper") return } XCTAssertTrue(messageTruncated.isMalformed) XCTAssertFalse(messageTruncated.malformedFingerprint.isEmpty) guard let decodedTrailing = PacketRegistry.decode(from: withTrailingByte), let messageTrailing = decodedTrailing.packet as? PacketMessage else { XCTFail("Failed to decode trailing-byte packet wrapper") return } XCTAssertTrue(messageTrailing.isMalformed) XCTAssertFalse(messageTrailing.malformedFingerprint.isEmpty) } 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) } private func makePacketMessageData(attachmentMetaFieldCount: Int) -> Data { let stream = Rosetta.Stream() stream.writeInt16(PacketMessage.packetId) stream.writeString("02from") stream.writeString("02to") stream.writeString("ciphertext") stream.writeString("chacha-key") stream.writeInt64(1_710_000_000_000) stream.writeString("hash") stream.writeString("msg-compat") stream.writeInt8(1) stream.writeString("att-1") stream.writeString("preview") stream.writeString("blob") stream.writeInt8(AttachmentType.image.rawValue) if attachmentMetaFieldCount >= 2 { stream.writeString("tag-1") stream.writeString("cdn.rosetta.im") } if attachmentMetaFieldCount >= 4 { stream.writeString("02encoded-for") stream.writeString("desktop") } stream.writeString("aes-key") return stream.toData() } }