Skip to content

Conversation

@jbelkins
Copy link
Contributor

Description of changes

Makes HTTP response & error protocol tests independent of internal implementation by using a specially configured client to perform the tests, similar to what we already do for request tests.

This change will allow schema-based services and services using current ReadWrite serde to use the same protocol tests, since the test no longer utilizes client internals.

Specific changes:

  • Client is created with a special HTTP client for response tests, that returns the test's httpResponse when called.
  • Similar to request tests, client uses dummy credential and endpoint resolution.
  • Input is populated to the extent needed to allow request creation to succeed.
  • Response & error tests are refactored to share setup.

Also:

  • Clients initialize CRT at the start of client initialization, to ensure that CRT is initialized before any CRT functions are called. (This has been an ongoing problem when clients are configured with non-default components; performing initialization in the client initializer should remedy this for good.)

Scope

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

/// Calls to CRT functions may crash the SDK if `CommonRuntimeKit.initialize()` is not called first.
///
/// This function may safely be called multiple times.
public func initialize() { CommonRuntimeKit.initialize() }
Copy link
Contributor Author

@jbelkins jbelkins Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some protocol tests were crashing because they were calling CRT before it had been initialized. We have seen this problem crop up from time to time when customers swap out our CRT-based implementations of things for mocks or for their own.

The issue is that we typically initialize CRT at the point of use, but not all points of use. So if we remove a config component that is initializing CRT, then the first call might get made from another component that doesn't initialize CRT but was depending on the removed one to do it. That results in a crash.

To fix this problem once & for all, we'll be calling this during initialization of any client, so that we're assured CRT is already initialized before any operation is started.


import protocol SmithyHTTPAPI.HTTPClient
import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below is a simple mock that conforms to the HTTPClient protocol. Rather than making a request it simply returns the response it was created with.

It will be used to initialize the client when performing HTTP response & error protocol tests.


open fun renderInitFunction() {
writer.openBlock("public required init(config: \$L) {", "}", serviceConfig.typeName) {
writer.write("\$N()", ClientRuntimeTypes.Core.initialize)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the call to ClientRuntime.initialize() that will ensure CRT is initialized before any operation is called.

builder: Builder,
) : HttpProtocolUnitTestResponseGenerator(builder) {
val error: Shape = builder.error ?: throw CodegenException("builder did not set an error shape")
override val outputShape: Shape? = error
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This override is confusing because the error is not an output shape; these terms have specific meaning. Better to just use the error property (just above) directly.

writer.write("")
renderActualOutput()
writer.write("")
renderCompareActualAndExpectedErrors(test, error)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above, we rendered a superfluous do-catch to wrap the error test & fail on an error, it's useless because XCTest fails on an uncaught error anyway.

val errorType = symbolProvider.toSymbol(errorShape).name

writer.openBlock("if let actual = \$L as? \$L {", "} else {", operationErrorVariableName, errorType) {
writer.openBlock("if let actual = operationError as? \$L {", "} else {", errorType) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here & elsewhere in this file, operationErrorVariableName is deleted and the name operationError is used in all cases.

Trying to use a different name per-test for the variable holding the error adds complexity for no gain.


private fun renderActualOutput(outputStruct: Symbol) {
val responseClosure = ResponseClosureUtils(ctx, writer, operation).render()
writer.write("let actual: \$N = try await \$L(httpResponse)", outputStruct, responseClosure)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above accessed client internals to process the HTTP response into an output.

It is replaced with a specially configured client, see below.

let serviceName = "Rest Json Protocol"
public required init(config: RestJsonProtocolClient.RestJsonProtocolClientConfiguration) {
ClientRuntime.initialize()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here & below are codegen test changes.

"let actual = try await client.\$L(input: input)",
operation.toLowerCamelCase(),
)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an output test, captureResponse() renders a call to the operation and stores the result for verification.

Error tests override this method to catch and store the thrown error for test verification.

@jbelkins jbelkins requested review from dayaffe and sichanyoo January 29, 2026 23:29
@jbelkins jbelkins marked this pull request as ready for review January 29, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants