diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift index 4732eccf5d7792700211c1a9d152f280f89d9584..8aca65c4a59f7d55d350f5ee8c9b189d1217fbe5 100644 --- a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift @@ -3,18 +3,49 @@ import XCTestDynamicOverlay import XXClient public struct MessengerSendFile { - public typealias Callback = (Data, XXClient.Progress) -> Void + public struct Params: Equatable { + public init( + file: FileSend, + recipientId: Data, + retry: Int = 3, + callbackIntervalMS: Int = 250 + ) { + self.file = file + self.recipientId = recipientId + self.retry = retry + self.callbackIntervalMS = callbackIntervalMS + } + + public var file: FileSend + public var recipientId: Data + public var retry: Int + public var callbackIntervalMS: Int + } + + public enum CallbackInfo: Equatable { + public enum Failure: Equatable { + case error(NSError) + case progressError(String) + case close(NSError) + } + + case progress(id: Data, transmitted: Int, total: Int) + case finished(id: Data) + case failed(id: Data, Failure) + } + + public typealias Callback = (CallbackInfo) -> Void public enum Error: Swift.Error, Equatable { case notConnected } - public var run: (FileTransferSend.Params, @escaping Callback) throws -> Void + public var run: (Params, @escaping Callback) throws -> Data public func callAsFunction( - _ params: FileTransferSend.Params, + _ params: Params, callback: @escaping Callback - ) throws -> Void { + ) throws -> Data { try run(params, callback) } } @@ -35,33 +66,48 @@ extension MessengerSendFile { fatalError("Bindings issue: ReceiveFileCallback called when sending file.") } ) - let semaphore = DispatchSemaphore(value: 0) + func close(id: Data) { + do { + try fileTransfer.closeSend(transferId: id) + } catch { + callback(.failed(id: id, .close(error as NSError))) + } + } var transferId: Data! - var error: Swift.Error? transferId = try fileTransfer.send( - params: params, + params: FileTransferSend.Params( + payload: params.file, + recipientId: params.recipientId, + retry: Float(params.retry), + period: params.callbackIntervalMS + ), callback: FileTransferProgressCallback { result in guard let transferId else { fatalError("Bindings issue: file transfer progress callback was called before send function returned transfer id.") } switch result { - case .failure(let err): - error = err - semaphore.signal() + case .failure(let error): + callback(.failed(id: transferId, .error(error))) + close(id: transferId) case .success(let cb): - callback(transferId, cb.progress) - if cb.progress.completed || cb.progress.error != nil { - semaphore.signal() + if let error = cb.progress.error { + callback(.failed(id: transferId, .progressError(error))) + close(id: transferId) + } else if cb.progress.completed { + callback(.finished(id: transferId)) + close(id: transferId) + } else { + callback(.progress( + id: transferId, + transmitted: cb.progress.transmitted, + total: cb.progress.total + )) } } } ) - semaphore.wait() - try fileTransfer.closeSend(transferId: transferId) - if let error { - throw error - } + return transferId } } } diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..9adc15b1d89fe2f19445629fb1381b5520661233 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift @@ -0,0 +1,261 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerSendFileTests: XCTestCase { + func testSendFile() throws { + let e2eId = 123 + let e2eFileTransferParams = "e2eFileTransferParams".data(using: .utf8)! + let fileTransferParams = "fileTransferParams".data(using: .utf8)! + let newTransferId = "transferId".data(using: .utf8)! + + var didInitFileTransfer: [InitFileTransfer.Params] = [] + var didSendFile: [FileTransferSend.Params] = [] + var didCloseSend: [Data] = [] + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + return e2e + } + env.getE2EFileTransferParams.run = { e2eFileTransferParams } + env.getFileTransferParams.run = { fileTransferParams } + env.initFileTransfer.run = { params, callback in + didInitFileTransfer.append(params) + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { params, callback in + didSendFile.append(params) + fileTransferProgressCallback = callback + return newTransferId + } + fileTransfer.closeSend.run = { id in + didCloseSend.append(id) + } + return fileTransfer + } + + let sendFile: MessengerSendFile = .live(env) + let params = MessengerSendFile.Params.stub + + let transferId = try sendFile(params) { info in + didReceiveCallback.append(info) + } + + XCTAssertNoDifference(transferId, newTransferId) + XCTAssertNoDifference(didInitFileTransfer, [ + .init( + e2eId: e2eId, + e2eFileTransferParamsJSON: e2eFileTransferParams, + fileTransferParamsJSON: fileTransferParams + ) + ]) + XCTAssertNoDifference(didSendFile, [ + .init( + payload: params.file, + recipientId: params.recipientId, + retry: Float(params.retry), + period: params.callbackIntervalMS + ) + ]) + + fileTransferProgressCallback.handle(.success(.init( + progress: Progress( + completed: false, + transmitted: 1, + total: 10, + error: nil + ), + partTracker: .unimplemented + ))) + fileTransferProgressCallback.handle(.success(.init( + progress: Progress( + completed: false, + transmitted: 6, + total: 10, + error: nil + ), + partTracker: .unimplemented + ))) + fileTransferProgressCallback.handle(.success(.init( + progress: Progress( + completed: true, + transmitted: 10, + total: 10, + error: nil + ), + partTracker: .unimplemented + ))) + + XCTAssertNoDifference(didReceiveCallback, [ + .progress(id: transferId, transmitted: 1, total: 10), + .progress(id: transferId, transmitted: 6, total: 10), + .finished(id: transferId), + ]) + XCTAssertNoDifference(didCloseSend, [transferId]) + } + + func testSendFileWhenNotConnected() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { nil } + let sendFile: MessengerSendFile = .live(env) + + XCTAssertThrowsError(try sendFile(.stub) { _ in }) { error in + XCTAssertNoDifference( + error as? MessengerSendFile.Error, + MessengerSendFile.Error.notConnected + ) + } + } + + func testSendFileCallbackFailure() throws { + var didCloseSend: [Data] = [] + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.getE2EFileTransferParams.run = { Data() } + env.getFileTransferParams.run = { Data() } + env.initFileTransfer.run = { params, callback in + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { _, callback in + fileTransferProgressCallback = callback + return "transferId".data(using: .utf8)! + } + fileTransfer.closeSend.run = { id in + didCloseSend.append(id) + } + return fileTransfer + } + let sendFile: MessengerSendFile = .live(env) + + let transferId = try sendFile(.stub) { info in + didReceiveCallback.append(info) + } + + let error = NSError(domain: "test", code: 1234) + fileTransferProgressCallback.handle(.failure(error)) + + XCTAssertNoDifference(didReceiveCallback, [ + .failed(id: transferId, .error(error)), + ]) + XCTAssertNoDifference(didCloseSend, [transferId]) + } + + func testSendFileProgressError() throws { + var didCloseSend: [Data] = [] + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.getE2EFileTransferParams.run = { Data() } + env.getFileTransferParams.run = { Data() } + env.initFileTransfer.run = { params, callback in + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { _, callback in + fileTransferProgressCallback = callback + return "transferId".data(using: .utf8)! + } + fileTransfer.closeSend.run = { id in + didCloseSend.append(id) + } + return fileTransfer + } + let sendFile: MessengerSendFile = .live(env) + + let transferId = try sendFile(.stub) { info in + didReceiveCallback.append(info) + } + + let error = "something went wrong" + fileTransferProgressCallback.handle(.success(.init( + progress: .init( + completed: false, + transmitted: 0, + total: 0, + error: error + ), + partTracker: .unimplemented + ))) + + XCTAssertNoDifference(didReceiveCallback, [ + .failed(id: transferId, .progressError(error)), + ]) + XCTAssertNoDifference(didCloseSend, [transferId]) + } + + func testSendFileCloseError() throws { + let closeError = NSError(domain: "test", code: 1234) + + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.getE2EFileTransferParams.run = { Data() } + env.getFileTransferParams.run = { Data() } + env.initFileTransfer.run = { params, callback in + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { _, callback in + fileTransferProgressCallback = callback + return "transferId".data(using: .utf8)! + } + fileTransfer.closeSend.run = { id in + throw closeError + } + return fileTransfer + } + let sendFile: MessengerSendFile = .live(env) + + let transferId = try sendFile(.stub) { info in + didReceiveCallback.append(info) + } + + fileTransferProgressCallback.handle(.success(.init( + progress: .init( + completed: true, + transmitted: 1, + total: 1, + error: nil + ), + partTracker: .unimplemented + ))) + + XCTAssertNoDifference(didReceiveCallback, [ + .finished(id: transferId), + .failed(id: transferId, .close(closeError)), + ]) + } +} + +private extension MessengerSendFile.Params { + static let stub = MessengerSendFile.Params( + file: FileSend( + name: "file-name", + type: "file-type", + preview: "file-preview".data(using: .utf8)!, + contents: "file-contents".data(using: .utf8)! + ), + recipientId: "recipient-id".data(using: .utf8)!, + retry: 123, + callbackIntervalMS: 321 + ) +}