-
Notifications
You must be signed in to change notification settings - Fork 6
HTTP Protocol Compliance Tests
This wiki contains a mapping between Smithy HTTP Protocol Compliance Tests and generated Ruby code.
The httpRequestTests
trait is used to define how an HTTP request is serialized given a specific protocol, authentication scheme, and set of input parameters.
To handle these tests, middleware containing RSpec expectations is inserted before Send
. The client is stubbed to prevent network calls. A successful test will call the operation and not raise an error.
client = Client.new(stub_responses: true)
middleware = Hearth::MiddlewareBuilder.before_send do |input, context|
request = context.request
request_uri = URI.parse(context.request.url)
# expectations
# ...
end
client.get_high_score(
{id: '1'},
middleware: middleware
)
See Smithy Documentation for more details.
Each section is listed below, assuming that the value comes from a test
object.
The identifier of the test case. This becomes the RSpec test name.
it "#{test.id}" do
...
end
A shape ID that targets a shape marked with the @protocolDefinition
trait. This is used to filter protocol tests to the protocol being generated.
The expected serialized HTTP request method. The RSpec expectation is:
expect(request.http_method).to eq(test.method)
The request-target of the HTTP request, not including the query string (for example, "/foo/bar"). The RSpec expectation is:
expect(request.uri.path).to eq(test.uri)
The host / endpoint provided to the client (for example, “example.com”). This value is passed to the Client:
options[:endpoint] = test.host
The host / endpoint that the client should send to, not including the path or scheme (for example, "prefix.example.com"). The RSpec expectation is:
expect(request.uri.host).to eq(test.resolvedHost)
A shape ID that specifies the optional authentication scheme to assume. This does not influence code generation right now.
A list of the expected serialized query string parameters. The list should be joined by &
and parsed by CGI
to output a hash. The actual query string is also parsed by CGI
. Then, for each value in the expected hash, check if it is the same as the actual hash for the same key. The Rspec expectation is:
expected_query = ::CGI.parse(['foo=bar', 'hello'].join('&'))
actual_query = ::CGI.parse(request.uri.query)
expected_query.each do |k, v|
expect(actual_query[k]).to eq(v)
end
A list of query string parameter names that must not appear in the serialized HTTP request. Using CGI
parsed query params, check if the key is a forbidden query param. The RSpec expectation is:
forbid_query = test.forbid_query_params
actual_query = ::CGI.parse(request_uri.query)
forbid_query.each do |query|
expect(actual_query.key?(query)).to be false
end
A list of query string parameter names that MUST appear in the serialized request URI, but no assertion is made on the value. Using CGI
parsed query params, check if the required params are keys in the query params. The RSpec expectation is:
require_query = test.require_query_params
actual_query = ::CGI.parse(request_uri.query)
require_query.each do |query|
expect(actual_query.key?(query)).to be true
end
A map of expected HTTP Headers - each key is a header field. The request headers hash is checked to include the test headers hash. The Rspec expectation is:
{ *test.headers }.each { |k, v| expect(request.headers[k]).to eq(v) }
A list of headers that must not appear in the request. The request header keys are checked to not include the forbidden headers. The Rspec expectation is:
[*test.headers].each { |k| expect(request.headers.key?(k)).to be(false) }
A list of headers that must appear in the request. Values are not checked. The request headers keys are checked to include the required headers. The RSpec expectation is:
[*test.headers].each { |k| expect(request.headers.key?(k)).to be(true) }
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"). The bodyMediaType
is used to determine the comparison.
The media type of the body
. This is used to help parse and validate the expected data against generated data.
For plain text (or default), the bodies can be compared using string equality:
expect(request.body.read).to eq(test.body)
For application/json
the bodies are parsed as json and compared:
expect(JSON.parse(request.body.read)).to eq(JSON.parse(<test body>))
For application/xml
the bodies are parsed as xml and the Hearth
match_xml_node
matcher is used:
expect(Hearth::XML.parse(request.body.read)).to match_xml_node(Hearth::XML.parse(<test body>))
For application/x-www-form-urlencoded
(Used in AWS Query Protocol), bodies are parsed using CGI.parse
and compared using the match_query_params
:
expect(CGI.parse(request.body.read)).to match_query_params(CGI.parse(<test body>))
Defines the input parameters used to generate the HTTP request. These parameters MUST be compatible with the input of the operation. These values are passed into the Client’s operation:
client.get_high_score(*test.params)
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.
This does not need to influence initial protocol test generation, but can be used to extend it in the future for more complex tests (eg validating behavior of configuration/env).
A shape to be used to validate the vendorParams
member contents. Not used in the initial protocol test generation.
A description of the test and what is being asserted defined in CommonMark. The documentation is added to the RSpec test:
# #{test.documentation}
it "#{test.id}" do
...
end
Attaches a list of tags that allow test cases to be categorized and grouped. Does not influence code generation.
Indicates that the test case is only to be implemented by "client" or "server" implementations. If appliesTo
is server
then test generation is skipped.
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. However - in the context of client testing - httpResponseTests
define deserialization tests: given some http request, does the response/error match the expected. The Send
and Build
middleware are removed, and a new middleware is provided that sets test values on the response object.
client = Client.new(stub_responses: true)
middleware = Hearth::MiddlewareBuilder.around_send do |app, input, context|
response = context.response
# status, body, and headers are overriden here
# skip the app.call to skip real send
Hearth::Output.new
end
middleware
.remove_send
.remove_build
output = client.get_high_score({}, middleware: middleware)
See Smithy Documentation for more details.
Each section is listed below, assuming that the value comes from a test
object.
The identifier of the test case. This will become the RSpec test name.
it "#{test.id}" do
...
end
A shape ID that targets a shape marked with the @protocolDefinition
trait. This is used to filter protocol tests to the protocol being generated.
The expected HTTP response status code. The replacement middleware contains:
response.status = test.code
A shape ID that specifies the optional authentication scheme to assume. This does not influence code generation right now.
A map of expected HTTP headers. Each key represents a header field name and each value represents the expected header value. The replacement middleware contains:
response.headers = test.headers
A list of header field names that must not appear in the serialized HTTP response. Does not influence client code generation.
A list of header field names that must appear in the serialized HTTP response, but no assertion is made on the value. Does not influence client code generation.
The expected HTTP message body. If no response body is defined, then no assertions are made about the body of the message. The replacement middleware contains:
response.body = StringIO.new(test.body)
The media type of the body. This is used to help parse and validate the expected data against generated data. Does not influence client code generation.
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. These are used to validate the returned operation data. The test params
will be code generated as a Ruby hash, and compared against output.to_h
.
expected = { } # code generate test.params as Ruby hash
expect(output.to_h).to include(expected)
Defines vendor-specific parameters that are used to influence the response. Does not influence client code generation.
A shape to be used to validate the vendorParams
member contents. Does not influence client code generation.
A description of the test and what is being asserted defined in CommonMark. The documentation is added to the RSpec test:
# #{test.documentation}
it "#{test.id}" do
...
end
Attaches a list of tags that allow test cases to be categorized and grouped. Does not influence code generation.
Indicates that the test case is only to be implemented by "client" or "server" implementations. If appliesTo
is server
then test generation is skipped.
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 Send and Build middleware are removed, and a new middleware is provided that sets test values on the response object. The expected Error class is rescued and assertions are added.
middleware = Hearth::MiddlewareBuilder.around_send do |app, input, context|
response = context.response
# status, body, and headers are overridden here
# skip the app.call to skip real send
Hearth::Output.new
end
middleware.remove_send.remove_build
begin
client.greeting_with_errors({}, middleware: middleware)
rescue Errors::ExpectedError => e
# expect values on e to match test params
end
The test trait values (id, protocol, code, ect) are handled identically to those described in the httpResponseTests
section above.
This trait is intended for servers to validate requests and it does not influence client code generation.
See Smithy Documentation for more details.