Stubbed request not allowing http connections (Real HTTP connections are disabled.) - ruby-on-rails

I am trying to stub a POST request to an external API. The spec test does not replace the ENV variable with my fake one and goes to my local env (localhost:3000) in the stub request returning this error:
Failure/Error: response = http.request(request)
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: POST http://localhost:3000/Target with body '{"name":"Wayfarer"}' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}
You can stub this request with the following snippet:
stub_request(:post, "http://localhost:3000/Target").
with(
body: "{\"name\":\"Wayfarer\"}",
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'User-Agent'=>'Ruby'
}).
to_return(status: 200, body: "", headers: {})
My test is:
let!(:user) { create(:user, target: 'Wayfarer') }
before(:each) do
allow(ENV).to receive(:fetch).with('API_METADATA_URL').and_return('http://example.com')
end
describe '#create_target' do
context 'successful API request to create target' do
it 'sends data to API and sets response instance variables' do
target = user.target
stub_request(:post, 'http://example.com/Target').with(
headers: {
},
body: '{
"name": "Wayfarer"
}'
).to_return(
status: 200,
body: '{"id": "uuid",' \
'"name": "Wayfarer",' \
'"created_at": 00000,' \
'"updated_at": 00000}'
)
api_client = ApiClient.new
api_client.create_target(target)
expect(api_client.response_status).to eq(200)
expect(api_client.response_body).to eq(
{
'id' => 'uuid',
'name' => 'Wayfarer',
'created_at' => 00000,
'updated_at' => 00000
}
)
end
end
end
It doesn't even reach the test, instead it seems to run the ApiClient as is including using my local environment variable (as stated above).

I ended up needing a separate .env file for tests (.env.test.local) for the API_METADATA_URL for a url that did not throw the error.

Related

Mock GoogleAPI request

I am using the google-maps gem.
I am trying to mock/stub api requests unsuccessfully.
module GoogleMap
class Route
attr_accessor :start_location, :end_location
def initialize(start_location, end_location)
#start_location = start_location
#end_location = end_location
end
def driving_duration_in_seconds
route.duration.value
end
def driving_distance_in_meters
route.distance.value
end
def driving_distance_hash
return unless start_location && end_location
{ distance_in_meters: driving_distance_in_meters, duration_in_seconds: driving_duration_in_seconds }
end
private
def coordinates_as_strings(location)
"#{location.latitude},#{location.longitude}"
end
def route
#route ||= Google::Maps.route(coordinates_as_strings(start_location), coordinates_as_strings(end_location))
end
end
end
I need to stub:
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: GET https://maps.googleapis.com/maps/api/directions/json?destination=37.19687112,116.43791248&key=<apikey>&language=en&origin=2.15362819,-81.63712649 with headers {'Accept'=>'*/*', 'Date'=>'Sat, 12 Feb 2022 21:35:55 GMT', 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.7.2 (2020-10-01))'}
You can stub this request with the following snippet:
stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?destination=37.19687112,116.43791248&key=<apikey>&language=en&origin=2.15362819,-81.63712649").
with(
headers: {
'Accept'=>'*/*',
'Date'=>'Sat, 12 Feb 2022 21:35:55 GMT',
'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.7.2 (2020-10-01))'
}).
to_return(status: 200, body: "", headers: {})
If I try a most basic stub I get an error:
stub_request(:any, /maps.googleapis.com/).
to_return(status: 200, body: '', headers: {})
Google::Maps::InvalidResponseException:
unknown error: 783: unexpected token at ''
# .../gems/ruby-2.7.2/gems/google-maps-3.0.7/lib/google_maps/api.rb:64:in `rescue in response'
# .../gems/ruby-2.7.2/gems/google-maps-3.0.7/lib/google_maps/api.rb:60:in `response'
# .../.rvm/gems/ruby-2.7.2/gems/google-maps-3.0.7/lib/google_maps/api.rb:27:in `query'
I think it is erroring out because I am not passing a key in. But I don't see why I should have to pass in a valid api key into a webmock.
I also would not have my route defined by anything. And in order to test that route can return route.distance.value etc, I would need to mock with something.
For other tests I was successful in mocking instances, but to test this lib that it actually works, I feel like mocking instance methods and not that an api was actually called is a waste of a test. Maybe this is just a waste of time, and I should assume it works because I am using a gem.
But I was expecting something like this:
RSpec.describe GoogleMap do
let(:start_location) { create(:location) }
let(:end_location) { create(:location) }
context 'GoogleMaps::Route.new(start_location, end_location)' do
let(:subject) { GoogleMap::Route.new(start_location, end_location) }
# I have not been successful in stubbing this with the correct regex
# stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?destination=<lat>,<long>key=<key>&language=en&origin=<lat>,<long>").
# with(
# headers: {
# 'Accept'=>'*/*',
# 'Date'=>'Thu, 10 Feb 2022 21:09:02 GMT',
# 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.7.2 (2020-10-01))'
# }).
# to_return(status: 200, body: "", headers: {})
# stub_request(:get, %r{https:\/\/maps\.googleapis\.com\/maps\/api\/directions\/json\?destination=.+,.+&key=.+&language=en&origin=.+,.+}).
# stub_request(:any, /maps.googleapis.com/).
# to_return(status: 200, body: '', headers: {})
xit 'gives driving distance in seconds'
xit 'gives driving duration in meters'
end
end
Your WebMock is working fine. Google::Maps::InvalidResponseException is raised after WebMock has replaced the network call. At the point that exception is raised, the Google Maps API client is trying to parse what the network call returned, which is ''.
It's expecting some valid JSON to be returned. If you have your mock return {} is should get past that line. It may well stumble on some other exception later though, as the gem expects a certain schema.
You can dig that out and add in a valid response if you wanted to continue down this path. However, I'd recommend not mocking the network request as that's an implementation detail of a third party piece of code which could change at any time - making your test fail. Instead, I would mock out Google::Maps.route to return what you need it to.

