diff --git a/Package.swift b/Package.swift index 70447daf1..38fb8869c 100644 --- a/Package.swift +++ b/Package.swift @@ -130,7 +130,7 @@ let package = Package( ), .target( name: "SmithyTestUtil", - dependencies: ["ClientRuntime"] + dependencies: ["ClientRuntime", "SmithyHTTPAPI"] ), .target( name: "SmithyIdentity", diff --git a/Sources/ClientRuntime/Endpoints/StaticEndpointResolver.swift b/Sources/ClientRuntime/Endpoints/StaticEndpointResolver.swift new file mode 100644 index 000000000..9b31d251f --- /dev/null +++ b/Sources/ClientRuntime/Endpoints/StaticEndpointResolver.swift @@ -0,0 +1,21 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct SmithyHTTPAPI.Endpoint + +public struct StaticEndpointResolver { + + private let endpoint: SmithyHTTPAPI.Endpoint + + public init(endpoint: SmithyHTTPAPI.Endpoint) { + self.endpoint = endpoint + } + + public func resolve(params: Params) throws -> SmithyHTTPAPI.Endpoint { + return endpoint + } +} diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/SignerMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/SignerMiddleware.swift index d9f71ba87..7bf64736a 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/SignerMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/SignerMiddleware.swift @@ -10,6 +10,7 @@ import enum Smithy.ClientError import Foundation import SmithyHTTPAPI import SmithyHTTPAuthAPI +import struct Smithy.AttributeKey public struct SignerMiddleware: Middleware { public let id: String = "SignerMiddleware" @@ -69,10 +70,17 @@ extension SignerMiddleware: ApplySigner { ) } + // Check if CRT should be provided a pre-computed Sha256 SignedBodyValue + var updatedSigningProperties = signingProperties + let sha256: String? = attributes.get(key: AttributeKey(name: "X-Amz-Content-Sha256")) + if let bodyValue = sha256 { + updatedSigningProperties.set(key: AttributeKey(name: "SignedBodyValue"), value: bodyValue) + } + let signed = try await signer.signRequest( requestBuilder: request.toBuilder(), identity: identity, - signingProperties: signingProperties + signingProperties: updatedSigningProperties ) // The saved signature is used to sign event stream messages if needed. diff --git a/Sources/SmithyHTTPAuth/CRTAdapters.swift b/Sources/SmithyHTTPAuth/CRTAdapters.swift index 09847c0e5..e3f395075 100644 --- a/Sources/SmithyHTTPAuth/CRTAdapters.swift +++ b/Sources/SmithyHTTPAuth/CRTAdapters.swift @@ -61,6 +61,7 @@ extension AWSSignedBodyValue { case .streamingSha256Events: return .streamingSha256Events case .streamingSha256PayloadTrailer: return .streamingSha256PayloadTrailer case .streamingUnsignedPayloadTrailer: return .streamingUnSignedPayloadTrailer + case .precomputed(let value): return .precomputedSha256(value) } } } diff --git a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift index 74c070ba5..d4110fca8 100644 --- a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift +++ b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift @@ -13,4 +13,5 @@ public enum AWSSignedBodyValue { case streamingSha256Events case streamingSha256PayloadTrailer case streamingUnsignedPayloadTrailer + case precomputed(String) } diff --git a/Sources/SmithyTestUtil/ProtocolTestClient.swift b/Sources/SmithyTestUtil/ProtocolTestClient.swift new file mode 100644 index 000000000..3a42f9e49 --- /dev/null +++ b/Sources/SmithyTestUtil/ProtocolTestClient.swift @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +import protocol SmithyHTTPAPI.HTTPClient +import class SmithyHTTPAPI.SdkHttpRequest +import class SmithyHTTPAPI.HttpResponse +import ClientRuntime + +public class ProtocolTestClient { + public init() {} +} + +public enum TestCheckError: Error { + case actual(SdkHttpRequest) +} + +extension ProtocolTestClient: HTTPClient { + public func send(request: SdkHttpRequest) async throws -> HttpResponse { + throw TestCheckError.actual(request) + } +} + +public class ProtocolTestIdempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator { + public init() {} + + public func generateToken() -> String { + return "00000000-0000-4000-8000-000000000000" + } +} diff --git a/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift b/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift index 03bb22c33..a027ad2e8 100644 --- a/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift +++ b/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift @@ -228,6 +228,32 @@ open class HttpRequestTestBase: XCTestCase { try await assertEqualHttpBody?(expected.body, actual.body) } + private func checkStreamLengthEqualityIfUnseekableActualStreamHasBeenRead( + expected: ByteStream, + actual: ByteStream, + file: StaticString = #filePath, + line: UInt = #line + ) -> Bool { + switch actual { + case .stream(let actualStream): + if actualStream.position == actualStream.length, !actualStream.isSeekable { + switch expected { + case .stream(let expectedStream): + if actualStream.length == expectedStream.length { + return true // Streams are considered equal + } else { + XCTFail("Actual stream is a different size than expected!", file: file, line: line) + } + default: + break // This is only applicable to streams + } + } + default: + break // This is only applicable to streams + } + return false + } + public func genericAssertEqualHttpBodyData( expected: ByteStream, actual: ByteStream, @@ -237,6 +263,17 @@ open class HttpRequestTestBase: XCTestCase { ) async throws { let expectedData = try await expected.readData() let actualData = try await actual.readData() + + // Unseekable streams may have already been read by Signer middleware and cannot be read again + // Compare stream lengths if ByteStream is a .stream and actualData is nil + if checkStreamLengthEqualityIfUnseekableActualStreamHasBeenRead( + expected: expected, actual: actual, file: file, line: line + ) { + // Stream lengths were checked and comparing data will result in failure due to above conditions + return + } + + // Compare the data compareData(contentType: contentType, expectedData, actualData, file: file, line: line) } @@ -304,10 +341,17 @@ open class HttpRequestTestBase: XCTestCase { return } - let actualValue = actual.values(for: header.name)?.joined(separator: ", ") + let actualValue = actual.values(for: header.name)? + .joined(separator: ", ") + .components(separatedBy: .whitespaces) + .joined() XCTAssertNotNil(actualValue, file: file, line: line) - let expectedValue = header.value.joined(separator: ", ") + let expectedValue = header.value + .joined(separator: ", ") + .components(separatedBy: .whitespaces) + .joined() + XCTAssertEqual(actualValue, expectedValue, file: file, line: line) } } @@ -371,7 +415,7 @@ open class HttpRequestTestBase: XCTestCase { } for expectedQueryItem in expectedQueryItems { - let values = actualQueryItems.filter {$0.name == expectedQueryItem.name}.map { $0.value} + let values = actualQueryItems.filter {$0.name == expectedQueryItem.name}.map { $0.value } XCTAssertNotNil( values, "expected query parameter \(expectedQueryItem.name); no values found", diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt index 57ad31a4a..1ea692a1d 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt @@ -22,7 +22,7 @@ class SwiftDependency( val CRT = SwiftDependency( "AwsCommonRuntimeKit", null, - "0.30.0", + "0.31.0", "https://github.com/awslabs/aws-crt-swift", "", "aws-crt-swift", diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointResolverGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointResolverGenerator.kt index f31655e0c..19b6a5f35 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointResolverGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointResolverGenerator.kt @@ -29,6 +29,7 @@ class EndpointResolverGenerator( renderResolverProtocol(it) it.write("") renderResolver(it, ruleSet) + renderStaticResolver(it) val inputSymbol = Symbol.builder().name("SdkHttpRequestBuilder").build() val outputSymbol = Symbol.builder().name("OperationStackOutput").build() val outputErrorSymbol = Symbol.builder().name("OperationStackError").build() @@ -62,4 +63,14 @@ class EndpointResolverGenerator( writer.write("") writer.write("extension DefaultEndpointResolver: EndpointResolver {}") } + + private fun renderStaticResolver(writer: SwiftWriter) { + writer.write("") + writer.write( + "typealias StaticEndpointResolver = \$N", + ClientRuntimeTypes.Core.StaticEndpointResolver, + ) + writer.write("") + writer.write("extension StaticEndpointResolver: EndpointResolver {}") + } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt index bd8ff661b..99b9b0401 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt @@ -8,18 +8,17 @@ import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.StructureShape -import software.amazon.smithy.model.traits.IdempotencyTokenTrait import software.amazon.smithy.protocoltests.traits.HttpRequestTestCase +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait import software.amazon.smithy.swift.codegen.ShapeValueGenerator import software.amazon.smithy.swift.codegen.SwiftDependency import software.amazon.smithy.swift.codegen.hasStreamingMember -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol -import software.amazon.smithy.swift.codegen.middleware.MiddlewareStep import software.amazon.smithy.swift.codegen.model.RecursiveShapeBoxer +import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.model.toUpperCamelCase -import software.amazon.smithy.swift.codegen.swiftFunctionParameterIndent +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: Builder) : @@ -29,20 +28,33 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B override fun renderTestBody(test: HttpRequestTestCase) { renderExpectedBlock(test) writer.write("") + renderClientBlock(test) renderOperationBlock(test) } private fun renderExpectedBlock(test: HttpRequestTestCase) { - var resolvedHostValue = test.resolvedHost?.let { it } ?: run { "nil" } + var resolvedHostValue = if (test.resolvedHost.isPresent && test.resolvedHost.get() != "") test.resolvedHost.get() else "example.com" + var hostValue = if (test.host.isPresent && test.host.get() != "") test.host.get() else "example.com" + + // Normalize the URI + val normalizedUri = when { + test.uri == "/" -> "/" + test.uri.isEmpty() -> "" + else -> { + val trimmedUri = test.uri.removeSuffix("/") + if (!trimmedUri.startsWith('/')) "/$trimmedUri" else trimmedUri + } + } + writer.write("let urlPrefix = urlPrefixFromHost(host: \$S)", test.host) writer.write("let hostOnly = hostOnlyFromHost(host: \$S)", test.host) writer.openBlock("let expected = buildExpectedHttpRequest(") .write("method: .${test.method.toLowerCase()},") - .write("path: \$S,", test.uri) + .write("path: \$S,", normalizedUri) .call { renderExpectedHeaders(test) } .call { renderExpectedQueryParams(test) } .call { renderExpectedBody(test) } - .write("host: \$S,", test.host) + .write("host: \$S,", hostValue) .write("resolvedHost: \$S", resolvedHostValue) .closeBlock(")") } @@ -53,7 +65,7 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B val inputShape = model.expectShape(it) as StructureShape val data = writer.format( "Data(\"\"\"\n\$L\n\"\"\".utf8)", - test.body.get().replace("\\\"", "\\\\\"") + test.body.get().replace("\\\"", "\\\\\""), ) // depending on the shape of the input, wrap the expected body in a stream or not if (inputShape.hasStreamingMember(model)) { @@ -68,8 +80,26 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B } } + private fun renderClientBlock(test: HttpRequestTestCase) { + val serviceShape = ctx.service + val clientName = "${ctx.settings.sdkId}Client" + + if (!serviceShape.getTrait(EndpointRuleSetTrait::class.java).isPresent) { + val host: String? = test.host.orElse(null) + val url: String = "http://${host ?: "example.com"}" + writer.write("\nlet config = try await $clientName.${clientName}Configuration(endpointResolver: StaticEndpointResolver(endpoint: try \$N(urlString: \$S)))", SmithyHTTPAPITypes.Endpoint, url) + } else { + writer.write("\nlet config = try await $clientName.${clientName}Configuration()") + } + writer.write("config.region = \"us-west-2\"") + writer.write("config.httpClientEngine = ProtocolTestClient()") + writer.write("config.idempotencyTokenGenerator = ProtocolTestIdempotencyTokenGenerator()") + writer.write("let client = $clientName(config: config)") + } + private fun renderOperationBlock(test: HttpRequestTestCase) { operation.input.ifPresent { it -> + val clientName = "${ctx.settings.sdkId}Client" val inputShape = model.expectShape(it) model = RecursiveShapeBoxer.transform(model) writer.writeInline("\nlet input = ") @@ -83,55 +113,17 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B val outputSymbol = symbolProvider.toSymbol(outputShape) val outputErrorName = "${operation.toUpperCamelCase()}OutputError" writer.addImport(SwiftDependency.SMITHY.target) - writer.write("let context = ContextBuilder()") - val idempotentMember = inputShape.members().firstOrNull() { it.hasTrait(IdempotencyTokenTrait::class.java) } - val hasIdempotencyTokenTrait = idempotentMember != null - val httpMethod = resolveHttpMethod(operation) - writer.swiftFunctionParameterIndent { - writer.write(" .withMethod(value: .$httpMethod)") - if (hasIdempotencyTokenTrait) { - writer.write(" .withIdempotencyTokenGenerator(value: QueryIdempotencyTestTokenGenerator())") - } - writer.write(" .build()") - } - val operationStack = "operationStack" - if (!ctx.settings.useInterceptors) { - writer.write("var $operationStack = OperationStack<$inputSymbol, $outputSymbol>(id: \"${test.id}\")") - } else { - writer.addImport(SwiftDependency.SMITHY_HTTP_API.target) - writer.write("let builder = OrchestratorBuilder<$inputSymbol, $outputSymbol, SdkHttpRequest, HttpResponse>()") - } - - operationMiddleware.renderMiddleware(ctx, writer, operation, operationStack, MiddlewareStep.INITIALIZESTEP) - operationMiddleware.renderMiddleware(ctx, writer, operation, operationStack, MiddlewareStep.BUILDSTEP) - operationMiddleware.renderMiddleware(ctx, writer, operation, operationStack, MiddlewareStep.SERIALIZESTEP) - operationMiddleware.renderMiddleware(ctx, writer, operation, operationStack, MiddlewareStep.FINALIZESTEP) - operationMiddleware.renderMiddleware(ctx, writer, operation, operationStack, MiddlewareStep.DESERIALIZESTEP) - - if (ctx.settings.useInterceptors) { - writer.write( - """ - let op = builder.attributes(context) - .deserialize({ (_, _) in - return $outputSymbol() - }) - .executeRequest({ (actual, attributes) in - ${'$'}{C|} - return HttpResponse(body: .noStream, statusCode: .ok) - }) - .build() - - _ = try await op.execute(input: input) - """.trimIndent(), - Runnable { renderBodyAssert(test, inputSymbol, inputShape) } - ) - } else { - renderMockDeserializeMiddleware(test, operationStack, inputSymbol, outputSymbol, outputErrorName, inputShape) - writer.openBlock("_ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in ", "})") { - writer.write("XCTFail(\"Deserialize was mocked out, this should fail\")") - writer.write("throw SmithyTestUtilError(\"Mock handler unexpectedly failed\")") + writer.addImport(SwiftDependency.SMITHY.target) + writer.write( + """ + do { + _ = try await client.${operation.toLowerCamelCase()}(input: input) + } catch TestCheckError.actual(let actual) { + ${'$'}{C|} } - } + """.trimIndent(), + Runnable { renderBodyAssert(test, inputSymbol, inputShape) }, + ) } } @@ -140,34 +132,11 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B return httpTrait.method.toLowerCase() } - private fun renderMockDeserializeMiddleware( - test: HttpRequestTestCase, - operationStack: String, - inputSymbol: Symbol, - outputSymbol: Symbol, - outputErrorName: String, - inputShape: Shape - ) { - writer.openBlock("\$L.deserializeStep.intercept(", ")", operationStack) { - writer.write("position: .after,") - writer.openBlock("middleware: MockDeserializeMiddleware<\$N>(", ")", outputSymbol) { - writer.write("id: \"TestDeserializeMiddleware\",") - val responseClosure = ResponseClosureUtils(ctx, writer, operation).render() - writer.write("responseClosure: \$L,", responseClosure) - writer.openBlock("callback: { context, actual in", "}") { - renderBodyAssert(test, inputSymbol, inputShape) - writer.addImport(SwiftDependency.SMITHY_HTTP_API.target) - writer.write("return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: \$N())", outputSymbol) - } - } - } - } - private fun renderBodyAssert(test: HttpRequestTestCase, inputSymbol: Symbol, inputShape: Shape) { if (test.body.isPresent && test.body.get().isNotBlank()) { writer.openBlock( "try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in", - "})" + "})", ) { writer.write("XCTAssertNotNil(actualHttpBody, \"The actual ByteStream is nil\")") writer.write("XCTAssertNotNil(expectedHttpBody, \"The expected ByteStream is nil\")") @@ -180,7 +149,7 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B } } else { writer.write( - "try await self.assertEqual(expected, actual)" + "try await self.assertEqual(expected, actual)", ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt index 89b34bc9b..7e99367c4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt @@ -74,6 +74,7 @@ object ClientRuntimeTypes { val DefaultEndpointsAuthSchemeResolver = runtimeSymbol("DefaultEndpointsAuthSchemeResolver", SwiftDeclaration.STRUCT) val EndpointsAuthScheme = runtimeSymbol("EndpointsAuthScheme", SwiftDeclaration.ENUM) val DefaultEndpointResolver = runtimeSymbol("DefaultEndpointResolver", SwiftDeclaration.STRUCT) + val StaticEndpointResolver = runtimeSymbol("StaticEndpointResolver", SwiftDeclaration.STRUCT) val EndpointResolverMiddleware = runtimeSymbol("EndpointResolverMiddleware", SwiftDeclaration.STRUCT) val Plugin = runtimeSymbol("Plugin", SwiftDeclaration.PROTOCOL) val ClientConfiguration = runtimeSymbol("ClientConfiguration", SwiftDeclaration.PROTOCOL) diff --git a/smithy-swift-codegen/src/test/kotlin/HttpProtocolUnitTestRequestGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/HttpProtocolUnitTestRequestGeneratorTests.kt deleted file mode 100644 index f0f0fd770..000000000 --- a/smithy-swift-codegen/src/test/kotlin/HttpProtocolUnitTestRequestGeneratorTests.kt +++ /dev/null @@ -1,725 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -import io.kotest.matchers.string.shouldContainOnlyOnce -import org.junit.jupiter.api.Test -import software.amazon.smithy.swift.codegen.model.AddOperationShapes - -class HttpProtocolUnitTestRequestGeneratorTests { - var model = javaClass.getResource("http-binding-protocol-generator-test.smithy").asSmithy() - private fun newTestContext(): TestContext { - val settings = model.defaultSettings() - model = AddOperationShapes.execute(model, settings.getService(model), settings.moduleName) - return model.newTestContext() - } - - val ctx = newTestContext() - init { - ctx.generator.initializeMiddleware(ctx.generationCtx) - ctx.generator.generateProtocolUnitTests(ctx.generationCtx) - ctx.generationCtx.delegator.flushWriters() - } - - @Test - fun `it creates smoke test request test`() { - val contents = getTestFileContents("Tests/example", "SmokeTestRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - - val expectedContents = """ - func testSmokeTest() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .post, - path: "/smoketest/{label1}/foo", - headers: [ - "X-Header1": "Foo", - "X-Header2": "Bar" - ], - requiredHeaders: [ - "Content-Length" - ], - queryParams: [ - "Query1=Query 1" - ], - body: .data(Data(""${'"'} - { - "payload1": "String", - "payload2": 2, - "payload3": { - "member1": "test string", - "member2": "test string 2" - } - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = SmokeTestInput( - header1: "Foo", - header2: "Bar", - label1: "label", - payload1: "String", - payload2: 2, - payload3: Nested( - member1: "test string", - member2: "test string 2" - ), - query1: "Query 1" - ) - let context = ContextBuilder() - .withMethod(value: .post) - .build() - var operationStack = OperationStack(id: "SmokeTest") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, SmokeTestInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.HeaderMiddleware(SmokeTestInput.headerProvider(_:))) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.QueryItemMiddleware(SmokeTestInput.queryItemProvider(_:))) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/json")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: SmokeTestInput.write(value:to:))) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: SmokeTestOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: SmokeTestOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -} -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates explicit string test`() { - val contents = getTestFileContents("Tests/example", "ExplicitStringRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testExplicitString() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .post, - path: "/explicit/string", - requiredHeaders: [ - "Content-Length" - ], - body: .data(Data(""${'"'} - { - "payload1": "explicit string" - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = ExplicitStringInput( - payload1: "explicit string" - ) - let context = ContextBuilder() - .withMethod(value: .post) - .build() - var operationStack = OperationStack(id: "ExplicitString") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, ExplicitStringInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "text/plain")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.StringBodyMiddleware(keyPath: \.payload1)) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: ExplicitStringOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: ExplicitStringOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test for a request without a body`() { - val contents = getTestFileContents("Tests/example", "EmptyInputAndEmptyOutputRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testRestJsonEmptyInputAndEmptyOutput() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .post, - path: "/EmptyInputAndEmptyOutput", - body: nil, - host: "", - resolvedHost: "" - ) - - let input = EmptyInputAndEmptyOutputInput( - ) - let context = ContextBuilder() - .withMethod(value: .post) - .build() - var operationStack = OperationStack(id: "RestJsonEmptyInputAndEmptyOutput") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, EmptyInputAndEmptyOutputInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: EmptyInputAndEmptyOutputOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: EmptyInputAndEmptyOutputOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test for a request without a body given an empty object`() { - val contents = getTestFileContents("Tests/example", "SimpleScalarPropertiesRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testRestJsonDoesntSerializeNullStructureValues() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .put, - path: "/SimpleScalarProperties", - headers: [ - "Content-Type": "application/json" - ], - body: .data(Data(""${'"'} - {} - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = SimpleScalarPropertiesInput( - stringValue: nil - ) - let context = ContextBuilder() - .withMethod(value: .put) - .build() - var operationStack = OperationStack(id: "RestJsonDoesntSerializeNullStructureValues") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, SimpleScalarPropertiesInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.HeaderMiddleware(SimpleScalarPropertiesInput.headerProvider(_:))) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/json")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: SimpleScalarPropertiesInput.write(value:to:))) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: SimpleScalarPropertiesOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: SimpleScalarPropertiesOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test with a string to be converted to data`() { - val contents = getTestFileContents("Tests/example", "StreamingTraitsRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testRestJsonStreamingTraitsWithBlob() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .post, - path: "/StreamingTraits", - headers: [ - "Content-Type": "application/octet-stream", - "X-Foo": "Foo" - ], - body: .stream(SmithyStreams.BufferedStream(data: Data(""${'"'} - blobby blob blob - ""${'"'}.utf8), isClosed: true)), - host: "", - resolvedHost: "" - ) - - let input = StreamingTraitsInput( - blob: .stream(SmithyStreams.BufferedStream(data: "blobby blob blob".data(using: .utf8)!, isClosed: true)), - foo: "Foo" - ) - let context = ContextBuilder() - .withMethod(value: .post) - .build() - var operationStack = OperationStack(id: "RestJsonStreamingTraitsWithBlob") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, StreamingTraitsInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.HeaderMiddleware(StreamingTraitsInput.headerProvider(_:))) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/octet-stream")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.BlobStreamBodyMiddleware(keyPath: \.blob)) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: StreamingTraitsOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: StreamingTraitsOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates unit test with an empty map`() { - val contents = getTestFileContents("Tests/example", "HttpPrefixHeadersRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testRestJsonHttpPrefixHeadersAreNotPresent() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .get, - path: "/HttpPrefixHeaders", - headers: [ - "X-Foo": "Foo" - ], - body: nil, - host: "", - resolvedHost: "" - ) - - let input = HttpPrefixHeadersInput( - foo: "Foo", - fooMap: [:] - - ) - let context = ContextBuilder() - .withMethod(value: .get) - .build() - var operationStack = OperationStack(id: "RestJsonHttpPrefixHeadersAreNotPresent") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, HttpPrefixHeadersInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.HeaderMiddleware(HttpPrefixHeadersInput.headerProvider(_:))) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: HttpPrefixHeadersOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: HttpPrefixHeadersOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test for union shapes`() { - val contents = getTestFileContents("Tests/example", "JsonUnionsRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testRestJsonSerializeStringUnionValue() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .put, - path: "/JsonUnions", - headers: [ - "Content-Type": "application/json" - ], - body: .data(Data(""${'"'} - { - "contents": { - "stringValue": "foo" - } - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = JsonUnionsInput( - contents: MyUnion.stringvalue("foo") - - ) - let context = ContextBuilder() - .withMethod(value: .put) - .build() - var operationStack = OperationStack(id: "RestJsonSerializeStringUnionValue") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, JsonUnionsInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/json")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: JsonUnionsInput.write(value:to:))) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: JsonUnionsOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: JsonUnionsOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test for recursive shapes`() { - val contents = getTestFileContents("Tests/example", "RecursiveShapesRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testRestJsonRecursiveShapes() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .put, - path: "/RecursiveShapes", - headers: [ - "Content-Type": "application/json" - ], - body: .data(Data(""${'"'} - { - "nested": { - "foo": "Foo1", - "nested": { - "bar": "Bar1", - "recursiveMember": { - "foo": "Foo2", - "nested": { - "bar": "Bar2" - } - } - } - } - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = RecursiveShapesInput( - nested: RecursiveShapesInputOutputNested1( - foo: "Foo1", - nested: RecursiveShapesInputOutputNested2( - bar: "Bar1", - recursiveMember: RecursiveShapesInputOutputNested1( - foo: "Foo2", - nested: RecursiveShapesInputOutputNested2( - bar: "Bar2" - ) - ) - ) - ) - ) - let context = ContextBuilder() - .withMethod(value: .put) - .build() - var operationStack = OperationStack(id: "RestJsonRecursiveShapes") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, RecursiveShapesInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/json")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: RecursiveShapesInput.write(value:to:))) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: RecursiveShapesOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: RecursiveShapesOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test for inline document`() { - val contents = getTestFileContents("Tests/example", "InlineDocumentRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testInlineDocumentInput() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .put, - path: "/InlineDocument", - headers: [ - "Content-Type": "application/json" - ], - body: .data(Data(""${'"'} - { - "stringValue": "string", - "documentValue": { - "foo": "bar" - } - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = InlineDocumentInput( - documentValue: try SmithyReadWrite.Document.make(from: Data(""${'"'} - { - "foo": "bar" - } - ""${'"'}.utf8)) - , - stringValue: "string" - ) - let context = ContextBuilder() - .withMethod(value: .put) - .build() - var operationStack = OperationStack(id: "InlineDocumentInput") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, InlineDocumentInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/json")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: InlineDocumentInput.write(value:to:))) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: InlineDocumentOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: InlineDocumentOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it creates a unit test for inline document as payload`() { - val contents = getTestFileContents("Tests/example", "InlineDocumentAsPayloadRequestTest.swift", ctx.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = """ - func testInlineDocumentAsPayloadInput() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .put, - path: "/InlineDocumentAsPayload", - headers: [ - "Content-Type": "application/json" - ], - body: .data(Data(""${'"'} - { - "foo": "bar" - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = InlineDocumentAsPayloadInput( - documentValue: try SmithyReadWrite.Document.make(from: Data(""${'"'} - { - "foo": "bar" - } - ""${'"'}.utf8)) - - ) - let context = ContextBuilder() - .withMethod(value: .put) - .build() - var operationStack = OperationStack(id: "InlineDocumentAsPayloadInput") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, InlineDocumentAsPayloadInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.ContentTypeMiddleware(contentType: "application/json")) - operationStack.serializeStep.intercept(position: .after, middleware: ClientRuntime.PayloadBodyMiddleware(rootNodeInfo: "", inputWritingClosure: SmithyReadWrite.WritingClosures.writeDocument(value:to:), keyPath: \.documentValue, defaultBody: "{}")) - operationStack.finalizeStep.intercept(position: .before, middleware: ClientRuntime.ContentLengthMiddleware()) - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: InlineDocumentAsPayloadOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: InlineDocumentAsPayloadOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } -} diff --git a/smithy-swift-codegen/src/test/kotlin/IsolatedHttpProtocolUnitTestRequestGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/IsolatedHttpProtocolUnitTestRequestGeneratorTests.kt deleted file mode 100644 index d30234c12..000000000 --- a/smithy-swift-codegen/src/test/kotlin/IsolatedHttpProtocolUnitTestRequestGeneratorTests.kt +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -import io.kotest.matchers.string.shouldContainOnlyOnce -import org.junit.jupiter.api.Test - -class IsolatedHttpProtocolUnitTestRequestGeneratorTests { - @Test - fun `it can handle nan values`() { - val context = setupTests("Isolated/number-type-test.smithy", "aws.protocoltests.restjson#RestJson") - val contents = getFileContents(context.manifest, "Tests/RestJsonTests/HttpRequestWithFloatLabelsRequestTest.swift") - - val expectedContents = """ - func testRestJsonSupportsNaNFloatLabels() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .get, - path: "/FloatHttpLabels/NaN/NaN", - body: nil, - host: "", - resolvedHost: "" - ) - - let input = HttpRequestWithFloatLabelsInput( - double: Swift.Double.nan, - float: Swift.Float.nan - ) - let context = ContextBuilder() - .withMethod(value: .get) - .build() - var operationStack = OperationStack(id: "RestJsonSupportsNaNFloatLabels") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, HttpRequestWithFloatLabelsInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: HttpRequestWithFloatLabelsOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: HttpRequestWithFloatLabelsOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it can handle infinity values`() { - val context = setupTests("Isolated/number-type-test.smithy", "aws.protocoltests.restjson#RestJson") - val contents = getFileContents(context.manifest, "Tests/RestJsonTests/HttpRequestWithFloatLabelsRequestTest.swift") - - val expectedContents = """ - func testRestJsonSupportsInfinityFloatLabels() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .get, - path: "/FloatHttpLabels/Infinity/Infinity", - body: nil, - host: "", - resolvedHost: "" - ) - - let input = HttpRequestWithFloatLabelsInput( - double: Swift.Double.infinity, - float: Swift.Float.infinity - ) - let context = ContextBuilder() - .withMethod(value: .get) - .build() - var operationStack = OperationStack(id: "RestJsonSupportsInfinityFloatLabels") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, HttpRequestWithFloatLabelsInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: HttpRequestWithFloatLabelsOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: HttpRequestWithFloatLabelsOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it can handle negative infinity values`() { - val context = setupTests("Isolated/number-type-test.smithy", "aws.protocoltests.restjson#RestJson") - val contents = getFileContents(context.manifest, "Tests/RestJsonTests/HttpRequestWithFloatLabelsRequestTest.swift") - - val expectedContents = """ - func testRestJsonSupportsNegativeInfinityFloatLabels() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .get, - path: "/FloatHttpLabels/-Infinity/-Infinity", - body: nil, - host: "", - resolvedHost: "" - ) - - let input = HttpRequestWithFloatLabelsInput( - double: -Swift.Double.infinity, - float: -Swift.Float.infinity - ) - let context = ContextBuilder() - .withMethod(value: .get) - .build() - var operationStack = OperationStack(id: "RestJsonSupportsNegativeInfinityFloatLabels") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, HttpRequestWithFloatLabelsInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: HttpRequestWithFloatLabelsOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: HttpRequestWithFloatLabelsOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it can handle nan values in response`() { - val context = setupTests("Isolated/number-type-test.smithy", "aws.protocoltests.restjson#RestJson") - val contents = getFileContents(context.manifest, "Tests/RestJsonTests/InputAndOutputWithHeadersResponseTest.swift") - - val expectedContents = """ -class InputAndOutputWithHeadersResponseTest: HttpResponseTestBase { - /// Supports handling NaN float header values. - func testRestJsonSupportsNaNFloatHeaderOutputs() async throws { - guard let httpResponse = buildHttpResponse( - code: 200, - headers: [ - "X-Double": "NaN", - "X-Float": "NaN" - ], - content: nil - ) else { - XCTFail("Something is wrong with the created http response") - return - } - - let actual: InputAndOutputWithHeadersOutput = try await InputAndOutputWithHeadersOutput.httpOutput(from:)(httpResponse) - - let expected = InputAndOutputWithHeadersOutput( - headerDouble: Swift.Double.nan, - headerFloat: Swift.Float.nan - ) - - XCTAssertEqual(actual, expected) - - } -} -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it generates the document type correctly`() { - val context = setupTests("Isolated/document-type-test.smithy", "aws.protocoltests.restjson#RestJson") - val contents = getFileContents(context.manifest, "Tests/RestJsonTests/DocumentTypeRequestTest.swift") - - val expectedContents = """ -class DocumentTypeRequestTest: HttpRequestTestBase { - /// Serializes document types using a list. - func testDocumentInputWithList() async throws { - let urlPrefix = urlPrefixFromHost(host: "") - let hostOnly = hostOnlyFromHost(host: "") - let expected = buildExpectedHttpRequest( - method: .put, - path: "/DocumentType", - headers: [ - "Content-Type": "application/json" - ], - body: .data(Data(""${'"'} - { - "stringValue": "string", - "documentValue": [ - true, - "hi", - [ - 1, - 2 - ], - { - "foo": { - "baz": [ - 3, - 4 - ] - } - } - ] - } - ""${'"'}.utf8)), - host: "", - resolvedHost: "" - ) - - let input = DocumentTypeInput( - documentValue: try SmithyReadWrite.Document.make(from: Data(""${'"'} - [ - true, - "hi", - [ - 1, - 2 - ], - { - "foo": { - "baz": [ - 3, - 4 - ] - } - } - ] - ""${'"'}.utf8)) - , - stringValue: "string" - ) - let context = ContextBuilder() - .withMethod(value: .put) - .build() - var operationStack = OperationStack(id: "DocumentInputWithList") - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLPathMiddleware(urlPrefix: urlPrefix, DocumentTypeInput.urlPathProvider(_:))) - operationStack.initializeStep.intercept(position: .after, middleware: ClientRuntime.URLHostMiddleware(host: hostOnly)) - operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> ClientRuntime.OperationOutput in - input.withMethod(context.method) - input.withPath(context.path) - let host = "\(context.hostPrefix ?? "")\(context.host ?? "")" - input.withHost(host) - return try await next.handle(context: context, input: input) - } - operationStack.deserializeStep.intercept( - position: .after, - middleware: MockDeserializeMiddleware( - id: "TestDeserializeMiddleware", - responseClosure: DocumentTypeOutput.httpOutput(from:), - callback: { context, actual in - try await self.assertEqual(expected, actual, { (expectedHttpBody, actualHttpBody) -> Void in - XCTAssertNotNil(actualHttpBody, "The actual ByteStream is nil") - XCTAssertNotNil(expectedHttpBody, "The expected ByteStream is nil") - try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: .json) - }) - return OperationOutput(httpResponse: HttpResponse(body: ByteStream.noStream, statusCode: .ok), output: DocumentTypeOutput()) - } - ) - ) - _ = try await operationStack.handleMiddleware(context: context, input: input, next: MockHandler() { (context, request) in - XCTFail("Deserialize was mocked out, this should fail") - throw SmithyTestUtilError("Mock handler unexpectedly failed") - }) - } -} -""" - contents.shouldContainOnlyOnce(expectedContents) - } - - private fun setupTests(smithyFile: String, serviceShapeId: String): TestContext { - val context = TestContext.initContextFrom(smithyFile, serviceShapeId, MockHTTPRestJsonProtocolGenerator()) { model -> - model.defaultSettings(serviceShapeId, "RestJson", "2019-12-16", "Rest Json Protocol") - } - context.generator.generateProtocolUnitTests(context.generationCtx) - context.generationCtx.delegator.flushWriters() - return context - } -}