0

I'm using Webmock to test http requests made by the ruby aws-sdk (in this case aws-sdk-batch).

Webmock handles json requests with a much more convenient hash diff and partial matching when the requests fail, but it will only do so if the Content-Type of the stubbed request is application/json.

However the aws-sdk-batch gem creates a request with an empty Content-type (despite having documented the content type as application/json) and appears to rely on the default behavior of the endpoint to interpret this as application/json.

I would like to manually add the header Content-type: application/json, so that I can benefit from Webmock's better handling of json request body. Is this possible?


Example Webmock response without specifying Content-Type:application/json:

     WebMock::NetConnectNotAllowedError:
       Real HTTP connections are disabled. Unregistered request: POST https://batch.us-east-1.amazonaws.com/v1/submitjob with body '{"jobName":"Fakie","jobQueue":"queue","jobDefinition":"def","parameters":{"task":"{\"fake\":\"town\"}"}}' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'', 'Authorization'=>'AWS4-HMAC-SHA256 Credential=ACCESS_KEY_ID/20190701/us-east-1/batch/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=5f4bf85ba48333e6cda6ff613b4ea2faacd0417b4136621c58b87a488c3019ee', 'Content-Length'=>'106', 'Content-Type'=>'', 'Host'=>'batch.us-east-1.amazonaws.com', 'User-Agent'=>'aws-sdk-ruby3/3.54.2 ruby/2.5.5 x86_64-darwin18 aws-sdk-batch/1.20.0', 'X-Amz-Content-Sha256'=>'cf52595364d1a588b4ca4fdeaddb8170e4ad944fa28ac6df647484bb596de9c4', 'X-Amz-Date'=>'20190701T215756Z'}

       You can stub this request with the following snippet:

       stub_request(:post, "https://batch.us-east-1.amazonaws.com/v1/submitjob").
         with(
           body: "{\"jobName\":\"Fakie\",\"jobQueue\":\"quue\",\"jobDefinition\":\"def\",\"parameters\":{\"task\":\"{\\\"fake\\\":\\\"town\\\"}\"}}",
           headers: {
          'Accept'=>'*/*',
          'Accept-Encoding'=>'',
          'Authorization'=>'AWS4-HMAC-SHA256 Credential=ACCESS_KEY_ID/20190701/us-east-1/batch/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=5f4bf85ba48333e6cda6ff613b4ea2faacd0417b4136621c58b87a488c3019ee',
          'Content-Length'=>'106',
          'Content-Type'=>'',
          'Host'=>'batch.us-east-1.amazonaws.com',
          'User-Agent'=>'aws-sdk-ruby3/3.54.2 ruby/2.5.5 x86_64-darwin18 aws-sdk-batch/1.20.0',
          'X-Amz-Content-Sha256'=>'cf52595364d1a588b4ca4fdeaddb8170e4ad944fa28ac6df647484bb596de9c4',
          'X-Amz-Date'=>'20190701T215756Z'
           }).
         to_return(status: 200, body: "", headers: {})

       registered request stubs:

       stub_request(:post, "https://batch.us-east-1.amazonaws.com/v1/submitjob").
         with(
           body: {"jobDefinition"=>"def", "jobName"=>"Wrong", "jobQueue"=>"queue", "parameters"=>{"task"=>"{\"fake\":\"town\"}"}})

As you can see when these tests fail for strings you're left with the agonizing task of picking through the body character by character which is a pretty horrible developer experience. The Hashdiff experience is FAR superior.

Jacob Dalton
  • 1,643
  • 14
  • 23
  • Just as a remark you can try [`vcr gem`](https://github.com/vcr/vcr) which gives you more convenient way of mocking http requests in tests – Martin Jul 01 '19 at 21:47
  • @MartinZinovsky I'm not a fan of VCR. I worked on a large Rails project that mocked hundreds of requests with VCR, and I found them to be very brittle. I much prefer to set an explicit mock that's defined in the test. When recording requests with VCR you also pick things that are not relevant (consider caching headers or tokens that expire) to what you're testing (also adding to the brittleness). Also in this case VCR would not help me -- the AWS SDK (specifically Seahorse) is not setting the `Content-Type` so even if I recorded the request, I'd still have an ugly diff on failure. – Jacob Dalton Jul 01 '19 at 22:02

1 Answers1

0

The base http client of the AWS SDK is called Seahorse and allows you to add handlers that change the behavior of the request chain.

A handler that can add the header Content-Type:application/json looks like the following:

  class ContentType < Seahorse::Client::Plugin
    class Handler < Seahorse::Client::Handler
      def call(context)
        context.http_request.headers['Content-Type'] = 'application/json'
        @handler.call(context)
      end
    end
    handler(Handler, step: :sign, priority: 0)
  end

This can then be added to an AWS SDK service that inherits from Seahorse:

Aws::Batch::Client.add_plugin(ContentType)

Now Webmock will respond to a failure like this:

...
       registered request stubs:

       stub_request(:post, "https://batch.us-east-1.amazonaws.com/v1/submitjob").
         with(
           body: {"jobDefinition"=>"def", "jobName"=>"Wrong", "jobQueue"=>"queueName", "parameters"=>{"task"=>"{\"fake\":\"town\"}"}})

       Body diff:
        [["~", "jobName", "Fakie", "Wrong"]]
Jacob Dalton
  • 1,643
  • 14
  • 23