Паритет вложений и поиска на iOS (desktop/server/android), новые autotests и аудит
This commit is contained in:
126
RosettaTests/SearchParityTests.swift
Normal file
126
RosettaTests/SearchParityTests.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
import XCTest
|
||||
@testable import Rosetta
|
||||
|
||||
@MainActor
|
||||
final class SearchParityTests: XCTestCase {
|
||||
func testSearchViewModelAndChatListUseSameQueryNormalization() async {
|
||||
let searchDispatcher = MockSearchDispatcher()
|
||||
let chatDispatcher = MockSearchDispatcher()
|
||||
let searchVM = SearchViewModel(searchDispatcher: searchDispatcher)
|
||||
let chatVM = ChatListViewModel(searchDispatcher: chatDispatcher)
|
||||
|
||||
searchVM.setSearchQuery(" @Alice ")
|
||||
chatVM.setSearchQuery(" @Alice ")
|
||||
|
||||
try? await Task.sleep(for: .milliseconds(1200))
|
||||
|
||||
XCTAssertEqual(searchDispatcher.sentQueries, ["alice"])
|
||||
XCTAssertEqual(chatDispatcher.sentQueries, ["alice"])
|
||||
}
|
||||
|
||||
func testSavedAliasesAndExactPublicKeyFallback() throws {
|
||||
let ownPair = try Self.makeKeyPair()
|
||||
let peerPair = try Self.makeKeyPair()
|
||||
|
||||
let dialog = Self.makeDialog(
|
||||
account: ownPair.publicKeyHex,
|
||||
opponentKey: peerPair.publicKeyHex,
|
||||
username: "peer_user",
|
||||
title: "Peer User"
|
||||
)
|
||||
|
||||
let saved = SearchParityPolicy.localAugmentedUsers(
|
||||
query: "Saved Messages",
|
||||
currentPublicKey: ownPair.publicKeyHex,
|
||||
dialogs: [dialog]
|
||||
)
|
||||
XCTAssertEqual(saved.count, 1)
|
||||
XCTAssertEqual(saved.first?.publicKey, ownPair.publicKeyHex)
|
||||
XCTAssertEqual(saved.first?.title, "Saved Messages")
|
||||
|
||||
let exactPeer = SearchParityPolicy.localAugmentedUsers(
|
||||
query: "0x" + peerPair.publicKeyHex,
|
||||
currentPublicKey: ownPair.publicKeyHex,
|
||||
dialogs: [dialog]
|
||||
)
|
||||
XCTAssertEqual(exactPeer.count, 1)
|
||||
XCTAssertEqual(exactPeer.first?.publicKey, peerPair.publicKeyHex)
|
||||
XCTAssertEqual(exactPeer.first?.username, "peer_user")
|
||||
}
|
||||
|
||||
func testServerAndLocalMergeDedupesByPublicKeyWithServerPriority() {
|
||||
let key = "021111111111111111111111111111111111111111111111111111111111111111"
|
||||
let localOnlyKey = "022222222222222222222222222222222222222222222222222222222222222222"
|
||||
|
||||
let server = [
|
||||
SearchUser(username: "server_u", title: "Server Name", publicKey: key, verified: 2, online: 0),
|
||||
]
|
||||
let local = [
|
||||
SearchUser(username: "local_u", title: "Local Name", publicKey: key, verified: 0, online: 1),
|
||||
SearchUser(username: "local_only", title: "Local Only", publicKey: localOnlyKey, verified: 0, online: 1),
|
||||
]
|
||||
|
||||
let merged = SearchParityPolicy.mergeServerAndLocal(server: server, local: local)
|
||||
XCTAssertEqual(merged.count, 2)
|
||||
XCTAssertEqual(merged[0].publicKey, key)
|
||||
XCTAssertEqual(merged[0].title, "Server Name")
|
||||
XCTAssertEqual(merged[1].publicKey, localOnlyKey)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SearchParityTests {
|
||||
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)
|
||||
}
|
||||
|
||||
static func makeDialog(
|
||||
account: String,
|
||||
opponentKey: String,
|
||||
username: String,
|
||||
title: String
|
||||
) -> Dialog {
|
||||
Dialog(
|
||||
id: UUID().uuidString,
|
||||
account: account,
|
||||
opponentKey: opponentKey,
|
||||
opponentTitle: title,
|
||||
opponentUsername: username,
|
||||
lastMessage: "",
|
||||
lastMessageTimestamp: 0,
|
||||
unreadCount: 0,
|
||||
isOnline: true,
|
||||
lastSeen: 0,
|
||||
verified: 0,
|
||||
iHaveSent: true,
|
||||
isPinned: false,
|
||||
isMuted: false,
|
||||
lastMessageFromMe: false,
|
||||
lastMessageDelivered: .delivered,
|
||||
lastMessageRead: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockSearchDispatcher: SearchResultDispatching {
|
||||
var connectionState: ConnectionState = .authenticated
|
||||
var privateHash: String? = "mock-private-hash"
|
||||
private(set) var sentQueries: [String] = []
|
||||
private var handlers: [UUID: (PacketSearch) -> Void] = [:]
|
||||
|
||||
func sendSearchPacket(_ packet: PacketSearch) {
|
||||
sentQueries.append(packet.search)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func addSearchResultHandler(_ handler: @escaping (PacketSearch) -> Void) -> UUID {
|
||||
let id = UUID()
|
||||
handlers[id] = handler
|
||||
return id
|
||||
}
|
||||
|
||||
func removeSearchResultHandler(_ id: UUID) {
|
||||
handlers.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user