Паритет вложений и поиска на iOS (desktop/server/android), новые autotests и аудит
This commit is contained in:
181
RosettaTests/AttachmentParityTests.swift
Normal file
181
RosettaTests/AttachmentParityTests.swift
Normal file
@@ -0,0 +1,181 @@
|
||||
import UIKit
|
||||
import XCTest
|
||||
@testable import Rosetta
|
||||
|
||||
@MainActor
|
||||
final class AttachmentParityTests: XCTestCase {
|
||||
private var ctx: DBTestContext!
|
||||
private var transportMock: MockAttachmentFlowTransport!
|
||||
private var senderMock: MockPacketFlowSender!
|
||||
|
||||
private var ownPrivateKeyHex: String = ""
|
||||
private var ownPublicKey: String = ""
|
||||
private var peerPublicKey: String = ""
|
||||
|
||||
override func setUpWithError() throws {
|
||||
let ownPair = try Self.makeKeyPair()
|
||||
let peerPair = try Self.makeKeyPair()
|
||||
|
||||
ownPrivateKeyHex = ownPair.privateKeyHex
|
||||
ownPublicKey = ownPair.publicKeyHex
|
||||
peerPublicKey = peerPair.publicKeyHex
|
||||
|
||||
ctx = DBTestContext(account: ownPublicKey)
|
||||
transportMock = MockAttachmentFlowTransport()
|
||||
senderMock = MockPacketFlowSender()
|
||||
|
||||
SessionManager.shared.testConfigureSessionForParityFlows(
|
||||
currentPublicKey: ownPublicKey,
|
||||
privateKeyHex: ownPrivateKeyHex
|
||||
)
|
||||
SessionManager.shared.attachmentFlowTransport = transportMock
|
||||
SessionManager.shared.packetFlowSender = senderMock
|
||||
AttachmentCache.shared.privateKey = ownPrivateKeyHex
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
ctx?.teardown()
|
||||
ctx = nil
|
||||
transportMock = nil
|
||||
senderMock = nil
|
||||
AttachmentCache.shared.privateKey = nil
|
||||
SessionManager.shared.testResetParityFlowDependencies()
|
||||
}
|
||||
|
||||
func testAttachmentPreviewParserMatrix() {
|
||||
let tag = "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
|
||||
|
||||
XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: "\(tag)::LKO2"), tag)
|
||||
XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: "\(tag)::LKO2"), "LKO2")
|
||||
|
||||
XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: "::LKO2"), "")
|
||||
XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: "::LKO2"), "LKO2")
|
||||
|
||||
let taggedFile = AttachmentPreviewCodec.parseFilePreview("\(tag)::2048::report.pdf")
|
||||
XCTAssertEqual(taggedFile.downloadTag, tag)
|
||||
XCTAssertEqual(taggedFile.fileSize, 2048)
|
||||
XCTAssertEqual(taggedFile.fileName, "report.pdf")
|
||||
|
||||
let localFile = AttachmentPreviewCodec.parseFilePreview("512::notes.txt")
|
||||
XCTAssertEqual(localFile.downloadTag, "")
|
||||
XCTAssertEqual(localFile.fileSize, 512)
|
||||
XCTAssertEqual(localFile.fileName, "notes.txt")
|
||||
|
||||
XCTAssertEqual(AttachmentPreviewCodec.payload(from: "legacy_preview"), "legacy_preview")
|
||||
}
|
||||
|
||||
func testOutgoingAttachmentPacketShapeClearsBlobAndUsesTaggedPreview() async throws {
|
||||
try await ctx.bootstrap()
|
||||
|
||||
let image = Self.makeSolidImage(color: .systemBlue)
|
||||
let imageAttachment = PendingAttachment.fromImage(image)
|
||||
let fileData = Data("hello parity".utf8)
|
||||
let fileAttachment = PendingAttachment.fromFile(data: fileData, fileName: "notes.txt")
|
||||
|
||||
let imageTag = "11111111-1111-1111-1111-111111111111"
|
||||
let fileTag = "22222222-2222-2222-2222-222222222222"
|
||||
transportMock.tagsById[imageAttachment.id] = imageTag
|
||||
transportMock.tagsById[fileAttachment.id] = fileTag
|
||||
|
||||
try await SessionManager.shared.sendMessageWithAttachments(
|
||||
text: "",
|
||||
attachments: [imageAttachment, fileAttachment],
|
||||
toPublicKey: peerPublicKey,
|
||||
opponentTitle: "Peer",
|
||||
opponentUsername: "peer"
|
||||
)
|
||||
|
||||
XCTAssertEqual(transportMock.uploadedIds.count, 2)
|
||||
XCTAssertEqual(senderMock.sentMessages.count, 1)
|
||||
|
||||
guard let sent = senderMock.sentMessages.first else {
|
||||
XCTFail("No outgoing packet captured")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(sent.attachments.count, 2)
|
||||
XCTAssertTrue(sent.attachments.allSatisfy { $0.blob.isEmpty })
|
||||
|
||||
guard let sentImage = sent.attachments.first(where: { $0.id == imageAttachment.id }) else {
|
||||
XCTFail("Missing image attachment in packet")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: sentImage.preview), imageTag)
|
||||
|
||||
guard let sentFile = sent.attachments.first(where: { $0.id == fileAttachment.id }) else {
|
||||
XCTFail("Missing file attachment in packet")
|
||||
return
|
||||
}
|
||||
let parsedFile = AttachmentPreviewCodec.parseFilePreview(sentFile.preview)
|
||||
XCTAssertEqual(parsedFile.downloadTag, fileTag)
|
||||
XCTAssertEqual(parsedFile.fileSize, fileData.count)
|
||||
XCTAssertEqual(parsedFile.fileName, "notes.txt")
|
||||
}
|
||||
|
||||
func testSavedSelfFileFlowKeepsLocalFileOpenableWithoutUpload() async throws {
|
||||
try await ctx.bootstrap()
|
||||
|
||||
let fileData = Data("self file payload".utf8)
|
||||
let fileAttachment = PendingAttachment.fromFile(data: fileData, fileName: "local.txt")
|
||||
|
||||
try await SessionManager.shared.sendMessageWithAttachments(
|
||||
text: "",
|
||||
attachments: [fileAttachment],
|
||||
toPublicKey: ownPublicKey
|
||||
)
|
||||
|
||||
XCTAssertTrue(transportMock.uploadedIds.isEmpty)
|
||||
XCTAssertTrue(senderMock.sentMessages.isEmpty)
|
||||
|
||||
let cachedURL = AttachmentCache.shared.fileURL(
|
||||
forAttachmentId: fileAttachment.id,
|
||||
fileName: "local.txt"
|
||||
)
|
||||
XCTAssertNotNil(cachedURL)
|
||||
|
||||
let loaded = AttachmentCache.shared.loadFileData(
|
||||
forAttachmentId: fileAttachment.id,
|
||||
fileName: "local.txt"
|
||||
)
|
||||
XCTAssertEqual(loaded, fileData)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AttachmentParityTests {
|
||||
static func makeSolidImage(color: UIColor) -> UIImage {
|
||||
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 32, height: 32))
|
||||
return renderer.image { ctx in
|
||||
color.setFill()
|
||||
ctx.fill(CGRect(x: 0, y: 0, width: 32, height: 32))
|
||||
}
|
||||
}
|
||||
|
||||
static func makeKeyPair() throws -> (privateKeyHex: String, publicKeyHex: String) {
|
||||
let mnemonic = try CryptoManager.shared.generateMnemonic()
|
||||
let pair = try CryptoManager.shared.deriveKeyPair(from: mnemonic)
|
||||
return (pair.privateKey.hexString, pair.publicKey.hexString)
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockAttachmentFlowTransport: AttachmentFlowTransporting {
|
||||
var tagsById: [String: String] = [:]
|
||||
private(set) var uploadedIds: [String] = []
|
||||
|
||||
func uploadFile(id: String, content: Data) async throws -> String {
|
||||
uploadedIds.append(id)
|
||||
return tagsById[id] ?? UUID().uuidString.lowercased()
|
||||
}
|
||||
|
||||
func downloadFile(tag: String) async throws -> Data {
|
||||
Data()
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockPacketFlowSender: PacketFlowSending {
|
||||
private(set) var sentMessages: [PacketMessage] = []
|
||||
|
||||
func sendPacket(_ packet: any Packet) {
|
||||
if let message = packet as? PacketMessage {
|
||||
sentMessages.append(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user