diff --git a/Tests/SwiftAwsLambdaTests/Lambda+CodeableTest.swift b/Tests/SwiftAwsLambdaTests/Lambda+CodeableTest.swift index ee56e030..04a2aec6 100644 --- a/Tests/SwiftAwsLambdaTests/Lambda+CodeableTest.swift +++ b/Tests/SwiftAwsLambdaTests/Lambda+CodeableTest.swift @@ -12,32 +12,45 @@ // //===----------------------------------------------------------------------===// +import NIO @testable import SwiftAwsLambda import XCTest class CodableLambdaTest: XCTestCase { func testSuccess() { - let server = MockLambdaServer(behavior: GoodBehavior()) + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } + struct Handler: LambdaCodableHandler { + func handle(context: Lambda.Context, payload: Request, callback: @escaping LambdaCodableCallback) { + callback(.success(Response(requestId: payload.requestId))) + } + } let maxTimes = Int.random(in: 1 ... 10) let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(handler: CodableEchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + let result = Lambda.run(handler: Handler(), configuration: configuration) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testFailure() { - let server = MockLambdaServer(behavior: BadBehavior()) + let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let result = Lambda.run(handler: CodableEchoHandler()) - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) + struct Handler: LambdaCodableHandler { + func handle(context: Lambda.Context, payload: Request, callback: @escaping LambdaCodableCallback) { + callback(.failure(TestError("boom"))) + } + } + let maxTimes = Int.random(in: 1 ... 10) + let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(handler: Handler(), configuration: configuration) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testClosureSuccess() { - let server = MockLambdaServer(behavior: GoodBehavior()) + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } @@ -46,40 +59,34 @@ class CodableLambdaTest: XCTestCase { let result = Lambda.run(configuration: configuration) { (_, payload: Request, callback) in callback(.success(Response(requestId: payload.requestId))) } - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testClosureFailure() { - let server = MockLambdaServer(behavior: BadBehavior()) + let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let result: LambdaLifecycleResult = Lambda.run { (_, payload: Request, callback) in - callback(.success(Response(requestId: payload.requestId))) - } - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) - } -} - -private func assertLambdaLifecycleResult(result: LambdaLifecycleResult, shoudHaveRun: Int = 0, shouldFailWithError: Error? = nil) { - switch result { - case .success(let count): - if shouldFailWithError != nil { - XCTFail("should fail with \(shouldFailWithError!)") - } - XCTAssertEqual(shoudHaveRun, count, "should have run \(shoudHaveRun) times") - case .failure(let error): - if shouldFailWithError == nil { - XCTFail("should succeed, but failed with \(error)") - break + let maxTimes = Int.random(in: 1 ... 10) + let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) + let result: Result = Lambda.run(configuration: configuration) { (_, _: Request, callback: (Result) -> Void) in + callback(.failure(TestError("boom"))) } - XCTAssertEqual(shouldFailWithError?.localizedDescription, error.localizedDescription, "expected error to mactch") + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } } // TODO: taking advantage of the fact we know the serialization is json -private struct GoodBehavior: LambdaServerBehavior { - let requestId = UUID().uuidString +private struct Behavior: LambdaServerBehavior { + let requestId: String + let payload: String + let result: Result + + init(requestId: String = UUID().uuidString, payload: String = "hello", result: Result = .success("hello")) { + self.requestId = requestId + self.payload = payload + self.result = result + } func getWork() -> GetWorkResult { guard let payload = try? JSONEncoder().encode(Request(requestId: requestId)) else { @@ -93,48 +100,48 @@ private struct GoodBehavior: LambdaServerBehavior { return .success((requestId: self.requestId, payload: payloadAsString)) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { - guard let data = response.data(using: .utf8) else { - XCTFail("decoding error") - return .failure(.internalServerError) - } - guard let response = try? JSONDecoder().decode(Response.self, from: data) else { - XCTFail("decoding error") + func processResponse(requestId: String, response: String?) -> Result { + switch self.result { + case .success(let expected) where expected != nil: + guard let data = response?.data(using: .utf8) else { + XCTFail("decoding error") + return .failure(.internalServerError) + } + guard let response = try? JSONDecoder().decode(Response.self, from: data) else { + XCTFail("decoding error") + return .failure(.internalServerError) + } + XCTAssertEqual(self.requestId, response.requestId, "expecting requestId to match") + return .success(()) + case .success(let expected) where expected == nil: + XCTAssertNil(response) + return .success(()) + case .failure: + XCTFail("unexpected to fail, but succeeded with: \(response ?? "undefined")") return .failure(.internalServerError) + default: + preconditionFailure("invalid state") } - XCTAssertEqual(self.requestId, response.requestId, "expecting requestId to match") - return .success } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - XCTFail("should not report error") - return .failure(.internalServerError) + func processError(requestId: String, error: ErrorResponse) -> Result { + XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") + switch self.result { + case .success: + XCTFail("unexpected to succeed, but failed with: \(error)") + return .failure(.internalServerError) + case .failure(let expected): + XCTAssertEqual(expected.description, error.errorMessage, "expecting error to match") + return .success(()) + } } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } } -private struct BadBehavior: LambdaServerBehavior { - func getWork() -> GetWorkResult { - return .failure(.internalServerError) - } - - func processResponse(requestId: String, response: String) -> ProcessResponseResult { - return .failure(.internalServerError) - } - - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - return .failure(.internalServerError) - } - - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { - return .failure(.internalServerError) - } -} - private struct Request: Codable { let requestId: String init(requestId: String) { @@ -148,9 +155,3 @@ private struct Response: Codable { self.requestId = requestId } } - -private struct CodableEchoHandler: LambdaCodableHandler { - func handle(context: Lambda.Context, payload: Request, callback: @escaping LambdaCodableCallback) { - callback(.success(Response(requestId: payload.requestId))) - } -} diff --git a/Tests/SwiftAwsLambdaTests/Lambda+StringTest.swift b/Tests/SwiftAwsLambdaTests/Lambda+StringTest.swift index 20e4f566..3a17d425 100644 --- a/Tests/SwiftAwsLambdaTests/Lambda+StringTest.swift +++ b/Tests/SwiftAwsLambdaTests/Lambda+StringTest.swift @@ -12,32 +12,45 @@ // //===----------------------------------------------------------------------===// +import NIO @testable import SwiftAwsLambda import XCTest class StringLambdaTest: XCTestCase { func testSuccess() { - let server = MockLambdaServer(behavior: GoodBehavior()) + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } + struct Handler: LambdaStringHandler { + func handle(context: Lambda.Context, payload: String, callback: @escaping LambdaStringCallback) { + callback(.success(payload)) + } + } let maxTimes = Int.random(in: 1 ... 10) let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(handler: StringEchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + let result = Lambda.run(handler: Handler(), configuration: configuration) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testFailure() { - let server = MockLambdaServer(behavior: BadBehavior()) + let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let result = Lambda.run(handler: StringEchoHandler()) - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) + struct Handler: LambdaStringHandler { + func handle(context: Lambda.Context, payload: String, callback: @escaping LambdaStringCallback) { + callback(.failure(TestError("boom"))) + } + } + let maxTimes = Int.random(in: 1 ... 10) + let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(handler: Handler(), configuration: configuration) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testClosureSuccess() { - let server = MockLambdaServer(behavior: GoodBehavior()) + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } @@ -46,81 +59,64 @@ class StringLambdaTest: XCTestCase { let result = Lambda.run(configuration: configuration) { (_, payload: String, callback) in callback(.success(payload)) } - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testClosureFailure() { - let server = MockLambdaServer(behavior: BadBehavior()) + let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let result: LambdaLifecycleResult = Lambda.run { (_, payload: String, callback) in - callback(.success(payload)) + let maxTimes = Int.random(in: 1 ... 10) + let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) + let result: Result = Lambda.run(configuration: configuration) { (_, _: String, callback) in + callback(.failure(TestError("boom"))) } - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } } -private func assertLambdaLifecycleResult(result: LambdaLifecycleResult, shoudHaveRun: Int = 0, shouldFailWithError: Error? = nil) { - switch result { - case .success(let count): - if shouldFailWithError != nil { - XCTFail("should fail with \(shouldFailWithError!)") - } - XCTAssertEqual(shoudHaveRun, count, "should have run \(shoudHaveRun) times") - case .failure(let error): - if shouldFailWithError == nil { - XCTFail("should succeed, but failed with \(error)") - break - } - XCTAssertEqual(shouldFailWithError?.localizedDescription, error.localizedDescription, "expected error to mactch") +private struct Behavior: LambdaServerBehavior { + let requestId: String + let payload: String + let result: Result + + init(requestId: String = UUID().uuidString, payload: String = "hello", result: Result = .success("hello")) { + self.requestId = requestId + self.payload = payload + self.result = result } -} -private struct GoodBehavior: LambdaServerBehavior { - let requestId = UUID().uuidString - let payload = "hello" func getWork() -> GetWorkResult { return .success((requestId: self.requestId, payload: self.payload)) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") - XCTAssertEqual(self.payload, response, "expecting response to match") - return .success + switch self.result { + case .success(let expected): + XCTAssertEqual(expected, response, "expecting response to match") + return .success(()) + case .failure: + XCTFail("unexpected to fail, but succeeded with: \(response ?? "undefined")") + return .failure(.internalServerError) + } } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - XCTFail("should not report error") - return .failure(.internalServerError) + func processError(requestId: String, error: ErrorResponse) -> Result { + XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") + switch self.result { + case .success: + XCTFail("unexpected to succeed, but failed with: \(error)") + return .failure(.internalServerError) + case .failure(let expected): + XCTAssertEqual(expected.description, error.errorMessage, "expecting error to match") + return .success(()) + } } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } } - -private struct BadBehavior: LambdaServerBehavior { - func getWork() -> GetWorkResult { - return .failure(.internalServerError) - } - - func processResponse(requestId: String, response: String) -> ProcessResponseResult { - return .failure(.internalServerError) - } - - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - return .failure(.internalServerError) - } - - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { - return .failure(.internalServerError) - } -} - -private struct StringEchoHandler: LambdaStringHandler { - func handle(context: Lambda.Context, payload: String, callback: @escaping LambdaStringCallback) { - callback(.success(payload)) - } -} diff --git a/Tests/SwiftAwsLambdaTests/LambdaRunnerTest.swift b/Tests/SwiftAwsLambdaTests/LambdaRunnerTest.swift index 583ce27b..f74f5f21 100644 --- a/Tests/SwiftAwsLambdaTests/LambdaRunnerTest.swift +++ b/Tests/SwiftAwsLambdaTests/LambdaRunnerTest.swift @@ -12,13 +12,11 @@ // //===----------------------------------------------------------------------===// -import Logging -import NIO @testable import SwiftAwsLambda import XCTest class LambdaRunnerTest: XCTestCase { - func testSuccess() throws { + func testSuccess() { struct Behavior: LambdaServerBehavior { let requestId = UUID().uuidString let payload = "hello" @@ -26,18 +24,18 @@ class LambdaRunnerTest: XCTestCase { return .success((self.requestId, self.payload)) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") XCTAssertEqual(self.payload, response, "expecting response to match") - return .success + return .success(()) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTFail("should not report error") return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } @@ -45,7 +43,7 @@ class LambdaRunnerTest: XCTestCase { XCTAssertNoThrow(try runLambda(behavior: Behavior(), handler: EchoHandler())) } - func testFailure() throws { + func testFailure() { struct Behavior: LambdaServerBehavior { static let error = "boom" let requestId = UUID().uuidString @@ -53,18 +51,18 @@ class LambdaRunnerTest: XCTestCase { return .success((requestId: self.requestId, payload: "hello")) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTFail("should report error") return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") XCTAssertEqual(Behavior.error, error.errorMessage, "expecting error to match") return .success(()) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } diff --git a/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest+XCTest.swift b/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest+XCTest.swift index d683d8de..1cde4609 100644 --- a/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest+XCTest.swift +++ b/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest+XCTest.swift @@ -25,12 +25,15 @@ import XCTest extension LambdaRuntimeClientTest { static var allTests: [(String, (LambdaRuntimeClientTest) -> () throws -> Void)] { return [ + ("testSuccess", testSuccess), + ("testFailure", testFailure), + ("testBootstrapFailure", testBootstrapFailure), ("testGetWorkServerInternalError", testGetWorkServerInternalError), ("testGetWorkServerNoBodyError", testGetWorkServerNoBodyError), ("testGetWorkServerMissingHeaderRequestIDError", testGetWorkServerMissingHeaderRequestIDError), ("testProcessResponseInternalServerError", testProcessResponseInternalServerError), ("testProcessErrorInternalServerError", testProcessErrorInternalServerError), - ("testProcessInitErrorInternalServerError", testProcessInitErrorInternalServerError), + ("testProcessInitErrorOnBootstrapFailure", testProcessInitErrorOnBootstrapFailure), ] } } diff --git a/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest.swift b/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest.swift index b8c33d0f..20315724 100644 --- a/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest.swift +++ b/Tests/SwiftAwsLambdaTests/LambdaRuntimeClientTest.swift @@ -16,23 +16,43 @@ import XCTest class LambdaRuntimeClientTest: XCTestCase { - func testGetWorkServerInternalError() throws { + func testSuccess() { + let behavior = Behavior() + XCTAssertNoThrow(try runLambda(behavior: behavior, handler: EchoHandler())) + XCTAssertEqual(behavior.state, 6) + } + + func testFailure() { + let behavior = Behavior() + XCTAssertNoThrow(try runLambda(behavior: behavior, handler: FailedHandler("boom"))) + XCTAssertEqual(behavior.state, 10) + } + + func testBootstrapFailure() { + let behavior = Behavior() + XCTAssertThrowsError(try runLambda(behavior: behavior, handler: FailedInitializerHandler("boom"))) { error in + XCTAssertEqual(error as? TestError, TestError("boom")) + } + XCTAssertEqual(behavior.state, 1) + } + + func testGetWorkServerInternalError() { struct Behavior: LambdaServerBehavior { func getWork() -> GetWorkResult { return .failure(.internalServerError) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTFail("should not report results") return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTFail("should not report error") return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } @@ -42,23 +62,23 @@ class LambdaRuntimeClientTest: XCTestCase { } } - func testGetWorkServerNoBodyError() throws { + func testGetWorkServerNoBodyError() { struct Behavior: LambdaServerBehavior { func getWork() -> GetWorkResult { return .success(("1", "")) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTFail("should not report results") return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTFail("should not report error") return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } @@ -68,24 +88,24 @@ class LambdaRuntimeClientTest: XCTestCase { } } - func testGetWorkServerMissingHeaderRequestIDError() throws { + func testGetWorkServerMissingHeaderRequestIDError() { struct Behavior: LambdaServerBehavior { func getWork() -> GetWorkResult { // no request id -> no context return .success(("", "hello")) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTFail("should not report results") return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTFail("should not report error") return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } @@ -95,22 +115,22 @@ class LambdaRuntimeClientTest: XCTestCase { } } - func testProcessResponseInternalServerError() throws { + func testProcessResponseInternalServerError() { struct Behavior: LambdaServerBehavior { func getWork() -> GetWorkResult { return .success((requestId: "1", payload: "payload")) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTFail("should not report error") return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } @@ -120,22 +140,22 @@ class LambdaRuntimeClientTest: XCTestCase { } } - func testProcessErrorInternalServerError() throws { + func testProcessErrorInternalServerError() { struct Behavior: LambdaServerBehavior { func getWork() -> GetWorkResult { return .success((requestId: "1", payload: "payload")) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTFail("should not report results") return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } @@ -145,29 +165,53 @@ class LambdaRuntimeClientTest: XCTestCase { } } - func testProcessInitErrorInternalServerError() throws { + func testProcessInitErrorOnBootstrapFailure() { struct Behavior: LambdaServerBehavior { func getWork() -> GetWorkResult { XCTFail("should not get work") return .failure(.internalServerError) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTFail("should not report results") return .failure(.internalServerError) } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { + func processError(requestId: String, error: ErrorResponse) -> Result { XCTFail("should not report error") return .failure(.internalServerError) } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { return .failure(.internalServerError) } } XCTAssertThrowsError(try runLambda(behavior: Behavior(), handler: FailedInitializerHandler("boom"))) { error in - XCTAssertEqual(error as? FailedInitializerHandler.Error, FailedInitializerHandler.Error(description: "boom")) + XCTAssertEqual(error as? TestError, TestError("boom")) + } + } + + class Behavior: LambdaServerBehavior { + var state = 0 + + func processInitError(error: ErrorResponse) -> Result { + self.state += 1 + return .success(()) + } + + func getWork() -> GetWorkResult { + self.state += 2 + return .success(("1", "hello")) + } + + func processResponse(requestId: String, response: String?) -> Result { + self.state += 4 + return .success(()) + } + + func processError(requestId: String, error: ErrorResponse) -> Result { + self.state += 8 + return .success(()) } } } diff --git a/Tests/SwiftAwsLambdaTests/LambdaTest+XCTest.swift b/Tests/SwiftAwsLambdaTests/LambdaTest+XCTest.swift index 98402082..f65ce1fc 100644 --- a/Tests/SwiftAwsLambdaTests/LambdaTest+XCTest.swift +++ b/Tests/SwiftAwsLambdaTests/LambdaTest+XCTest.swift @@ -27,10 +27,9 @@ extension LambdaTest { return [ ("testSuccess", testSuccess), ("testFailure", testFailure), - ("testInitFailure", testInitFailure), - ("testInitFailureAndReportErrorFailure", testInitFailureAndReportErrorFailure), - ("testClosureSuccess", testClosureSuccess), - ("testClosureFailure", testClosureFailure), + ("testServerFailure", testServerFailure), + ("testBootstrapFailure", testBootstrapFailure), + ("testBootstrapFailureAndReportErrorFailure", testBootstrapFailureAndReportErrorFailure), ("testStartStop", testStartStop), ("testTimeout", testTimeout), ("testDisconnect", testDisconnect), diff --git a/Tests/SwiftAwsLambdaTests/LambdaTest.swift b/Tests/SwiftAwsLambdaTests/LambdaTest.swift index 7ca0ab62..48e1380c 100644 --- a/Tests/SwiftAwsLambdaTests/LambdaTest.swift +++ b/Tests/SwiftAwsLambdaTests/LambdaTest.swift @@ -18,7 +18,7 @@ import XCTest class LambdaTest: XCTestCase { func testSuccess() { - let server = MockLambdaServer(behavior: GoodBehavior()) + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } @@ -26,246 +26,235 @@ class LambdaTest: XCTestCase { let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) let handler = EchoHandler() let result = Lambda.run(handler: handler, configuration: configuration) - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) XCTAssertEqual(handler.initializeCalls, 1) } func testFailure() { - let server = MockLambdaServer(behavior: BadBehavior()) + let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let result = Lambda.run(handler: EchoHandler()) - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) + let maxTimes = Int.random(in: 10 ... 20) + let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(handler: FailedHandler("boom"), configuration: configuration) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } - func testInitFailure() { - let server = MockLambdaServer(behavior: GoodBehaviourWhenInitFails()) + func testServerFailure() { + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let handler = FailedInitializerHandler("kaboom") - let result = Lambda.run(handler: handler) - assertLambdaLifecycleResult(result: result, shouldFailWithError: FailedInitializerHandler.Error(description: "kaboom")) - } + struct Behavior: LambdaServerBehavior { + func getWork() -> GetWorkResult { + return .failure(.internalServerError) + } - func testInitFailureAndReportErrorFailure() { - let server = MockLambdaServer(behavior: BadBehaviourWhenInitFails()) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } + func processResponse(requestId: String, response: String?) -> Result { + return .failure(.internalServerError) + } + + func processError(requestId: String, error: ErrorResponse) -> Result { + return .failure(.internalServerError) + } - let handler = FailedInitializerHandler("kaboom") - let result = Lambda.run(handler: handler) - assertLambdaLifecycleResult(result: result, shouldFailWithError: FailedInitializerHandler.Error(description: "kaboom")) + func processInitError(error: ErrorResponse) -> Result { + XCTFail("should not report init error") + return .failure(.internalServerError) + } + } + + let result = Lambda.run(handler: EchoHandler()) + assertLambdaLifecycleResult(result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) } - func testClosureSuccess() { - let server = MockLambdaServer(behavior: GoodBehavior()) + func testBootstrapFailure() { + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let maxTimes = Int.random(in: 10 ... 20) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration) { (_, payload: [UInt8], callback: LambdaCallback) in - callback(.success(payload)) + struct Behavior: LambdaServerBehavior { + func getWork() -> GetWorkResult { + XCTFail("should not get work") + return .failure(.internalServerError) + } + + func processResponse(requestId: String, response: String?) -> Result { + XCTFail("should not report a response") + return .failure(.internalServerError) + } + + func processError(requestId: String, error: ErrorResponse) -> Result { + XCTFail("should not report an error") + return .failure(.internalServerError) + } + + func processInitError(error: ErrorResponse) -> Result { + return .success(()) + } } - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + + let result = Lambda.run(handler: FailedInitializerHandler("kaboom")) + assertLambdaLifecycleResult(result, shouldFailWithError: TestError("kaboom")) } - func testClosureFailure() { - let server = MockLambdaServer(behavior: BadBehavior()) + func testBootstrapFailureAndReportErrorFailure() { + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - let result: LambdaLifecycleResult = Lambda.run { (_, payload: [UInt8], callback: LambdaCallback) in - callback(.success(payload)) + struct Behavior: LambdaServerBehavior { + func getWork() -> GetWorkResult { + XCTFail("should not get work") + return .failure(.internalServerError) + } + + func processResponse(requestId: String, response: String?) -> Result { + XCTFail("should not report a response") + return .failure(.internalServerError) + } + + func processError(requestId: String, error: ErrorResponse) -> Result { + XCTFail("should not report an error") + return .failure(.internalServerError) + } + + func processInitError(error: ErrorResponse) -> Result { + return .failure(.internalServerError) + } } - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.badStatusCode(.internalServerError)) + + let result = Lambda.run(handler: FailedInitializerHandler("kaboom")) + assertLambdaLifecycleResult(result, shouldFailWithError: TestError("kaboom")) } - func testStartStop() throws { - let server = try MockLambdaServer(behavior: GoodBehavior()).start().wait() - struct MyHandler: LambdaHandler { + func testStartStop() { + let server = MockLambdaServer(behavior: Behavior()) + XCTAssertNoThrow(try server.start().wait()) + defer { XCTAssertNoThrow(try server.stop().wait()) } + + struct Handler: LambdaHandler { func handle(context: Lambda.Context, payload: [UInt8], callback: @escaping LambdaCallback) { callback(.success(payload)) } } + let signal = Signal.ALRM let maxTimes = 1000 let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes, stopSignal: signal)) let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - let future = Lambda.runAsync(eventLoopGroup: eventLoopGroup, handler: MyHandler(), configuration: configuration) + defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } + + let future = Lambda.runAsync(eventLoopGroup: eventLoopGroup, handler: Handler(), configuration: configuration) DispatchQueue(label: "test").async { usleep(100_000) kill(getpid(), signal.rawValue) } - let result = try future.wait() - XCTAssertGreaterThan(result, 0, "should have stopped before any request made") - XCTAssertLessThan(result, maxTimes, "should have stopped before \(maxTimes)") - try server.stop().wait() - try eventLoopGroup.syncShutdownGracefully() + future.whenSuccess { result in + XCTAssertGreaterThan(result, 0, "should have stopped before any request made") + XCTAssertLessThan(result, maxTimes, "should have stopped before \(maxTimes)") + } + XCTAssertNoThrow(try future.wait()) } func testTimeout() { let timeout: Int64 = 100 - let server = MockLambdaServer(behavior: GoodBehavior(requestId: "timeout", payload: "\(timeout * 2)")) + let server = MockLambdaServer(behavior: Behavior(requestId: "timeout", payload: "\(timeout * 2)")) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: 1), runtimeEngine: .init(requestTimeout: .milliseconds(timeout))) let result = Lambda.run(handler: EchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.upstreamError("timeout")) + assertLambdaLifecycleResult(result, shouldFailWithError: LambdaRuntimeClientError.upstreamError("timeout")) } func testDisconnect() { - let server = MockLambdaServer(behavior: GoodBehavior(requestId: "disconnect")) + let server = MockLambdaServer(behavior: Behavior(requestId: "disconnect")) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: 1)) let result = Lambda.run(handler: EchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shouldFailWithError: LambdaRuntimeClientError.upstreamError("connectionResetByPeer")) + assertLambdaLifecycleResult(result, shouldFailWithError: LambdaRuntimeClientError.upstreamError("connectionResetByPeer")) } func testBigPayload() { let payload = String(repeating: "*", count: 104_448) - let server = MockLambdaServer(behavior: GoodBehavior(payload: payload)) + let server = MockLambdaServer(behavior: Behavior(payload: payload, result: .success(payload))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: 1)) let result = Lambda.run(handler: EchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shoudHaveRun: 1) + assertLambdaLifecycleResult(result, shoudHaveRun: 1) } func testKeepAliveServer() { - let server = MockLambdaServer(behavior: GoodBehavior(), keepAlive: true) + let server = MockLambdaServer(behavior: Behavior(), keepAlive: true) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } let maxTimes = 10 let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(handler: EchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } func testNoKeepAliveServer() { - let server = MockLambdaServer(behavior: GoodBehavior(), keepAlive: false) + let server = MockLambdaServer(behavior: Behavior(), keepAlive: false) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } let maxTimes = 10 let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(handler: EchoHandler(), configuration: configuration) - assertLambdaLifecycleResult(result: result, shoudHaveRun: maxTimes) + assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) } } -private func assertLambdaLifecycleResult(result: LambdaLifecycleResult, shoudHaveRun: Int = 0, shouldFailWithError: Error? = nil) { - switch result { - case .success(let count): - if shouldFailWithError != nil { - XCTFail("should fail with \(shouldFailWithError!)") - break - } - XCTAssertEqual(shoudHaveRun, count, "should have run \(shoudHaveRun) times") - case .failure(let error): - if shouldFailWithError == nil { - XCTFail("should succeed, but failed with \(error)") - break - } - XCTAssertEqual(shouldFailWithError?.localizedDescription, error.localizedDescription, "expected error to mactch") - } -} - -private struct GoodBehavior: LambdaServerBehavior { +private struct Behavior: LambdaServerBehavior { let requestId: String let payload: String + let result: Result - init(requestId: String = UUID().uuidString, payload: String = UUID().uuidString) { + init(requestId: String = UUID().uuidString, payload: String = "hello", result: Result = .success("hello")) { self.requestId = requestId self.payload = payload + self.result = result } func getWork() -> GetWorkResult { return .success((requestId: self.requestId, payload: self.payload)) } - func processResponse(requestId: String, response: String) -> ProcessResponseResult { + func processResponse(requestId: String, response: String?) -> Result { XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") - XCTAssertEqual(self.payload, response, "expecting response to match") - return .success - } - - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - XCTFail("should not report error") - return .failure(.internalServerError) - } - - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { - XCTFail("should not report init error") - return .failure(.internalServerError) - } -} - -private struct BadBehavior: LambdaServerBehavior { - func getWork() -> GetWorkResult { - return .failure(.internalServerError) - } - - func processResponse(requestId: String, response: String) -> ProcessResponseResult { - return .failure(.internalServerError) + switch self.result { + case .success(let expected): + XCTAssertEqual(expected, response, "expecting response to match") + return .success(()) + case .failure: + XCTFail("unexpected to fail, but succeeded with: \(response ?? "undefined")") + return .failure(.internalServerError) + } } - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - return .failure(.internalServerError) + func processError(requestId: String, error: ErrorResponse) -> Result { + XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") + switch self.result { + case .success: + XCTFail("unexpected to succeed, but failed with: \(error)") + return .failure(.internalServerError) + case .failure(let expected): + XCTAssertEqual(expected.description, error.errorMessage, "expecting error to match") + return .success(()) + } } - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { + func processInitError(error: ErrorResponse) -> Result { XCTFail("should not report init error") return .failure(.internalServerError) } } - -private struct GoodBehaviourWhenInitFails: LambdaServerBehavior { - func getWork() -> GetWorkResult { - XCTFail("should not get work") - return .failure(.internalServerError) - } - - func processResponse(requestId: String, response: String) -> ProcessResponseResult { - XCTFail("should not report a response") - return .failure(.internalServerError) - } - - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - XCTFail("should not report an error") - return .failure(.internalServerError) - } - - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { - return .success(()) - } -} - -private struct BadBehaviourWhenInitFails: LambdaServerBehavior { - func getWork() -> GetWorkResult { - XCTFail("should not get work") - return .failure(.internalServerError) - } - - func processResponse(requestId: String, response: String) -> ProcessResponseResult { - XCTFail("should not report a response") - return .failure(.internalServerError) - } - - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult { - XCTFail("should not report an error") - return .failure(.internalServerError) - } - - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult { - return .failure(.internalServerError) - } -} diff --git a/Tests/SwiftAwsLambdaTests/MockLambdaServer.swift b/Tests/SwiftAwsLambdaTests/MockLambdaServer.swift index a25c559f..a1c721b7 100644 --- a/Tests/SwiftAwsLambdaTests/MockLambdaServer.swift +++ b/Tests/SwiftAwsLambdaTests/MockLambdaServer.swift @@ -150,10 +150,10 @@ internal final class HTTPHandler: ChannelInboundHandler { responseStatus = .init(statusCode: error.rawValue) } } else if request.head.uri.hasSuffix(Consts.postResponseURLSuffix) { - guard let requestId = request.head.uri.split(separator: "/").dropFirst(3).first, let response = requestBody else { + guard let requestId = request.head.uri.split(separator: "/").dropFirst(3).first else { return self.writeResponse(context: context, status: .badRequest) } - switch self.behavior.processResponse(requestId: String(requestId), response: response) { + switch self.behavior.processResponse(requestId: String(requestId), response: requestBody) { case .success: responseStatus = .accepted case .failure(let error): @@ -175,7 +175,6 @@ internal final class HTTPHandler: ChannelInboundHandler { } else { responseStatus = .notFound } - self.logger.info("\(self) responding to \(request.head.uri)") self.writeResponse(context: context, status: responseStatus, headers: responseHeaders, body: responseBody) } @@ -212,9 +211,9 @@ internal final class HTTPHandler: ChannelInboundHandler { internal protocol LambdaServerBehavior { func getWork() -> GetWorkResult - func processResponse(requestId: String, response: String) -> ProcessResponseResult - func processError(requestId: String, error: ErrorResponse) -> ProcessErrorResult - func processInitError(error: ErrorResponse) -> ProcessInitErrorResult + func processResponse(requestId: String, response: String?) -> Result + func processError(requestId: String, error: ErrorResponse) -> Result + func processInitError(error: ErrorResponse) -> Result } internal typealias GetWorkResult = Result<(String, String), GetWorkError> @@ -225,28 +224,19 @@ internal enum GetWorkError: Int, Error { case internalServerError = 500 } -internal enum ProcessResponseResult { - case success - case failure(ProcessResponseError) -} - -internal enum ProcessResponseError: Int { +internal enum ProcessResponseError: Int, Error { case badRequest = 400 case payloadTooLarge = 413 case tooManyRequests = 429 case internalServerError = 500 } -internal typealias ProcessErrorResult = Result - -internal enum ProcessError: Int, Error { +internal enum ProcessErrorError: Int, Error { case invalidErrorShape = 299 case badRequest = 400 case internalServerError = 500 } -internal typealias ProcessInitErrorResult = Result - internal enum ServerError: Error { case notReady case cantBind diff --git a/Tests/SwiftAwsLambdaTests/Utils.swift b/Tests/SwiftAwsLambdaTests/Utils.swift index 8de1aa30..1363625f 100644 --- a/Tests/SwiftAwsLambdaTests/Utils.swift +++ b/Tests/SwiftAwsLambdaTests/Utils.swift @@ -25,7 +25,7 @@ func runLambda(behavior: LambdaServerBehavior, handler: LambdaHandler) throws { let runner = LambdaRunner(eventLoop: eventLoopGroup.next(), configuration: configuration, lambdaHandler: handler) let server = try MockLambdaServer(behavior: behavior).start().wait() defer { XCTAssertNoThrow(try server.stop().wait()) } - try runner.initialize(logger: logger).flatMap { + try runner.initialize(logger: logger).flatMap { _ in runner.run(logger: logger) }.wait() } @@ -33,7 +33,7 @@ func runLambda(behavior: LambdaServerBehavior, handler: LambdaHandler) throws { final class EchoHandler: LambdaHandler { var initializeCalls = 0 - func initialize(callback: @escaping LambdaInitCallBack) { + public func initialize(callback: @escaping LambdaInitCallBack) { self.initializeCalls += 1 callback(.success(())) } @@ -51,11 +51,7 @@ struct FailedHandler: LambdaHandler { } func handle(context: Lambda.Context, payload: [UInt8], callback: @escaping LambdaCallback) { - callback(.failure(Error(description: self.reason))) - } - - struct Error: Swift.Error, Equatable, CustomStringConvertible { - let description: String + callback(.failure(TestError(self.reason))) } } @@ -67,14 +63,33 @@ struct FailedInitializerHandler: LambdaHandler { } func handle(context: Lambda.Context, payload: [UInt8], callback: @escaping LambdaCallback) { - callback(.success(payload)) + callback(.failure(TestError("should not be called"))) } func initialize(callback: @escaping LambdaInitCallBack) { - callback(.failure(Error(description: self.reason))) + callback(.failure(TestError(self.reason))) } +} + +func assertLambdaLifecycleResult(_ result: Result, shoudHaveRun: Int = 0, shouldFailWithError: Error? = nil, file: StaticString = #file, line: UInt = #line) { + switch result { + case .success where shouldFailWithError != nil: + XCTFail("should fail with \(shouldFailWithError!)", file: file, line: line) + case .success(let count) where shouldFailWithError == nil: + XCTAssertEqual(shoudHaveRun, count, "should have run \(shoudHaveRun) times", file: file, line: line) + case .failure(let error) where shouldFailWithError == nil: + XCTFail("should succeed, but failed with \(error)", file: file, line: line) + case .failure(let error) where shouldFailWithError != nil: + XCTAssertEqual(String(describing: shouldFailWithError!), String(describing: error), "expected error to mactch", file: file, line: line) + default: + XCTFail("invalid state") + } +} + +struct TestError: Error, Equatable, CustomStringConvertible { + let description: String - public struct Error: Swift.Error, Equatable, CustomStringConvertible { - let description: String + init(_ description: String) { + self.description = description } }