DEV: Output webmock errors in request specs (PR #14782)

In request specs, if you had not properly mocked an external HTTP call, you would end up with a 500 error with no further information instead of your expected response code, with an rspec output like this:

Failures:

  1) UploadsController#generate_presigned_put when the store is external generates a presigned URL and creates an external upload stub
     Failure/Error: expect(response.status).to eq(200)

       expected: 200
            got: 500

       (compared using ==)
     # ./spec/requests/uploads_controller_spec.rb:727:in `block (4 levels) in <top (required)>'
     # ./spec/rails_helper.rb:280:in `block (2 levels) in <top (required)>'

This is not helpful at all when you want to find what you actually failed to mock, which is shown straight away in non-request specs.

This commit introduces a rescue_from block in the application controller to log this error, so we have a much nicer output that helps the developer find the issue:

Failures:

  1) UploadsController#generate_presigned_put when the store is external generates a presigned URL and creates an external upload stub
     Failure/Error: expect(response.status).to eq(200)

       expected: 200
            got: 500

       (compared using ==)
     # ./spec/requests/uploads_controller_spec.rb:727:in `block (4 levels) in <top (required)>'
     # ./spec/rails_helper.rb:280:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # WebMock::NetConnectNotAllowedError:
     #   Real HTTP connections are disabled. Unregistered request: GET https://s3-upload-bucket.s3.us-west-1.amazonaws.com/?cors with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'', 'Authorization'=>'AWS4-HMAC-SHA256 Credential=some key/20211101/us-west-1/s3/aws4_request, SignedHeaders=host;user-agent;x-amz-content-sha256;x-amz-date, Signature=test', 'Host'=>'s3-upload-bucket.s3.us-west-1.amazonaws.com', 'User-Agent'=>'aws-sdk-ruby3/3.121.2 ruby/2.7.1 x86_64-linux aws-sdk-s3/1.96.1', 'X-Amz-Content-Sha256'=>'test', 'X-Amz-Date'=>'20211101T035113Z'}
     #
     #   You can stub this request with the following snippet:
     #
     #   stub_request(:get, "https://s3-upload-bucket.s3.us-west-1.amazonaws.com/?cors").
     #     with(
     #       headers: {
     #   	  'Accept'=>'*/*',
     #   	  'Accept-Encoding'=>'',
     #   	  'Authorization'=>'AWS4-HMAC-SHA256 Credential=some key/20211101/us-west-1/s3/aws4_request, SignedHeaders=host;user-agent;x-amz-content-sha256;x-amz-date, Signature=test',
     #   	  'Host'=>'s3-upload-bucket.s3.us-west-1.amazonaws.com',
     #   	  'User-Agent'=>'aws-sdk-ruby3/3.121.2 ruby/2.7.1 x86_64-linux aws-sdk-s3/1.96.1',
     #   	  'X-Amz-Content-Sha256'=>'test',
     #   	  'X-Amz-Date'=>'20211101T035113Z'
     #       }).
     #     to_return(status: 200, body: "", headers: {})
     #
     #   registered request stubs:
     #
     #   stub_request(:head, "https://s3-upload-bucket.s3.us-west-1.amazonaws.com/")
     #
     #   ============================================================

GitHub

We have this magic in rails_helper,

Can you see why the error isn’t being rescued?

RspecErrorTracker is empty here:

From further inspection the webmock error is not captured by RspecErrorTracker:

I guess this doesn’t matter in normal specs because the webmock error is raised directly to rspec, whereas during the request specs it’s going through all the rails middleware?

FWIW I haven’t ever seen this work properly for request specs.

Hmm it seems like the webmock error is being rescued and caught somewhere which we do not want in the request spec.

Hmm used pry to follow the call stack in steps from where the webmock error is called in the gem (raise WebMock::NetConnectNotAllowedError.new(request_signature)) and I get up to here and the error is still present, but then when RspecErrorTracker rescues the exception it has disappeared :S

Ah I found it, it’s caused by this discourse/test.rb at a1c96b52f441a1df73edbc5c55705109ad8318c3 · discourse/discourse · GitHub. Need to conditionally turn this off for request specs I think…

ActionDispatch relies on a middleware as well. Maybe try to insert our middleware after it?

NetConnectNotAllowedError does not inherit from StandardError, that’s why this wasn’t working :slight_smile: