Паритет вложений и поиска на iOS (desktop/server/android), новые autotests и аудит
This commit is contained in:
191
RosettaTests/SchemaParityTests.swift
Normal file
191
RosettaTests/SchemaParityTests.swift
Normal file
@@ -0,0 +1,191 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user