From abd2741fba395985aac2b0fb3468b6d527708f7a Mon Sep 17 00:00:00 2001 From: Vladislav Alekseev Date: Mon, 28 Sep 2020 12:52:10 +0300 Subject: [PATCH] JobResultFetcher --- Package.swift | 8 -- .../RunTestsOnRemoteQueueCommand.swift | 58 +++++++-- .../Clients/JobDeleter/JobDeleter.swift | 12 ++ .../Clients/JobDeleter/JobDeleterImpl.swift | 32 +++++ .../JobResultsFetcher/JobResultsFetcher.swift | 12 ++ .../JobResultsFetcherImpl.swift | 35 ++++++ Sources/QueueClient/QueueClient.swift | 111 ------------------ Sources/QueueClient/QueueClientDelegate.swift | 8 -- Sources/QueueClient/QueueClientError.swift | 27 ----- .../QueueClient/SynchronousQueueClient.swift | 101 ---------------- .../Endpoints/JobDeleteEndpoint.swift | 4 +- .../Endpoints/JobResultsEndpoint.swift | 4 +- Sources/RESTInterfaces/RESTMethod.swift | 2 - .../JobDelete/JobDeletePayload.swift | 11 ++ .../JobDelete/JobDeleteRESTMethod.swift | 8 ++ .../JobDelete/JobDeleteRequest.swift | 18 +-- .../JobResults/JobResultsPayload.swift | 10 ++ .../JobResults/JobResultsRESTMethod.swift | 8 ++ .../JobResults/JobResultsRequest.swift | 18 +-- .../FakeQueueClientDelegate.swift | 25 ---- Tests/QueueClientTests/JobDeleterTests.swift | 37 ++++++ .../JobResultsFetcherTests.swift | 46 ++++++++ Tests/QueueClientTests/QueueClientTests.swift | 62 ---------- .../JobDeleteEndpointTests.swift | 4 +- .../JobResultsEndpointTests.swift | 4 +- Tests/QueueServerTests/QueueServerTests.swift | 4 - 26 files changed, 286 insertions(+), 383 deletions(-) create mode 100644 Sources/QueueClient/Clients/JobDeleter/JobDeleter.swift create mode 100644 Sources/QueueClient/Clients/JobDeleter/JobDeleterImpl.swift create mode 100644 Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcher.swift create mode 100644 Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcherImpl.swift delete mode 100644 Sources/QueueClient/QueueClient.swift delete mode 100644 Sources/QueueClient/QueueClientDelegate.swift delete mode 100644 Sources/QueueClient/QueueClientError.swift delete mode 100644 Sources/QueueClient/SynchronousQueueClient.swift create mode 100644 Sources/RESTMethods/JobDelete/JobDeletePayload.swift create mode 100644 Sources/RESTMethods/JobDelete/JobDeleteRESTMethod.swift create mode 100644 Sources/RESTMethods/JobResults/JobResultsPayload.swift create mode 100644 Sources/RESTMethods/JobResults/JobResultsRESTMethod.swift delete mode 100644 Tests/QueueClientTests/FakeQueueClientDelegate.swift create mode 100644 Tests/QueueClientTests/JobDeleterTests.swift create mode 100644 Tests/QueueClientTests/JobResultsFetcherTests.swift delete mode 100644 Tests/QueueClientTests/QueueClientTests.swift diff --git a/Package.swift b/Package.swift index 69054e98..30f16965 100644 --- a/Package.swift +++ b/Package.swift @@ -1137,7 +1137,6 @@ let package = Package( "DistWorkerModels", "Logging", "QueueModels", - "RESTInterfaces", "RESTMethods", "RequestSender", "ScheduleStrategy", @@ -1153,22 +1152,15 @@ let package = Package( // MARK: QueueClientTests name: "QueueClientTests", dependencies: [ - "BuildArtifactsTestHelpers", "DistWorkerModels", "DistWorkerModelsTestHelpers", "QueueClient", "QueueModels", "QueueModelsTestHelpers", - "RESTInterfaces", "RESTMethods", "RequestSender", "RequestSenderTestHelpers", - "RunnerModels", - "RunnerTestHelpers", - "SimulatorPoolTestHelpers", "SocketModels", - "Swifter", - "SynchronousWaiter", "TestHelpers", "Types", "WorkerAlivenessModels", diff --git a/Sources/EmceeLib/Commands/RunTestsOnRemoteQueueCommand.swift b/Sources/EmceeLib/Commands/RunTestsOnRemoteQueueCommand.swift index c3b9a35a..466ed854 100644 --- a/Sources/EmceeLib/Commands/RunTestsOnRemoteQueueCommand.swift +++ b/Sources/EmceeLib/Commands/RunTestsOnRemoteQueueCommand.swift @@ -196,16 +196,27 @@ public final class RunTestsOnRemoteQueueCommand: Command { ), for: TestDiscoveryQuerier.self ) - - let queueClient = SynchronousQueueClient(queueServerAddress: queueServerAddress) + di.set( + JobStateFetcherImpl( + requestSender: try di.get(RequestSenderProvider.self).requestSender(socketAddress: queueServerAddress) + ), + for: JobStateFetcher.self + ) + di.set( + JobResultsFetcherImpl( + requestSender: try di.get(RequestSenderProvider.self).requestSender(socketAddress: queueServerAddress) + ), + for: JobResultsFetcher.self + ) + di.set( + JobDeleterImpl( + requestSender: try di.get(RequestSenderProvider.self).requestSender(socketAddress: queueServerAddress) + ), + for: JobDeleter.self + ) defer { - Logger.info("Will delete job \(testArgFile.prioritizedJob)") - do { - _ = try queueClient.delete(jobId: testArgFile.prioritizedJob.jobId) - } catch { - Logger.error("Failed to delete job \(testArgFile.prioritizedJob): \(error)") - } + deleteJob(jobId: testArgFile.prioritizedJob.jobId) } try JobPreparer(di: di).formJob( @@ -215,11 +226,8 @@ public final class RunTestsOnRemoteQueueCommand: Command { testArgFile: testArgFile ) - Logger.debug("Will now wait for job queue to deplete") try waitForJobQueueToDeplete(jobId: testArgFile.prioritizedJob.jobId) - - Logger.debug("Fetching job results") - return try queueClient.jobResults(jobId: testArgFile.prioritizedJob.jobId) + return try fetchJobResults(jobId: testArgFile.prioritizedJob.jobId) } private func waitForJobQueueToDeplete(jobId: JobId) throws { @@ -229,7 +237,7 @@ public final class RunTestsOnRemoteQueueCommand: Command { caughtSignal = true } - try di.get(Waiter.self).waitWhile(pollPeriod: 30.0, description: "Wait for job queue to deplete") { + try di.get(Waiter.self).waitWhile(pollPeriod: 30.0, description: "Waiting for job queue to deplete") { if caughtSignal { return false } let state = try fetchJobState(jobId: jobId) @@ -240,6 +248,16 @@ public final class RunTestsOnRemoteQueueCommand: Command { } } + private func fetchJobResults(jobId: JobId) throws -> JobResults { + let callbackWaiter: CallbackWaiter> = try di.get(Waiter.self).createCallbackWaiter() + try di.get(JobResultsFetcher.self).fetch( + jobId: jobId, + callbackQueue: callbackQueue, + completion: callbackWaiter.set + ) + return try callbackWaiter.wait(timeout: .infinity, description: "Fetching job results").dematerialize() + } + private func fetchJobState(jobId: JobId) throws -> JobState { let callbackWaiter: CallbackWaiter> = try di.get(Waiter.self).createCallbackWaiter() try di.get(JobStateFetcher.self).fetch( @@ -258,4 +276,18 @@ public final class RunTestsOnRemoteQueueCommand: Command { guard let port = ports.sorted().last else { throw NoRunningQueueFoundError() } return port } + + private func deleteJob(jobId: JobId) { + do { + let callbackWaiter: CallbackWaiter> = try di.get(Waiter.self).createCallbackWaiter() + try di.get(JobDeleter.self).delete( + jobId: jobId, + callbackQueue: callbackQueue, + completion: callbackWaiter.set + ) + try callbackWaiter.wait(timeout: .infinity, description: "Deleting job").dematerialize() + } catch { + Logger.error("Failed to delete job: \(error)") + } + } } diff --git a/Sources/QueueClient/Clients/JobDeleter/JobDeleter.swift b/Sources/QueueClient/Clients/JobDeleter/JobDeleter.swift new file mode 100644 index 00000000..dfd4d411 --- /dev/null +++ b/Sources/QueueClient/Clients/JobDeleter/JobDeleter.swift @@ -0,0 +1,12 @@ +import Dispatch +import Foundation +import QueueModels +import Types + +public protocol JobDeleter { + func delete( + jobId: JobId, + callbackQueue: DispatchQueue, + completion: @escaping (Either<(), Error>) -> () + ) +} diff --git a/Sources/QueueClient/Clients/JobDeleter/JobDeleterImpl.swift b/Sources/QueueClient/Clients/JobDeleter/JobDeleterImpl.swift new file mode 100644 index 00000000..881b77e6 --- /dev/null +++ b/Sources/QueueClient/Clients/JobDeleter/JobDeleterImpl.swift @@ -0,0 +1,32 @@ +import Dispatch +import Foundation +import QueueModels +import RESTMethods +import RequestSender +import Types + +public final class JobDeleterImpl: JobDeleter { + private let requestSender: RequestSender + + public init(requestSender: RequestSender) { + self.requestSender = requestSender + } + + public func delete( + jobId: JobId, + callbackQueue: DispatchQueue, + completion: @escaping (Either<(), Error>) -> () + ) { + let request = JobDeleteRequest( + payload: JobDeletePayload( + jobId: jobId + ) + ) + requestSender.sendRequestWithCallback( + request: request, + callbackQueue: callbackQueue + ) { (result: Either) in + completion(result.mapResult { _ in () }) + } + } +} diff --git a/Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcher.swift b/Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcher.swift new file mode 100644 index 00000000..f526090a --- /dev/null +++ b/Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcher.swift @@ -0,0 +1,12 @@ +import Dispatch +import Foundation +import QueueModels +import Types + +public protocol JobResultsFetcher { + func fetch( + jobId: JobId, + callbackQueue: DispatchQueue, + completion: @escaping (Either) -> () + ) +} diff --git a/Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcherImpl.swift b/Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcherImpl.swift new file mode 100644 index 00000000..b379d834 --- /dev/null +++ b/Sources/QueueClient/Clients/JobResultsFetcher/JobResultsFetcherImpl.swift @@ -0,0 +1,35 @@ +import Foundation +import QueueModels +import RESTMethods +import RequestSender +import Types + +public final class JobResultsFetcherImpl: JobResultsFetcher { + private let requestSender: RequestSender + + public init(requestSender: RequestSender) { + self.requestSender = requestSender + } + + public func fetch( + jobId: JobId, + callbackQueue: DispatchQueue, + completion: @escaping (Either) -> () + ) { + let request = JobResultRequest( + payload: JobResultsPayload( + jobId: jobId + ) + ) + + requestSender.sendRequestWithCallback( + request: request, + callbackQueue: callbackQueue, + callback: { (response: Either) in + completion( + response.mapResult { $0.jobResults } + ) + } + ) + } +} diff --git a/Sources/QueueClient/QueueClient.swift b/Sources/QueueClient/QueueClient.swift deleted file mode 100644 index d08c4eb6..00000000 --- a/Sources/QueueClient/QueueClient.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Dispatch -import Foundation -import Logging -import QueueModels -import RESTInterfaces -import RESTMethods -import RequestSender -import ScheduleStrategy -import SocketModels - -public final class QueueClient { - public weak var delegate: QueueClientDelegate? - private let queueServerAddress: SocketAddress - private let urlSession = URLSession(configuration: URLSessionConfiguration.default) - private let encoder = JSONEncoder.pretty() - private var isClosed = false - private let requestSender: RequestSender - - public init(queueServerAddress: SocketAddress, requestSenderProvider: RequestSenderProvider) { - self.queueServerAddress = queueServerAddress - self.requestSender = requestSenderProvider.requestSender(socketAddress: queueServerAddress) - } - - deinit { - close() - } - - public func close() { - requestSender.close() - - Logger.verboseDebug("Invalidating queue client URL session") - urlSession.finishTasksAndInvalidate() - isClosed = true - } - - public func fetchJobResults(jobId: JobId) throws { - try sendRequest( - .jobResults, - payload: JobResultsRequest(jobId: jobId), - completionHandler: handleJobResultsResponse - ) - } - - public func deleteJob(jobId: JobId) throws { - try sendRequest( - .jobDelete, - payload: JobDeleteRequest(jobId: jobId), - completionHandler: handleJobDeleteResponse - ) - } - - // MARK: - Request Generation - - private func sendRequest( - _ restMethod: RESTMethod, - payload: Payload, - completionHandler: @escaping (Response) throws -> ()) - throws where Payload : Encodable, Response : Decodable - { - guard !isClosed else { throw QueueClientError.queueClientIsClosed(restMethod) } - let url = createUrl(restMethod: restMethod) - let jsonData = try encoder.encode(payload) - if let stringJson = String(data: jsonData, encoding: .utf8) { - Logger.verboseDebug("Sending request to \(url): \(stringJson)") - } else { - Logger.verboseDebug("Sending request to \(url): unable to get string for json data \(jsonData.count) bytes") - } - var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: .infinity) - urlRequest.httpMethod = "POST" - urlRequest.addValue("Content-Type", forHTTPHeaderField: "application/json") - urlRequest.httpBody = jsonData - let dataTask = urlSession.dataTask(with: urlRequest) { [weak self] (data: Data?, response: URLResponse?, error: Error?) in - guard let strongSelf = self else { return } - - if let error = error { - strongSelf.delegate?.queueClient(strongSelf, didFailWithError: QueueClientError.communicationError(error)); return - } - guard let data = data else { - strongSelf.delegate?.queueClient(strongSelf, didFailWithError: QueueClientError.noData); return - } - do { - try completionHandler(try JSONDecoder().decode(Response.self, from: data)) - } catch { - strongSelf.delegate?.queueClient(strongSelf, didFailWithError: QueueClientError.parseError(error, data)); return - } - } - dataTask.resume() - } - - private func createUrl(restMethod: RESTMethod) -> URL { - var components = URLComponents() - components.scheme = "http" - components.host = queueServerAddress.host - components.port = queueServerAddress.port.value - components.path = restMethod.pathWithLeadingSlash - guard let url = components.url else { - Logger.fatal("Unable to convert components to url: \(components)") - } - return url - } - - // MARK: - Response Handlers - - private func handleJobResultsResponse(response: JobResultsResponse) { - delegate?.queueClient(self, didFetchJobResults: response.jobResults) - } - - private func handleJobDeleteResponse(response: JobDeleteResponse) { - delegate?.queueClient(self, didDeleteJob: response.jobId) - } -} diff --git a/Sources/QueueClient/QueueClientDelegate.swift b/Sources/QueueClient/QueueClientDelegate.swift deleted file mode 100644 index 45dbf48b..00000000 --- a/Sources/QueueClient/QueueClientDelegate.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation -import QueueModels - -public protocol QueueClientDelegate: class { - func queueClient(_ sender: QueueClient, didFailWithError error: QueueClientError) - func queueClient(_ sender: QueueClient, didFetchJobResults jobResults: JobResults) - func queueClient(_ sender: QueueClient, didDeleteJob jobId: JobId) -} diff --git a/Sources/QueueClient/QueueClientError.swift b/Sources/QueueClient/QueueClientError.swift deleted file mode 100644 index 505dba87..00000000 --- a/Sources/QueueClient/QueueClientError.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import RESTInterfaces - -public enum QueueClientError: Error, CustomStringConvertible { - case noData - case unexpectedResponse(Data) - case communicationError(Error) - case parseError(Error, Data) - case queueClientIsClosed(RESTMethod) - - public var description: String { - switch self { - case .noData: - return "Unexpected response: No data received" - case .unexpectedResponse(let data): - let string = String(data: data, encoding: .utf8) ?? "\(data.count) bytes" - return "Unexpected response: \(string)" - case .communicationError(let underlyingError): - return "Response had an error: \(underlyingError)" - case .parseError(let error, let data): - let string = String(data: data, encoding: .utf8) ?? "\(data.count) bytes" - return "Failed to parse response: \(error). Data: \(string)" - case .queueClientIsClosed(let restMethod): - return "Queue client has been closed, can't make request to: '\(restMethod)'" - } - } -} diff --git a/Sources/QueueClient/SynchronousQueueClient.swift b/Sources/QueueClient/SynchronousQueueClient.swift deleted file mode 100644 index fbbeb190..00000000 --- a/Sources/QueueClient/SynchronousQueueClient.swift +++ /dev/null @@ -1,101 +0,0 @@ -import Dispatch -import Foundation -import Logging -import QueueModels -import RESTMethods -import RequestSender -import ScheduleStrategy -import SocketModels -import SynchronousWaiter -import Types - -public final class SynchronousQueueClient: QueueClientDelegate { - private let queueClient: QueueClient - private var jobResultsResult: Either? - private var jobDeleteResult: Either? - private let syncQueue = DispatchQueue(label: "ru.avito.SynchronousQueueClient") - private let requestTimeout: TimeInterval - private let networkRequestRetryCount: Int - - public init( - queueServerAddress: SocketAddress, - requestTimeout: TimeInterval = 10, - networkRequestRetryCount: Int = 5 - ) { - self.requestTimeout = requestTimeout - self.networkRequestRetryCount = networkRequestRetryCount - self.queueClient = QueueClient( - queueServerAddress: queueServerAddress, - requestSenderProvider: DefaultRequestSenderProvider() - ) - self.queueClient.delegate = self - } - - public func close() { - queueClient.close() - } - - // MARK: Public API - - public func jobResults(jobId: JobId) throws -> JobResults { - return try synchronize { - jobResultsResult = nil - return try runRetrying { - try queueClient.fetchJobResults(jobId: jobId) - try SynchronousWaiter().waitWhile(timeout: requestTimeout, description: "Wait for \(jobId) job results") { - self.jobResultsResult == nil - } - return try jobResultsResult!.dematerialize() - } - } - } - - public func delete(jobId: JobId) throws -> JobId { - return try synchronize { - jobDeleteResult = nil - try queueClient.deleteJob(jobId: jobId) - try SynchronousWaiter().waitWhile(timeout: requestTimeout, description: "Wait for job \(jobId) to be deleted") { - self.jobDeleteResult == nil - } - return try jobDeleteResult!.dematerialize() - } - } - - // MARK: - Private - - private func synchronize(_ work: () throws -> T) rethrows -> T { - return try syncQueue.sync { - return try work() - } - } - - private func runRetrying(_ work: () throws -> T) rethrows -> T { - for retryIndex in 0 ..< networkRequestRetryCount { - if retryIndex > 0 { - Logger.verboseDebug("Attempting to send request: #\(retryIndex + 1) of \(networkRequestRetryCount)") - } - do { - return try work() - } catch { - Logger.error("Failed to send request with error: \(error)") - SynchronousWaiter().wait(timeout: 1.0, description: "Pause between request retries") - } - } - return try work() - } - - // MARK: - Queue Delegate - - public func queueClient(_ sender: QueueClient, didFailWithError error: QueueClientError) { - jobResultsResult = Either.error(error) - jobDeleteResult = Either.error(error) - } - - public func queueClient(_ sender: QueueClient, didFetchJobResults jobResults: JobResults) { - jobResultsResult = Either.success(jobResults) - } - - public func queueClient(_ sender: QueueClient, didDeleteJob jobId: JobId) { - jobDeleteResult = Either.success(jobId) - } -} diff --git a/Sources/QueueServer/Endpoints/JobDeleteEndpoint.swift b/Sources/QueueServer/Endpoints/JobDeleteEndpoint.swift index 88db0d94..2d9e283c 100644 --- a/Sources/QueueServer/Endpoints/JobDeleteEndpoint.swift +++ b/Sources/QueueServer/Endpoints/JobDeleteEndpoint.swift @@ -6,14 +6,14 @@ import RESTServer public final class JobDeleteEndpoint: RESTEndpoint { private let jobManipulator: JobManipulator - public let path: RESTPath = RESTMethod.jobDelete + public let path: RESTPath = JobDeleteRESTMethod() public let requestIndicatesActivity = true public init(jobManipulator: JobManipulator) { self.jobManipulator = jobManipulator } - public func handle(payload: JobDeleteRequest) throws -> JobDeleteResponse { + public func handle(payload: JobDeletePayload) throws -> JobDeleteResponse { try jobManipulator.delete(jobId: payload.jobId) return JobDeleteResponse(jobId: payload.jobId) } diff --git a/Sources/QueueServer/Endpoints/JobResultsEndpoint.swift b/Sources/QueueServer/Endpoints/JobResultsEndpoint.swift index 53caf149..9ca4a4db 100644 --- a/Sources/QueueServer/Endpoints/JobResultsEndpoint.swift +++ b/Sources/QueueServer/Endpoints/JobResultsEndpoint.swift @@ -6,14 +6,14 @@ import RESTServer public final class JobResultsEndpoint: RESTEndpoint { private let jobResultsProvider: JobResultsProvider - public let path: RESTPath = RESTMethod.jobResults + public let path: RESTPath = JobResultsRESTMethod() public let requestIndicatesActivity = true public init(jobResultsProvider: JobResultsProvider) { self.jobResultsProvider = jobResultsProvider } - public func handle(payload: JobResultsRequest) throws -> JobResultsResponse { + public func handle(payload: JobResultsPayload) throws -> JobResultsResponse { let jobResults = try jobResultsProvider.results(jobId: payload.jobId) return JobResultsResponse(jobResults: jobResults) } diff --git a/Sources/RESTInterfaces/RESTMethod.swift b/Sources/RESTInterfaces/RESTMethod.swift index 7dfe947a..c4ca8ae0 100644 --- a/Sources/RESTInterfaces/RESTMethod.swift +++ b/Sources/RESTInterfaces/RESTMethod.swift @@ -6,8 +6,6 @@ public enum RESTMethod: String, RESTPath { case disableWorker case enableWorker case getBucket - case jobDelete - case jobResults case queueVersion case registerWorker case reportAlive diff --git a/Sources/RESTMethods/JobDelete/JobDeletePayload.swift b/Sources/RESTMethods/JobDelete/JobDeletePayload.swift new file mode 100644 index 00000000..16a3a59f --- /dev/null +++ b/Sources/RESTMethods/JobDelete/JobDeletePayload.swift @@ -0,0 +1,11 @@ +import Foundation +import QueueModels + +public final class JobDeletePayload: Codable { + public let jobId: JobId + + public init(jobId: JobId) { + self.jobId = jobId + } +} + diff --git a/Sources/RESTMethods/JobDelete/JobDeleteRESTMethod.swift b/Sources/RESTMethods/JobDelete/JobDeleteRESTMethod.swift new file mode 100644 index 00000000..7bd0101a --- /dev/null +++ b/Sources/RESTMethods/JobDelete/JobDeleteRESTMethod.swift @@ -0,0 +1,8 @@ +import Foundation +import RESTInterfaces + +public final class JobDeleteRESTMethod: RESTPath { + public init() {} + + public var pathWithLeadingSlash: String { "/jobDelete" } +} diff --git a/Sources/RESTMethods/JobDelete/JobDeleteRequest.swift b/Sources/RESTMethods/JobDelete/JobDeleteRequest.swift index 2692d3be..e04f695e 100644 --- a/Sources/RESTMethods/JobDelete/JobDeleteRequest.swift +++ b/Sources/RESTMethods/JobDelete/JobDeleteRequest.swift @@ -1,11 +1,15 @@ import Foundation -import QueueModels +import RESTInterfaces +import RequestSender -public final class JobDeleteRequest: Codable { - public let jobId: JobId - - public init(jobId: JobId) { - self.jobId = jobId +public final class JobDeleteRequest: NetworkRequest { + public typealias Response = JobDeleteResponse + + public let httpMethod = HTTPMethod.post + public let pathWithLeadingSlash = JobDeleteRESTMethod().pathWithLeadingSlash + + public let payload: JobDeletePayload? + public init(payload: JobDeletePayload) { + self.payload = payload } } - diff --git a/Sources/RESTMethods/JobResults/JobResultsPayload.swift b/Sources/RESTMethods/JobResults/JobResultsPayload.swift new file mode 100644 index 00000000..7b6abdab --- /dev/null +++ b/Sources/RESTMethods/JobResults/JobResultsPayload.swift @@ -0,0 +1,10 @@ +import Foundation +import QueueModels + +public final class JobResultsPayload: Codable { + public let jobId: JobId + + public init(jobId: JobId) { + self.jobId = jobId + } +} diff --git a/Sources/RESTMethods/JobResults/JobResultsRESTMethod.swift b/Sources/RESTMethods/JobResults/JobResultsRESTMethod.swift new file mode 100644 index 00000000..2da6fa96 --- /dev/null +++ b/Sources/RESTMethods/JobResults/JobResultsRESTMethod.swift @@ -0,0 +1,8 @@ +import Foundation +import RESTInterfaces + +public final class JobResultsRESTMethod: RESTPath { + public init() {} + + public var pathWithLeadingSlash: String { "/jobResults" } +} diff --git a/Sources/RESTMethods/JobResults/JobResultsRequest.swift b/Sources/RESTMethods/JobResults/JobResultsRequest.swift index ae1c6c84..c295de05 100644 --- a/Sources/RESTMethods/JobResults/JobResultsRequest.swift +++ b/Sources/RESTMethods/JobResults/JobResultsRequest.swift @@ -1,10 +1,14 @@ -import Foundation -import QueueModels +import RESTInterfaces +import RequestSender -public final class JobResultsRequest: Codable { - public let jobId: JobId - - public init(jobId: JobId) { - self.jobId = jobId +public final class JobResultRequest: NetworkRequest { + public typealias Response = JobResultsResponse + + public let httpMethod = HTTPMethod.post + public let pathWithLeadingSlash = JobResultsRESTMethod().pathWithLeadingSlash + + public let payload: JobResultsPayload? + public init(payload: JobResultsPayload) { + self.payload = payload } } diff --git a/Tests/QueueClientTests/FakeQueueClientDelegate.swift b/Tests/QueueClientTests/FakeQueueClientDelegate.swift deleted file mode 100644 index bfa4878a..00000000 --- a/Tests/QueueClientTests/FakeQueueClientDelegate.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation -import QueueClient -import QueueModels - -class FakeQueueClientDelegate: QueueClientDelegate { - enum ServerResponse { - case error(QueueClientError) - case fecthedJobResults(JobResults) - case deletedJob(JobId) - } - - var responses = [ServerResponse]() - - func queueClient(_ sender: QueueClient, didFailWithError error: QueueClientError) { - responses.append(ServerResponse.error(error)) - } - - func queueClient(_ sender: QueueClient, didFetchJobResults jobResults: JobResults) { - responses.append(ServerResponse.fecthedJobResults(jobResults)) - } - - func queueClient(_ sender: QueueClient, didDeleteJob jobId: JobId) { - responses.append(ServerResponse.deletedJob(jobId)) - } -} diff --git a/Tests/QueueClientTests/JobDeleterTests.swift b/Tests/QueueClientTests/JobDeleterTests.swift new file mode 100644 index 00000000..140aca6e --- /dev/null +++ b/Tests/QueueClientTests/JobDeleterTests.swift @@ -0,0 +1,37 @@ +import Foundation +import QueueClient +import QueueModels +import RESTMethods +import RequestSenderTestHelpers +import TestHelpers +import Types +import XCTest + +final class JobDeleterTests: XCTestCase { + lazy var requestSender = FakeRequestSender() + lazy var deleter = JobDeleterImpl(requestSender: requestSender) + + func test() { + requestSender.validateRequest = { sender in + guard let request = sender.request as? JobDeleteRequest else { + self.failTest("Unexpected request type") + } + XCTAssertEqual( + request.payload?.jobId, + JobId("jobId") + ) + } + requestSender.result = JobDeleteResponse(jobId: "jobId") + + let expectation = XCTestExpectation(description: "callback invoked") + + deleter.delete( + jobId: "jobId", + callbackQueue: .global() + ) { (response: Either<(), Error>) in + expectation.fulfill() + } + + wait(for: [expectation], timeout: 10) + } +} diff --git a/Tests/QueueClientTests/JobResultsFetcherTests.swift b/Tests/QueueClientTests/JobResultsFetcherTests.swift new file mode 100644 index 00000000..2dd348e1 --- /dev/null +++ b/Tests/QueueClientTests/JobResultsFetcherTests.swift @@ -0,0 +1,46 @@ +import Foundation +import QueueClient +import QueueModels +import RESTMethods +import RequestSenderTestHelpers +import TestHelpers +import Types +import XCTest + +final class JobResultsFetcherTests: XCTestCase { + lazy var requestSender = FakeRequestSender() + lazy var fetcher = JobResultsFetcherImpl(requestSender: requestSender) + + func test() { + let jobResults = JobResults( + jobId: "jobId", + testingResults: [] + ) + + requestSender.validateRequest = { sender in + guard let request = sender.request as? JobResultRequest else { + self.failTest("Unexpected request type") + } + XCTAssertEqual( + request.payload?.jobId, + JobId("jobId") + ) + } + requestSender.result = JobResultsResponse(jobResults: jobResults) + + let expectation = XCTestExpectation(description: "callback invoked") + + fetcher.fetch( + jobId: "jobId", + callbackQueue: .global() + ) { (response: Either) in + XCTAssertEqual( + try? response.dematerialize(), + jobResults + ) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 10) + } +} diff --git a/Tests/QueueClientTests/QueueClientTests.swift b/Tests/QueueClientTests/QueueClientTests.swift deleted file mode 100644 index 3ffbde4a..00000000 --- a/Tests/QueueClientTests/QueueClientTests.swift +++ /dev/null @@ -1,62 +0,0 @@ -import BuildArtifactsTestHelpers -import QueueClient -import QueueModels -import QueueModelsTestHelpers -import RESTInterfaces -import RESTMethods -import RequestSender -import RunnerModels -import RunnerTestHelpers -import SimulatorPoolTestHelpers -import SocketModels -import Swifter -import SynchronousWaiter -import XCTest - -class QueueClientTests: XCTestCase { - - private var server = HttpServer() - private var port: SocketModels.Port = 0 - private let delegate = FakeQueueClientDelegate() - private var queueClient: QueueClient! - private let workerId = WorkerId(value: "workerId") - private let payloadSignature = PayloadSignature(value: "expectedPayloadSignature") - - override func tearDown() { - server.stop() - queueClient.close() - } - - func prepareServer(_ query: String, _ response: @escaping (HttpRequest) -> (HttpResponse)) throws { - do { - server[query] = response - try server.start(0) - port = SocketModels.Port(value: try server.port()) - queueClient = QueueClient( - queueServerAddress: SocketAddress(host: "127.0.0.1", port: port), - requestSenderProvider: DefaultRequestSenderProvider() - ) - queueClient.delegate = delegate - } catch { - XCTFail("Failed to prepare server: \(error)") - throw error - } - } - - func test___deleting_job() throws { - let jobId: JobId = "job_id" - try prepareServer(RESTMethod.jobDelete.pathWithLeadingSlash) { request -> HttpResponse in - let data: Data = (try? JSONEncoder().encode(JobDeleteResponse(jobId: jobId))) ?? Data() - return .raw(200, "OK", ["Content-Type": "application/json"]) { try $0.write(data) } - } - try queueClient.deleteJob(jobId: jobId) - try SynchronousWaiter().waitWhile(timeout: 5.0, description: "wait for response") { delegate.responses.isEmpty } - - switch delegate.responses[0] { - case .deletedJob(let deletedJobId): - XCTAssertEqual(jobId, deletedJobId) - default: - XCTFail("Unexpected result") - } - } -} diff --git a/Tests/QueueServerTests/EndpointTests/JobDeleteEndpointTests.swift b/Tests/QueueServerTests/EndpointTests/JobDeleteEndpointTests.swift index a54a7862..e73e3023 100644 --- a/Tests/QueueServerTests/EndpointTests/JobDeleteEndpointTests.swift +++ b/Tests/QueueServerTests/EndpointTests/JobDeleteEndpointTests.swift @@ -29,7 +29,7 @@ final class JobDeleteEndpointTests: XCTestCase, JobManipulator { shouldThrow = false let endpoint = JobDeleteEndpoint(jobManipulator: self) - let response = try endpoint.handle(payload: JobDeleteRequest(jobId: jobId)) + let response = try endpoint.handle(payload: JobDeletePayload(jobId: jobId)) XCTAssertEqual(response.jobId, jobId) } @@ -37,7 +37,7 @@ final class JobDeleteEndpointTests: XCTestCase, JobManipulator { shouldThrow = true let endpoint = JobDeleteEndpoint(jobManipulator: self) - XCTAssertThrowsError(_ = try endpoint.handle(payload: JobDeleteRequest(jobId: jobId))) + XCTAssertThrowsError(_ = try endpoint.handle(payload: JobDeletePayload(jobId: jobId))) } } diff --git a/Tests/QueueServerTests/EndpointTests/JobResultsEndpointTests.swift b/Tests/QueueServerTests/EndpointTests/JobResultsEndpointTests.swift index c3764119..face7c09 100644 --- a/Tests/QueueServerTests/EndpointTests/JobResultsEndpointTests.swift +++ b/Tests/QueueServerTests/EndpointTests/JobResultsEndpointTests.swift @@ -37,13 +37,13 @@ final class JobResultsEndpointTests: XCTestCase, JobResultsProvider { func test___requesting_job_results_for_existing_job() throws { let endpoint = JobResultsEndpoint(jobResultsProvider: self) - let response = try endpoint.handle(payload: JobResultsRequest(jobId: jobId)) + let response = try endpoint.handle(payload: JobResultsPayload(jobId: jobId)) XCTAssertEqual(response.jobResults, jobResults) } func test___request_state_for_non_existing_job() { let endpoint = JobResultsEndpoint(jobResultsProvider: self) - XCTAssertThrowsError(try endpoint.handle(payload: JobResultsRequest(jobId: "invalid job id"))) + XCTAssertThrowsError(try endpoint.handle(payload: JobResultsPayload(jobId: "invalid job id"))) } } diff --git a/Tests/QueueServerTests/QueueServerTests.swift b/Tests/QueueServerTests/QueueServerTests.swift index 266a628f..f39e3535 100644 --- a/Tests/QueueServerTests/QueueServerTests.swift +++ b/Tests/QueueServerTests/QueueServerTests.swift @@ -211,10 +211,6 @@ final class QueueServerTests: XCTestCase { ) } - private func synchronousQueueClient(port: SocketModels.Port) -> SynchronousQueueClient { - return SynchronousQueueClient(queueServerAddress: queueServerAddress(port: port)) - } - private func queueServerAddress(port: SocketModels.Port) -> SocketAddress { return SocketAddress(host: "localhost", port: port) }