HTTP Protocol Compliance Tests#
Smithy is a protocol-agnostic IDL that tries to abstract the serialization format of request and response messages sent between a client and server. Protocol specifications define the serialization format of a protocol, and protocol compliance tests help to ensure that implementations correctly implement a protocol specification.
Overview#
This specification defines two traits in the smithy.test
namespace that
are used to make assertions about client and server protocol implementations.
- smithy.test#httpRequestTests
- Used to define how an HTTP request is serialized given a specific protocol, authentication scheme, and set of input parameters.
- smithy.test#httpResponseTests
- Used to define how an HTTP response is serialized given a specific protocol, authentication scheme, and set of output or error parameters.
Additionally, it defines one trait specifically for the behavior of server protocol implementations.
- smithy.test#httpMalformedRequestTests
- Used to define how a server rejects a malformed HTTP request given a specific protocol and HTTP message.
Protocol implementation developers use these traits to ensure that their implementation is correct. This can be done through code generation of test cases or by dynamically loading test cases at runtime. For example, a Java implementation could generate JUnit test cases to assert that the expectations defined in a model match the behavior of a generated client or server.
Parameter format#
The params
property used in both the httpRequestTests
trait and
httpResponseTests
trait test cases represents parameters that are used
to serialize HTTP requests and responses. In order to compare implementation
specific results against the expected result of each test case across
different programming languages, parameters are defined in the same format
specified in Trait node values with the following additional
constraints:
- Timestamp values must be converted to a Unix timestamp represented as an integer.
- Client implementations that automatically provide values for members marked
with the idempotencyToken trait MUST use a constant value of
00000000-0000-4000-8000-000000000000
.
httpRequestTests#
- Summary
- The
httpRequestTests
trait is used to define how an HTTP request is serialized given a specific protocol, authentication scheme, and set of input parameters. - Trait selector
operation
- Value type
list
ofHttpRequestTestCase
structures
The httpRequestTests
trait is a list of HttpRequestTestCase
structures
that support the following members:
Property | Type | Description |
---|---|---|
id | string |
Required. The identifier of the test case. This identifier can
be used by protocol test implementations to filter out unsupported
test cases by ID, to generate test case names, etc. The provided
id MUST match Smithy's smithy:Identifier ABNF. No two
httpRequestTests test cases can share the same ID. |
protocol | shape ID | Required. A shape ID that targets a shape marked with the protocolDefinition trait. Because Smithy services can support multiple protocols, each test MUST specify which protocol is under test. |
method | string |
Required. The expected serialized HTTP request method. |
uri | string |
Required. The request-target of the HTTP request, not including the query string (for example, "/foo/bar"). |
host | string |
The host or endpoint provided as input used to generate the HTTP request (for example, "example.com").
|
resolvedHost | string |
The expected host present in the This can differ from the Server implementations SHOULD ignore discrepancies in paths when
comparing the |
authScheme | shape ID | A shape ID that specifies the optional authentication scheme to assume. It's possible that specific authentication schemes might influence the serialization logic of an HTTP request. The targeted shape MUST be marked with the authDefinition trait trait. |
queryParams | list<string> |
A list of the expected serialized query string parameters. Each element in the list is a query string key value pair that starts with the query string parameter name optionally followed by "=", optionally followed by the query string parameter value. For example, "foo=bar", "foo=", and "foo" are all valid values. Note This kind of list is used instead of a map so that query string parameter values for lists can be represented using repeated key-value pairs. The query string parameter name and the value MUST appear in the format in which it is expected to be sent over the wire; if a key or value needs to be percent-encoded, then it MUST appear percent-encoded in this list. A serialized HTTP request is not in compliance with the protocol
if any query string parameter defined in
|
forbidQueryParams | list<string> |
A list of query string parameter names that must not appear in the serialized HTTP request. Each value MUST appear in the format in which it is sent over the wire; if a key needs to be percent-encoded, then it MUST appear percent-encoded in this list. |
requireQueryParams | list<string> |
A list of query string parameter names that MUST appear in the serialized request URI, but no assertion is made on the value. Each value MUST appear in the format in which it is sent over the wire; if a key needs to be percent-encoded, then it MUST appear percent-encoded in this list. |
headers | map<string, string> |
A map of expected HTTP headers. Each key represents a header field name and each value represents the expected header value. An HTTP request is not in compliance with the protocol if any listed header is missing from the serialized request or if the expected header value differs from the serialized request value.
|
forbidHeaders | [string ] |
A list of header field names that must not appear in the serialized HTTP request. |
requireHeaders | [string ] |
A list of header field names that must appear in the serialized
HTTP message, but no assertion is made on the value. Headers listed
in headers do not need to appear in this list. |
body | string |
The expected HTTP message body. If no request body is defined,
then no assertions are made about the body of the message. Because
the body parameter is a string, binary data MUST be represented
in body by base64 encoding the data (for example, use "Zm9vCg=="
and not "foo"). |
bodyMediaType | string |
The media type of the body . This is used to help test runners
to parse and validate the expected data against generated data. |
params | document |
For clients, defines the input parameters used to generate the HTTP request. For servers, defines the input parameters extracted from the HTTP request. These parameters MUST be compatible with the input of the operation. Parameter values that contain binary data MUST be defined using values that can be represented in plain text (for example, use "foo" and not "Zm9vCg=="). While this limits the kinds of binary values that can be tested in protocol tests, it allows protocol tests to demonstrate the requirement of many protocols that binary data is automatically base64 encoded and decoded. |
vendorParams | document |
Defines vendor-specific parameters that are used to influence the request. For example, some vendors might utilize environment variables, configuration files on disk, or other means to influence the serialization formats used by clients or servers. If a |
vendorParamsShape | shape ID | A shape to be used to validate the If set, the parameters in |
documentation | string |
A description of the test and what is being asserted defined in CommonMark. |
tags | [string] |
Attaches a list of tags that allow test cases to be categorized and grouped. |
appliesTo | string , one of "client" or "server" |
Indicates that the test case is only to be implemented by "client" or "server" implementations. This property is useful for identifying and testing edge cases of clients and servers that are impossible or undesirable to test in both client and server implementations. For example, a "server" test might be useful to ensure a service can gracefully receive a request that optionally contains a payload. Is is assumed that test cases that do not define an |
HTTP request example#
The following example defines a protocol compliance test for a JSON protocol that uses HTTP binding traits.
$version: "2"
namespace smithy.example
use smithy.test#httpRequestTests
@endpoint(hostPrefix: "{hostLabel}.prefix.")
@http(method: "POST", uri: "/")
@httpRequestTests([
{
id: "say_hello"
protocol: exampleProtocol
params: {
"hostLabel": "foo"
"greeting": "Hi"
"name": "Teddy"
"query": "Hello there"
}
method: "POST"
host: "example.com"
resolvedHost: "foo.prefix.example.com"
uri: "/"
queryParams: [
"Hi=Hello%20there"
]
headers: {
"X-Greeting": "Hi"
}
body: "{\"name\": \"Teddy\"}"
bodyMediaType: "application/json"
}
])
operation SayHello {
input: SayHelloInput
output: Unit
}
@input
structure SayHelloInput {
@required
@hostLabel
hostLabel: String
@httpHeader("X-Greeting")
greeting: String
@httpQuery("Hi")
query: String
name: String
}
httpResponseTests#
- Summary
- The
httpResponseTests
trait is used to define how an HTTP response is serialized given a specific protocol, authentication scheme, and set of output or error parameters. - Trait selector
:test(operation, structure[trait|error])
- Value type
list
ofHttpResponseTestCase
structures
The httpResponseTests
trait is a list of HttpResponseTestCase
structures that support the following members:
Property | Type | Description |
---|---|---|
id | string |
Required. The identifier of the test case. This identifier can
be used by protocol test implementations to filter out unsupported
test cases by ID, to generate test case names, etc. The provided
id MUST match Smithy's smithy:Identifier ABNF. No two
httpResponseTests test cases can share the same ID. |
protocol | string |
Required. A shape ID that targets a shape marked with the protocolDefinition trait trait. Because Smithy services can support multiple protocols, each test MUST specify which protocol is under test. |
code | integer |
Required. The expected HTTP response status code. |
authScheme | shape ID | A shape ID that specifies the optional authentication scheme to assume. It's possible that specific authentication schemes might influence the serialization logic of an HTTP response. The targeted shape MUST be marked with the authDefinition trait trait. |
headers | map<string, string> |
A map of expected HTTP headers. Each key represents a header field name and each value represents the expected header value. An HTTP response is not in compliance with the protocol if any listed header is missing from the serialized response or if the expected header value differs from the serialized response value.
|
forbidHeaders | list<string> |
A list of header field names that must not appear in the serialized HTTP response. |
requireHeaders | list<string> |
A list of header field names that must appear in the serialized
HTTP response, but no assertion is made on the value. Headers listed
in headers do not need to appear in this list. |
body | string |
The expected HTTP message body. If no response body is defined, then no assertions are made about the body of the message. |
bodyMediaType | string |
The media type of the body . This is used to help test runners
to parse and validate the expected data against generated data.
Binary media type formats require that the contents of body are
base64 encoded. |
params | document |
For clients, defines the output or error parameters extracted from the HTTP response. For servers, defines the output or error parameters used to generate the HTTP response. These parameters MUST be compatible with the targeted operation's output or the targeted error structure. Parameter values that contain binary data MUST be defined using values that can be represented in plain text (for example, use "foo" and not "Zm9vCg=="). While this limits the kinds of binary values that can be tested in protocol tests, it allows protocol tests to demonstrate the requirement of many protocols that binary data is automatically base64 encoded and decoded. |
vendorParams | document |
Defines vendor-specific parameters that are used to influence the response. For example, some vendors might utilize environment variables, configuration files on disk, or other means to influence the serialization formats used by clients or servers. If a |
vendorParamsShape | shape ID | A shape to be used to validate the If set, the parameters in |
documentation | string |
A description of the test and what is being asserted defined in CommonMark. |
tags | [string] |
Attaches a list of tags that allow test cases to be categorized and grouped. |
appliesTo | string , one of "client" or "server" |
Indicates that the test case is only to be implemented by "client" or "server" implementations. This property is useful for identifying and testing edge cases of clients and servers that are impossible or undesirable to test in both client and server implementations. For example, a "client" test might be useful to ensure a client can gracefully receive a response that optionally contains a payload. Is is assumed that test cases that do not define an |
HTTP response example#
The following example defines a protocol compliance test for a JSON protocol that uses HTTP binding traits.
$version: "2"
namespace smithy.example
use smithy.test#httpResponseTests
@http(method: "POST", uri: "/")
@httpResponseTests([
{
id: "say_goodbye"
protocol: exampleProtocol
params: {farewell: "Bye"}
code: 200
headers: {
"X-Farewell": "Bye"
"Content-Length": "0"
}
}
])
operation SayGoodbye {
input: SayGoodbyeInput
output: SayGoodbyeOutput
}
@input
structure SayGoodbyeInput {}
@output
structure SayGoodbyeOutput {
@httpHeader("X-Farewell")
farewell: String
}
HTTP error response example#
The httpResponseTests
trait can be applied to error structures to define
how an error HTTP response is serialized. Client protocol compliance test
implementations SHOULD ensure that each error with the httpResponseTests
trait associated with an operation can be properly deserialized.
The following example defines a protocol compliance test for a JSON protocol that uses HTTP binding traits.
$version: "2"
namespace smithy.example
use smithy.test#httpResponseTests
@error("client")
@httpError(400)
@httpResponseTests([
{
id: "invalid_greeting"
protocol: exampleProtocol
params: {foo: "baz", message: "Hi"}
code: 400
headers: {"X-Foo": "baz"}
body: "{\"message\": \"Hi\"}"
bodyMediaType: "application/json"
}
])
structure InvalidGreeting {
@httpHeader("X-Foo")
foo: String
message: String
}
httpMalformedRequestTests#
- Summary
- The
httpMalformedRequestTests
trait is used to define how a malformed HTTP request is rejected given a specific protocol and HTTP message. Protocol implementations MUST assert that requests are rejected during request processing. - Trait selector
operation
- Value type
list
ofHttpMalformedRequestTestCase
structures
The httpMalformedRequestTests
trait is a list of
HttpMalformedRequestTestCase
structures that support the following members:
Property | Type | Description |
---|---|---|
id | string |
Required. The identifier of the test case. This identifier can
be used by protocol test implementations to filter out unsupported
test cases by ID, to generate test case names, etc. The provided
id MUST match Smithy's smithy:Identifier ABNF. No two
httpMalformedRequestTests test cases can share the same ID. |
protocol | shape ID | Required. A shape ID that targets a shape marked with the protocolDefinition trait. Because Smithy services can support multiple protocols, each test MUST specify which protocol is under test. |
request | HttpMalformedRequestDefinition | Required. A structure that describes the request. |
response | HttpMalformedResponseDefinition | Required. A structure that describes the required response. |
documentation | string |
A description of the test and what is being asserted defined in CommonMark. |
tags | [string] |
Attaches a list of tags that allow test cases to be categorized and grouped. Using tags to describe types of failures gives implementations control of test execution across different suites of tests. For example, it allows tests to be executed that exercise booleans being converted into numerics, even if there are such tests written for values appearing in paths, query strings, headers, and message bodies across different protocols. |
testParameters | map<string, list<string>> |
Optional parameters that are substituted into each member of
The lists of values for each key must be identical in length. One test permutation is generated for each index the parameter lists. For example, parameters with 5 values for each key will generate 5 tests in total. Parameter values are substituted using the conventions described by
the documentation for CodeWriter. They are available as named
parameters, and implementations must support both the Note If |
HttpMalformedRequestDefinition#
Property | Type | Description |
---|---|---|
method | string |
Required. The HTTP request method. |
uri | string |
Required. The request-target of the HTTP request, not including the query string (for example, "/foo/bar"). |
host | string |
The host or endpoint provided as input used to generate the HTTP request (for example, "example.com"). |
queryParams | list<string> |
A list of the serialized query string parameters to include in the request. Each element in the list is a query string key value pair that starts with the query string parameter name optionally followed by "=", optionally followed by the query string parameter value. For example, "foo=bar", "foo=", and "foo" are all valid values. Note This kind of list is used instead of a map so that query string parameter values for lists can be represented using repeated key-value pairs. The query string parameter name and the value MUST appear in the format in which it is expected to be sent over the wire; if a key or value needs to be percent-encoded, then it MUST appear percent-encoded in this list. |
headers | map<string, string> |
A map of HTTP headers to include in the request. Each key represents a header field name and each value represents the expected header value. |
body | string |
The HTTP message body to include in the request. Because the body
parameter is a string, binary data MUST be represented in body by
base64 encoding the data (for example, use "Zm9vCg==" and not "foo"). |
HttpMalformedResponseDefinition#
Property | Type | Description |
---|---|---|
headers | map<string, string> |
A map of expected HTTP headers. Each key represents a header field name and each value represents the expected header value. An HTTP response is not in compliance with the protocol if any listed header is missing from the serialized response or if the expected header value differs from the serialized response value.
|
code | integer |
Required. The expected HTTP response status code. |
body | HttpMalformedResponseBodyDefinition | The expected response body. |
HttpMalformedResponseBodyDefinition#
Property | Type | Description |
---|---|---|
assertion | HttpMalformedResponseBodyAssertion | Required. The assertion to be applied to the response body. |
mediaType | string |
Required. The media type of the body . This is used to help test
runners to parse and validate the expected data against generated data.
Binary media type formats require that the contents of body are
base64 encoded. |
HttpMalformedResponseBodyAssertion#
A union describing the assertion to run against the response body. As it is a union, exactly one member must be set.
Property | Type | Description |
---|---|---|
contents | string |
Defines the expected serialized response body, which will be matched exactly. |
messageRegex | string |
A regex to evaluate against the message field in the response body.
For responses that may have some variance from platform to platform,
such as those that include messages from a parser. |
HTTP malformed request example#
The following example defines a malformed request test for a JSON protocol
that uses HTTP binding traits. In this example, the server
is rejecting many different variants of invalid numerics, and uses
testParameters
to test three different invalid values, and tags each test
with a descriptive string that allows implementations to run, or skip,
specific types of malformed values.
$version: "2"
namespace smithy.example
use smithy.test#httpMalformedRequestTests
@http(method: "POST", uri: "/InvertNumber/{numberValue}")
@httpMalformedRequestTests([
{
id: "MalformedLongsInPathsRejected",
documentation: """
Malformed values in the path should be rejected""",
protocol: exampleProtocol,
request: {
method: "POST",
uri: "/InvertNumber/$value:L"
},
response: {
code: 400,
headers: {
"errorType": "BadNumeric"
},
body: {
assertion: {
contents: """
{"errorMessage": "Invalid value \"$value:L\""}"""
},
mediaType: "application/json"
}
},
testParameters : {
"value" : ["true", "1.001", "2ABC"],
"tag" : ["boolean_coercion", "float_truncation", "trailing_chars"]
},
tags: [ "$tag:L" ]
}
])
operation InvertNumber {
input: InvertNumberInput
}
structure InvertNumberInput {
@httpLabel
@required
numberValue: Long
}