How to mock requests made by the browser in a Rails app? - ruby-on-rails

WebMock works fine for requests made by the app. But how to mock AJAX requests made by the browser to 3rd party services?
Rails 4.

Finally found 2 answers:
Approach #1: Puffing Billy
A rewriting web proxy for testing interactions between your browser and external sites.
Works like WebMock:
proxy.stub('http://www.google.com/')
.and_return(:text => "I'm not Google!")
Approach #2: Rack Middleware
Although the previous approach can work, I discovered that it does not work with WebMock. So, if you want to mock your browser requests to external services, you can't mock your app requests.
The approach that worked for me is to run a separate Rack app and inject it into the middleware stack:
spec_helper.rb:
def test_app
Rack::Builder.new{
use ExternalServiceMock
run app
}.to_app
end
Capybara.app = test_app
ExternalServiceMock is a rack app that only responds to certain request paths.
For this particular app, all of the external service URI's were stored in configs, and I set them in the spec helper:
ENV[ 'EXTERNAL_SERVICE_URI' ] = 'http://localhost:3000'
This forces all external requests to be sent to the ExternalServiceMock.

So basically you only need to save the response from 3rd party services and stub the request, then you can test it! Also checkout VCR: https://github.com/vcr/vcr.

Related

Mocking login for remote services - Cucumber, Capybara, Rails

I have a Rails application that has changed from user authentication via devise to accessing a remote API for authentication. For the test suite, this requires that I mock the https request for authentication and return a valid response. We are using Cucumber and Capybara for testing and I am attempting to use the webmock Gem to mock the login response.
The login is initiated by a button click on a form (Capybara action 'click_button').
Unfortunately, although webmock appears to be installed correctly (I am able to make a Net::HTTP POST request and this is recognized by Webmock), the POST to the remote authorization facility is not being captured by webmock. I know that the form POST is making its way to the controller, because the stacktrace shows that the POST is being executed in the controller as it should and the error message is "Errno::ECONNREFUSED at ... Failed to open TCP connection".
I have tried:
WebMock.stub_request(:any, '127.0.0.1').to_return(body: {STATUSCODE: 1}.to_json)
and
WebMock::stub_request(:any, '127.0.0.1').to_return(body: {STATUSCODE: 1}.to_json)
and
stub_request(:any, '127.0.0.1').to_return(body: {STATUSCODE: 1}.to_json)
I have tried putting the stub_request call in the devise_steps.rb file, just before the "click_button" command, as well as in the features/support/env.rb and features/support/webmock.rb file.
I assume that what I am doing is so common that it has to be possible, but I have found nothing that indicates why the stub_request is not successful.
So the domain of the remote API for authentication is localhost? So it would be running on the same server with a different port? Then you have to mock the address with the port.
For instance your Rails app is running on port 80 and your auth API is running on 8080 then you have to do this.
stub_request(:any, '127.0.0.1:8080').to_return(body: {STATUSCODE: 1}.to_json)
I think Webmock should support different ports but I'm not 100% sure. Also have you set WebMock.disable_net_connect!(allow_localhost: true)?
https://github.com/bblimke/webmock#external-requests-can-be-disabled-while-allowing-localhost
Edit:
If this is not working, I suggest you make the API URL configurable and e.g. set it in tests to auth-api.com and then mock this.
The root cause of the problem was that the path on the stub_request was not complete. I had misread the webmock documentation....

AngularJS and Rails application security in AWS

I have an AngularJS front-end running on a Nginx server that sends requests to a Rails API backend running on a Puma application server. This application is running on an Amazon AWS EC2 instance.
The Rails API is listening on port 8081.
According to this architecture I had to open the HTTP port 8081 in AWS, so that I could receive the request from the front-end.
I have a domain, so It´s supposed all request should come from www.domain.com. However, I have noticed that if I use my EC2 instance name, such as, in example http://ec2-<ip>.eu-west-1.compute.amazonaws.com:8081/users the Rails API is serving all my users information.
How can I avoid this security bug. Where should I block this? In AWS configuration? In my Rails API CORS configuration? Any other place...
This seems an Authorization bug in your Rails API.
Who is the controller that answer to the route /users?
Let's say it is, for e.g., UsersController: in this case, you could have an action
def index
#users = User.all
end
or something similar, that returns the information you see.
Is difficult to give you a solution, without knowing if you need this action (maybe is just auto-generated boilerplate code) or if you want simply hiding it to who is not an Administrator...
Who wrote the Back End API should fix this for you based on your specifications.

How do I make webmock block/stub external requests from other gems?

I'm trying to use webmocks to test part of my app that interacts with an external service. However, all of the API calls are actually happening within another gem. So it continues to make the requests, even though I've enabled webmock.
This properly is blocked and requires a mock:
it 'test webmock' do
Net::HTTP.get("www.google.com", "/")
end
But the test which contains API requests against the gem, which should be calling the external service is not.
Any idea how to make that test also require a mock?

How to test a Rails HTTP request to a Sinatra app?

Consider a Rails app that hits a (Sinatra app) API being developed separately from the Rails app. I want to test an API call from within the Rails tests.
The API code:
post '/foo/create' do
...
I created a mock, but that doesn't make sense because it is just a copy of the API file. That stinks.
It is possible to require the API file in the test. But how to call it from RSpec? There is no route in the Rails app for it.
One option is to start the API and make the HTTP call from the Rails test, but this is smelly because:
You have to start the API server to run the Rails tests
Why should a Rails test make a HTTP request? Rack::Test simulates this.
I don't think this will work because the apps have different test databases, but share the same production database.
EDIT: The point of the test is that the API call creates records that the Rails app is expecting. So the Rails app needs to test the state of the database after the API call is made.
Well. The perfect answer for you is a gem to mock the answer like webmock. It will fake a response when acessing that url, so on the test your app will make the requisition as it was for real, only that before it hits the web, it will hit your mock and respond with the desired answer.

http request (using net http or RestClient) inside my rails controller

I have problem creating http request inside my controller action. I used net/http and RestClient but I can't get it to work on my local server url i.e http://localhost:3000/engine/do_process, I always get requesttimeout however It works with other valid url.
Hope you can enlighten me on this one. I did some research but I can't find resources as to why I got this timeout problem.
Sample controller code:
require 'rest_client'
class LgController < ApplicationController
def get_lgjson
response = RestClient.get("http://localhost:3000/engine/do_process_lg")
#generated_json = response.to_str
end
end
I encountered this problem today, too, exactly in the same context: using the Ruby RestClient to make a HTTP request inside a controller. It worked earlier in a different project using OpenURI without problems. This was surprising because both http libraries, the RestClient and OpenURI for Ruby, use the same library Net::HTTP.
It is the URL that makes the difference. We can make a connection to an external URL in the controller, but not to localhost. The problem seems to be the duplicated connection to localhost. There is already a connection to localhost open, and we are trying to open a second one. This does not seem to work in a single-threaded web server like Thin for instance. A multi-threaded web server such as Puma could help.
I think this is because you use single-threaded web server. You have two opportunities to fix.
use passenger
define if it makes sense to make net/http to localhost.

Resources