Webmock + HTTParty: stubbed request with real response inside

I have a test, where I test logging.
it "logs out correct messages" do
resp = stub_request(:post, "https://api.sandbox.ebay.com/identity/v1/oauth2/token").and_return(
headers: { "Content-Type" => "application/json" },
body: { access_token: "access_token_value" }.to_json,
)
user = create(:user)
create(:preference, user: user, key: "ebay_refresh_token", string_value: "refresh_token_value")
expect(Rails.logger).to receive(:info).with(/Access token requested for user##{user.id}/)
expect(Rails.logger).to receive(:info).with("Response for user##{user.id} access token request received: #{resp.response.inspect}.")
EbayService.new(user: user).pre_authorize
end
It fails with this error:
Logger received :info with unexpected arguments
expected: ("Response for user#15316 access token request received: #<WebMock::Response:0x00007fe19ec99078)
got: ("Response for user#15316 access token request received: #<HTTParty::Response:0x7fe19dbebac8)
I cannot understand why I keep getting HTTParty::Response instead of WebMock::Response and how can I fix it.
Real http connections are, of course, disabled.
Please, point me in the right direction to think.

Stub request when request body is different

Hi I am working on RoR project with ruby-2.5.0 and Rails 5. I am stubbing an http request with stub_request. This http request is basically a mailjet api request to send email.
So, I can not just reuse the snippet, rspec spits out to me, because body differs on every execution.
Stub request
stub_request(:post, "https://api.mailjet.com/v3/send").
with(
body: "{\"FromEmail\":\"abc#abc.ocm\",\"FromName\":\"abc\",\"To\":\"abc#abc.com\",\"Subject\":\"Forgot Password\",\"Text-part\":\"xV3SEtaoa1oyYYfYBt12pw\"}",
headers: {
'Accept'=>'application/json',
'Accept-Encoding'=>'deflate',
'Authorization'=>'Basic YmY0NzdhZmYyMmZlMGQwMzAxNzYzNDNkODUwZTY1MzE6YmRhNmZmYzQ0Y2ZmNGM3MmZkZGNkMjUwODA5YWJhZjM=',
'Content-Length'=>'143',
'Content-Type'=>'application/json',
'Host'=>'api.mailjet.com',
'User-Agent'=>'mailjet-api-v3-ruby/1.5.4'
}).
to_return(status: 200, body: "", headers: {})
Here Text-part is different for each user. I tried hash_including method as follows:-
stub_request(:post, "https://api.mailjet.com/v3/send").
with(
body: hash_including({"FromEmail": "abc#abc.com","FromName": "abc"}),
headers: {
'Accept'=>'application/json',
'Accept-Encoding'=>'deflate',
'Authorization'=>'Basic YmY0NzdhZmYyMmZlMGQwMzAxNzYzNDNkODUwZTY1MzE6YmRhNmZmYzQ0Y2ZmNGM3MmZkZGNkMjUwODA5YWJhZjM=',
'Content-Length'=>'143',
'Content-Type'=>'application/json',
'Host'=>'api.mailjet.com',
'User-Agent'=>'mailjet-api-v3-ruby/1.5.4'
}).
to_return(status: 200, body: "", headers: {})
But when i run test it throws an error:-
JSON::ParserError:
765 : unexpected token at ''
Please help me what is the right syntax to use hash_including method. Thanks in advance.

How to stub requests on domain or after method?

config.before(:each) do
stub_request(:post, "https://api.3rdpartysmsprovider.com/send.php?body=This%20is%20a%20test%20message&destination=60123456789&dlr='1'&output=json&password=0000000&reference=#{#text.sms_uid}&sender=silver&username=0000000").
to_return(:status => 200, :body => "01", :headers => {})
end
I am currently writing specs for a service class that sends an SMS and creates a log of it in our database. I'm trying to stub this request, however #text.sms_uid is a SecureRandom.urlsafe_base64 random code. Also I'm stubbing in config.before(:each).
Because of that, I can't specify the sms_uid in stub_request as the random sms_uid is generated after the stub is called. This causes the test to fail every time. Is there a way I can stub the request after it generates the code (in other words, after it goes through the specific method) or is there a way to stub all requests going through the domain "https://api.silverstreet.com"?
I see two options:
Stub SecureRandom.urlsafe_base64 to return a known string and use that known string when you stub_request:
config.before(:each) do
known_string = "known-string"
allow(SecureRandom).to receive(:known_string) { known_string }
stub_request(:post, "https://api.3rdpartysmsprovider.com/send.php?body=This%20is%20a%20test%20message&destination=60123456789&dlr='1'&output=json&password=0000000&reference=#{known_string}&sender=silver&username=0000000").
to_return(status: 200, body: "01", headers: {})
end
If SecureRandom.urlsafe_base64 is used in other places in your application, you'll need to stub it only in the specs where this request is generated.
Yes, you can stub any POST to that hostname
stub_request(:post, "api.3rdpartysmsprovider.com").
to_return(status: 200, body: "01", headers: {})
or even any request of any kind to that hostname
stub_request(:any, "api.3rdpartysmsprovider.com").
to_return(status: 200, body: "01", headers: {})
and webmock has a very large number of other ways to match requests.

Multiple HTTP requests matchable in one VCR cassette for rspec tests

I have a spec file with an expectation that a controller action will return success.
The POST api/v1/users/:id/features/block action in the controller calls two HTTP calls on an external API, the only difference being in the body.
I've put the two requests and responses in the same VCR cassette, but when the cassette is being used, only the first request ever gets compared against and fails when it should be matching the second, causing the tests to fail.
What I'm looking for is a way of having the multiple requests match so the controller action completes and returns successfully.
The error I'm getting is at the end.
describe "POST /api/v1/users/:id/features/block" do
before(:each) do
#user = FactoryGirl.create(:user)
post :block, user_id: #user.id, block: "0"
end
it "should return 200 OK" do
expect(response).to be_success
end
end
Simplified versions of my VCR configuration and RSpec configuration follow:
VCR.configure do |c|
c.hook_into :webmock
c.default_cassette_options = {
match_requests_on: [:method, :uri, :body_regex]
}
c.register_request_matcher :body_regex do |request_1, request_2|
# Match body against regex if cassette body is "--ruby_regex /regexhere/"
if request_2.body[/^--ruby_regex\s*\//]
regex = request_2.body.gsub(/^--ruby_regex\s*\//, '').gsub(/\/$/, '')
request_1.body[/#{regex}/] ? true : false
else
true # No regex defined, continue processing
end
end
end
RSpec.configure do |c|
c.around(:each) do |example|
options = example.metadata[:vcr] || {}
name = example.metadata[:full_description].split(/\s+/, 2).join("/").underscore.gsub(/[^\w\/]+/, "_")
VCR.use_cassette(name, options, &example)
end
end
end
A summarized version of the cassette being used in this comparison that I'm having trouble with is:
---
http_interactions:
- request:
method: post
uri: https://upstream/api
body:
string: --ruby_regex /query1.+block/
response:
status:
code: 200
body:
string: { "response": "SUCCESS" }
- request:
method: post
uri: https://upstream/api
body:
string: --ruby_regex /query2.+block/
response:
status:
code: 200
body:
string: { "response": "SUCCESS" }
recorded_at: Fri, 05 Sep 2014 08:26:12 GMT
recorded_with: VCR 2.8.0
Error during tests:
An HTTP request has been made that VCR does not know how to handle
...
VCR is using the current cassette: (Correct cassette file path)
...
Under the current configuration VCR can not find a suitable HTTP interaction to replay and is prevented from recording new requests.
I don't want to record new requests because then the second one overwrites the first instead of adding the second request to the end of the cassette.

Resources