From 156e32b16b4e021da2335107c076b4a09196bf23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 29 Jul 2022 14:46:35 -0300 Subject: [PATCH] Add unit tests for RPCHistory --- Package.swift | 4 +- Sources/Commons/Either.swift | 12 ++ Sources/JSONRPC/RPCRequest.swift | 4 +- Sources/JSONRPC/RPCResponse.swift | 4 + Sources/WalletConnectRelay/RelayClient.swift | 4 +- Sources/WalletConnectUtils/RPCHistory.swift | 4 +- Tests/TestingUtils/Mocks/RPC.swift | 12 ++ .../RPCHistoryTests.swift | 110 ++++++++++++++++++ 8 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 Tests/TestingUtils/Mocks/RPC.swift create mode 100644 Tests/WalletConnectUtilsTests/RPCHistoryTests.swift diff --git a/Package.swift b/Package.swift index 4741a09f3..a6bd606d6 100644 --- a/Package.swift +++ b/Package.swift @@ -71,11 +71,11 @@ let package = Package( dependencies: ["WalletConnectKMS", "WalletConnectUtils", "TestingUtils"]), .target( name: "TestingUtils", - dependencies: ["WalletConnectUtils", "WalletConnectKMS"], + dependencies: ["WalletConnectUtils", "WalletConnectKMS", "JSONRPC"], path: "Tests/TestingUtils"), .testTarget( name: "WalletConnectUtilsTests", - dependencies: ["WalletConnectUtils"]), + dependencies: ["WalletConnectUtils", "JSONRPC", "TestingUtils"]), .testTarget( name: "JSONRPCTests", dependencies: ["JSONRPC", "TestingUtils"]), diff --git a/Sources/Commons/Either.swift b/Sources/Commons/Either.swift index 71e7b8dde..3c5f1fbdf 100644 --- a/Sources/Commons/Either.swift +++ b/Sources/Commons/Either.swift @@ -63,3 +63,15 @@ extension Either: Codable where L: Codable, R: Codable { } } } + +extension Either: CustomStringConvertible { + + public var description: String { + switch self { + case let .left(left): + return "\(left)" + case let .right(right): + return "\(right)" + } + } +} diff --git a/Sources/JSONRPC/RPCRequest.swift b/Sources/JSONRPC/RPCRequest.swift index 473550899..ad0bfed2b 100644 --- a/Sources/JSONRPC/RPCRequest.swift +++ b/Sources/JSONRPC/RPCRequest.swift @@ -72,11 +72,11 @@ public struct RPCRequest: Equatable { extension RPCRequest { - static func notification(method: String, params: C) -> RPCRequest where C: Codable { + public static func notification(method: String, params: C) -> RPCRequest where C: Codable { return RPCRequest(method: method, params: AnyCodable(params), id: nil) } - static func notification(method: String) -> RPCRequest { + public static func notification(method: String) -> RPCRequest { return RPCRequest(method: method, params: nil, id: nil) } diff --git a/Sources/JSONRPC/RPCResponse.swift b/Sources/JSONRPC/RPCResponse.swift index fec762286..d09055542 100644 --- a/Sources/JSONRPC/RPCResponse.swift +++ b/Sources/JSONRPC/RPCResponse.swift @@ -31,6 +31,10 @@ public struct RPCResponse: Equatable { self.init(id: matchingRequest.id, outcome: .success(AnyCodable(result))) } + public init(matchingRequest: RPCRequest, error: JSONRPCError) { + self.init(id: matchingRequest.id, outcome: .failure(error)) + } + public init(id: Int, result: C) where C: Codable { self.init(id: RPCID(id), outcome: .success(AnyCodable(result))) } diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index b5236567d..edc105070 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -198,7 +198,7 @@ public final class RelayClient { .wrapToIridium() .asRPCRequest() let message = try! request.asJSONEncodedString() - rpcHistory.delete(topic: topic) + rpcHistory.deleteAll(forTopic: topic) var cancellable: AnyCancellable? cancellable = requestAcknowledgePublisher .filter { $0 == request.id } @@ -225,7 +225,7 @@ public final class RelayClient { if let request = tryDecode(RPCRequest.self, from: payload) { if let params = try? request.params?.get(Subscription.Params.self) { do { - try rpcHistory.set(request, for: params.data.topic, emmitedBy: .remote) + try rpcHistory.set(request, forTopic: params.data.topic, emmitedBy: .remote) try acknowledgeRequest(request) onMessage?(params.data.topic, params.data.message) } catch { diff --git a/Sources/WalletConnectUtils/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory.swift index 50ad886f5..86c5b8d05 100644 --- a/Sources/WalletConnectUtils/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory.swift @@ -32,7 +32,7 @@ public final class RPCHistory { try? storage.get(key: "\(recordId)") } - public func set(_ request: RPCRequest, for topic: String, emmitedBy origin: Record.Origin) throws { + public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws { guard let id = request.id else { throw HistoryError.unidentifiedRequest } @@ -57,7 +57,7 @@ public final class RPCHistory { storage.set(record, forKey: "\(record.id)") } - public func delete(topic: String) { + public func deleteAll(forTopic topic: String) { storage.getAll().forEach { record in if record.topic == topic { storage.delete(forKey: "\(record.id)") diff --git a/Tests/TestingUtils/Mocks/RPC.swift b/Tests/TestingUtils/Mocks/RPC.swift new file mode 100644 index 000000000..964c472b1 --- /dev/null +++ b/Tests/TestingUtils/Mocks/RPC.swift @@ -0,0 +1,12 @@ +import JSONRPC + +public extension RPCRequest { + + static func stub() -> RPCRequest { + RPCRequest(method: "method", params: EmptyCodable()) + } + + static func stub(method: String, id: Int) -> RPCRequest { + RPCRequest(method: method, params: EmptyCodable(), id: id) + } +} diff --git a/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift new file mode 100644 index 000000000..10bfbc049 --- /dev/null +++ b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift @@ -0,0 +1,110 @@ +import XCTest +import JSONRPC +import TestingUtils +@testable import WalletConnectUtils + +final class RPCHistoryTests: XCTestCase { + + var sut: RPCHistory! + + override func setUp() { + let storage = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") + sut = RPCHistory(keyValueStore: storage) + } + + override func tearDown() { + sut = nil + } + + // MARK: History Storage Tests + + func testRoundTrip() throws { + let request = RPCRequest.stub() + try sut.set(request, forTopic: String.randomTopic(), emmitedBy: .local) + let record = sut.get(recordId: request.id!) + XCTAssertNil(record?.response) + XCTAssertEqual(record?.request, request) + } + + func testResolveSuccessAndError() throws { + let requestA = RPCRequest.stub() + let requestB = RPCRequest.stub() + let responseA = RPCResponse(matchingRequest: requestA, result: true) + let responseB = RPCResponse(matchingRequest: requestB, error: .internalError) + try sut.set(requestA, forTopic: String.randomTopic(), emmitedBy: .remote) + try sut.set(requestB, forTopic: String.randomTopic(), emmitedBy: .local) + try sut.resolve(responseA) + try sut.resolve(responseB) + let recordA = sut.get(recordId: requestA.id!) + let recordB = sut.get(recordId: requestB.id!) + XCTAssertEqual(recordA?.response, responseA) + XCTAssertEqual(recordB?.response, responseB) + } + + func testDelete() throws { + let requests = (1...5).map { _ in RPCRequest.stub() } + let topic = String.randomTopic() + try requests.forEach { try sut.set($0, forTopic: topic, emmitedBy: .local) } + sut.deleteAll(forTopic: topic) + requests.forEach { + XCTAssertNil(sut.get(recordId: $0.id!)) + } + } + + // MARK: Error Cases Tests + + func testSetUnidentifiedRequest() { + let expectedError = RPCHistory.HistoryError.unidentifiedRequest + + let request = RPCRequest.notification(method: "notify") + XCTAssertThrowsError(try sut.set(request, forTopic: String.randomTopic(), emmitedBy: .local)) { error in + XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError) + } + } + + func testSetDuplicateRequest() throws { + let expectedError = RPCHistory.HistoryError.requestDuplicateNotAllowed + + let id = Int.random() + let requestA = RPCRequest.stub(method: "method-1", id: id) + let requestB = RPCRequest.stub(method: "method-2", id: id) + let topic = String.randomTopic() + + try sut.set(requestA, forTopic: topic, emmitedBy: .local) + XCTAssertThrowsError(try sut.set(requestB, forTopic: topic, emmitedBy: .local)) { error in + XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError) + } + } + + func testResolveResponseWithoutRequest() throws { + let expectedError = RPCHistory.HistoryError.requestMatchingResponseNotFound + + let response = RPCResponse(id: 0, result: true) + XCTAssertThrowsError(try sut.resolve(response)) { error in + XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError) + } + } + + func testResolveUnidentifiedResponse() throws { + let expectedError = RPCHistory.HistoryError.unidentifiedResponse + + let response = RPCResponse(errorWithoutID: JSONRPCError.internalError) + XCTAssertThrowsError(try sut.resolve(response)) { error in + XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError) + } + } + + func testResolveDuplicateResponse() throws { + let expectedError = RPCHistory.HistoryError.responseDuplicateNotAllowed + + let request = RPCRequest.stub() + let responseA = RPCResponse(matchingRequest: request, result: true) + let responseB = RPCResponse(matchingRequest: request, result: false) + + try sut.set(request, forTopic: String.randomTopic(), emmitedBy: .local) + try sut.resolve(responseA) + XCTAssertThrowsError(try sut.resolve(responseB)) { error in + XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError) + } + } +}