I am writin specs for my gem and I am using webmock to mock http requests. But i keep on getting this weird error.
Here is my specs code
require 'spec_helper'
describe 'Generator::Exotel' do
describe '#success' do
let(:resps) { {"Status"=>"200", "Message"=>"Success"} }
before do
stub_request(:post, "https://test_sid:test_token#twilix.exotel.in/v1/Accounts/#{Generator::configuration.sid}/Sms/send").
with(:body => {:To => 1234, :Body => "test sms"}, :headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
to_return(:body => resps.to_json, :headers => {})
end
it 'returns response object for success' do
response = Generator::Exotel.send(:to => 1234, :body => "test sms")
expect(response.to_json).to eq (resps.to_json)
end
end
describe '#failure' do
let(:resp) { {"Status"=>"401", "Message"=>"Not Authenticated"} }
before do
stub_request(:post, "https://test_sid:test_token#twilix.exotel.in/v1/Accounts/#{Generator::configuration.sid}/Sms/send").
with(:body => {:To => 1234, :Body => "test sms"}, :headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
to_return(:body=> resp.to_json, :headers => {})
end
it 'returns response object for failure' do
response = Generator::Exotel.send(:to => 1234, :body => "test sms")
expect(response.to_json).to eq (resp.to_json)
end
end
end
Whenever i run rspec, i am getting this following error
Generator::Exotel #success returns response object for success
Failure/Error: response = self.class.post("/#{Generator::configuration.sid}/Sms/send", {:body => params, :basic_auth => auth })
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: POST https://twilix.exotel.in/v1/Accounts/test_sid/Sms/send with body 'To=1234&Body=test%20sms' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Basic dGVzdF9zaWQ6dGVzdF90b2tlbg==', 'User-Agent'=>'Ruby'}
You can stub this request with the following snippet:
stub_request(:post, "https://twilix.exotel.in/v1/Accounts/test_sid/Sms/send").
with(:body => "To=1234&Body=test%20sms",
:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Basic dGVzdF9zaWQ6dGVzdF90b2tlbg==', 'User-Agent'=>'Ruby'}).
to_return(:status => 200, :body => "", :headers => {})
registered request stubs:
stub_request(:post, "https://test_sid:test_token#twilix.exotel.in/v1/Accounts/test_sid/Sms/send").
with(:body => {"Body"=>"test sms", "To"=>1234},
:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'})
============================================================
I have googled a lot and found some solutions, i.e,
Solution 1
Relish Documentation
“WebMock::NetConnectNotAllowedError”
Also i had a look at this post , But in vain.
Also i tried using WebMock.disable_net_connect!(:allow_localhost => true)
but got the same result. Anyone know what am i doing wrong? I am really new to ruby and for first time i am writing specs and its really confusing me.
You cannot make external http requests with webmock, as rightly said by various posts that you mentioned. I notice your stubbed urls have interpolated variables in them ie "https://test_sid:test_token#twilix.exotel.in/v1/Accounts/#{Generator::configuration.sid}/Sms/send". You need to:
Make sure Generator::configuration.sid is accessible within the specs
If it's not accessible then just return a plain url which is perfectly fine
Related
Controller: payments_controller.rb
class PaymentsController < ApplicationController
# This is needed to have Postman work
skip_before_action :verify_authenticity_token
rescue_from ActiveRecord::RecordNotFound do |exception|
render json: 'not_found', status: :not_found
def create
new_payment = Payment.new(new_params)
current_loan = Loan.find(new_params[:loan_id])
if Payment.valid?(new_payment, current_loan)
Payment.received(new_payment, current_loan)
current_loan.save
new_payment.save
redirect_to '/loans'
else
raise 'Amount entered is above the remaining balance'
end
end
end
This method works when I test it in Postman. However, I can't seem to write a test for it that passes. I currently have:
payments_controller_spec.rb
require 'rails_helper'
RSpec.describe PaymentsController, type: :controller do
describe "#create", :type => :request do
let!(:loan) {Loan.create!(id: 1, funded_amount: 500.0)}
params = '{"payment":{"amount":400, "loan_id":2}}'
it 'creates and saves a payment while saving the associated fund_amount of the loan' do
post "/payments", params.to_json, {'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
expect(loan.funded_amount).to eql(600.0)
end
end
end
The error is:
Failure/Error: post "/payments", params.to_json, {'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
ActionController::ParameterMissing:
param is missing or the value is empty: payment
Valid parameters (that work with Postman) are:
{"payment":{"amount":400,"loan_id":2}}
Any help would be appreciated!
*** UPDATE ****
After messing around with this for a while, I finally got it to work with this:
describe "#create", :type => :request do
let!(:loan) {Loan.create!(id: 1, funded_amount: 500.0)}
it 'creates and saves a payment while saving the associated fund_amount of the loan' do
json = { :format => 'json', :payment => { :amount => 200.0, :loan_id => 1 } }
post '/payments', json
loan.reload
expect(loan.funded_amount).to eql(300.0)
end
end
You can pass in your params like this.
it 'creates and saves a payment while saving the associated fund_amount of the loan' do
post "/payments", payment: { amount: 400, loan_id: 1 }, {'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
expect(loan.funded_amount).to eql(600.0)
end
I made a post request with RestClient::Request.execute, which works, but sometimes it ended with a 422(Unprocessable Entity).
Afterwards I tried out RestClient.post which didn´t gave me the 422 and worked all the time like a charm.
What is the difference between the two Calls?
I know that with RestClient::Request there are more possibilities for using parameters than with RestClient.post. I do not understand why i get a 422 with one method and not with the other.
Here I used json:
response = RestClient::Request.execute(
:method => :post,
:url => 'http://localhost:3000',
:timeout => 30,
:open_timeout => 2,
:payload => payload.to_json,
:headers => {
:content_type => :json,
:accept => :json
}
)
vs.
response = RestClient.post('http://localhost:3000',
:param1 => 'abc',
:param2 => "def")
They are the same - RestClient.post is a syntax sugar for execute, see restclient's sources, restclient.rb:
def self.post(url, payload, headers={}, &block)
Request.execute(:method => :post,
:url => url,
:payload => payload,
:headers => headers, &block)
end
The 422 is caused by something else
I can't stub request to Flickr API for my
controller test. I use gem 'flickraw' for getting data from Flickr API.
flickr_search_controller.rb:
module Dashboard
class FlickrSearchController < Dashboard::BaseController
respond_to :js
def search
#search_tag = params[:search]
photos_list = if #search_tag.blank?
flickr.photos.getRecent(per_page: 10)
else
flickr.photos.search(text: #search_tag, per_page: 10)
end
#photos = photos_list.map { |photo| FlickRaw.url_q(photo) }
end
end
end
flickr_search_controller_spec.rb:
require 'rails_helper'
describe Dashboard::FlickrSearchController do
let(:user) { FactoryGirl.create(:user) }
before(:each) do
stub_request(:post, "https://api.flickr.com/services/rest").to_return(status: 200)
#controller.send(:auto_login, user)
end
describe 'when user didn\'t set search tag' do
it 'returns recend photo'do
get :search, search: ' '
expect(response.status).to eq(200)
end
end
end
I get in console next error:
Failures:
1) Dashboard::FlickrSearchController when user didn't set search tag returns recend photo
Failure/Error: flickr.photos.getRecent(per_page: 10)
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: POST https://api.flickr.com/services/rest/ with body 'method=flickr.reflection.getMethods&format=json&nojsoncallback=1' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'OAuth realm="https://api.flickr.com/services/rest/", oauth_consumer_key="32904448e7d40c7e833c7b381c86cd31", oauth_nonce="lCL%2FUM9o8go5XNVy4F7p%2FNxHJrY%2BvFNLhlzueFq8Juc%3D", oauth_signature="1b77fc6af54b2b51%26", oauth_signature_method="PLAINTEXT", oauth_timestamp="1455128674", oauth_token="", oauth_version="1.0"', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'FlickRaw/0.9.8'}
You can stub this request with the following snippet:
stub_request(:post, "https://api.flickr.com/services/rest/").
with(:body => {"format"=>"json", "method"=>"flickr.reflection.getMethods", "nojsoncallback"=>"1"},
:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'OAuth realm="https://api.flickr.com/services/rest/", oauth_consumer_key="32904448e7d40c7e833c7b381c86cd31", oauth_nonce="lCL%2FUM9o8go5XNVy4F7p%2FNxHJrY%2BvFNLhlzueFq8Juc%3D", oauth_signature="1b77fc6af54b2b51%26", oauth_signature_method="PLAINTEXT", oauth_timestamp="1455128674", oauth_token="", oauth_version="1.0"', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'FlickRaw/0.9.8'}).
to_return(:status => 200, :body => "", :headers => {})
============================================================
Does somebody have idea how can I stub this request?
Note the slash in uri, the request you're stubbing is not the one being made
In order to ensure that my application is not vulnerable to this exploit, I am trying to create a controller test in RSpec to cover it. In order to do so, I need to be able to post raw JSON, but I haven't seemed to find a way to do that. In doing some research, I've determined that there at least used to be a way to do so using the RAW_POST_DATA header, but this doesn't seem to work anymore:
it "should not be exploitable by using an integer token value" do
request.env["CONTENT_TYPE"] = "application/json"
request.env["RAW_POST_DATA"] = { token: 0 }.to_json
post :reset_password
end
When I look at the params hash, token is not set at all, and it just contains { "controller" => "user", "action" => "reset_password" }. I get the same results when trying to use XML, or even when trying to just use regular post data, in all cases, it seems to not set it period.
I know that with the recent Rails vulnerabilities, the way parameters are hashed was changed, but is there still a way to post raw data through RSpec? Can I somehow directly use Rack::Test::Methods?
As far as I have been able to tell, sending raw POST data is no longer possible within a controller spec. However, it can be done pretty easily in a request spec:
describe "Example", :type => :request do
params = { token: 0 }
post "/user/reset_password", params.to_json, { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
#=> params contains { "controller" => "user", "action" => "reset_password", "token" => 0 }
end
This is the way to send raw JSON to a controller action (Rails 3+):
Let's say we have a route like this:
post "/users/:username/posts" => "posts#create"
And let's say you expect the body to be a json that you read by doing:
JSON.parse(request.body.read)
Then your test will look like this:
it "should create a post from a json body" do
json_payload = '{"message": "My opinion is very important"}'
post :create, json_payload, {format: 'json', username: "larry" }
end
{format: 'json'} is the magic that makes it happen. Additionally, if we look at the source for TestCase#post http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-process you can see that it takes the first argument after the action (json_payload) and if it is a string it sets that as raw post body, and parses the rest of the args as normal.
It's also important to point out that rspec is simply a DSL on top of the Rails testing architecture. The post method above is the ActionController::TestCase#post and not some rspec invention.
What we've done in our controller tests is explicitly set the RAW_POST_DATA:
before do
#request.env['RAW_POST_DATA'] = payload.to_json
post :my_action
end
Rails 5 example:
RSpec.describe "Sessions responds to JSON", :type => :request do
scenario 'with correct authentication' do
params = {id: 1, format: :json}
post "/users/sign_in", params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
expect(response.header['Content-Type']).to include 'application/json'
end
end
Here is a full working example of a controller test sending raw json data:
describe UsersController, :type => :controller do
describe "#update" do
context 'when resource is found' do
before(:each) do
#user = FactoryGirl.create(:user)
end
it 'updates the resource with valid data' do
#request.headers['Content-Type'] = 'application/vnd.api+json'
old_email = #user.email
new_email = Faker::Internet.email
jsondata =
{
"data" => {
"type" => "users",
"id" => #user.id,
"attributes" => {
"email" => new_email
}
}
}
patch :update, jsondata.to_json, jsondata.merge({:id => old_id})
expect(response.status).to eq(200)
json_response = JSON.parse(response.body)
expect(json_response['data']['id']).to eq(#user.id)
expect(json_response['data']['attributes']['email']).to eq(new_email)
end
end
end
end
The important parts are:
#request.headers['Content-Type'] = 'application/vnd.api+json'
and
patch :update, jsondata.to_json, jsondata.merge({:id => old_id})
The first makes sure that the content type is correctly set for your request, this is pretty straightforward.
The second part was giving me headaches for a few hours, my initial approach was quite a bit different, but it turned out that there is a Rails bug, which prevents us from sending raw post data in functional tests (but allows us in integration tests), and this is an ugly workaround, but it works (on rails 4.1.8 and rspec-rails 3.0.0).
On Rails 4:
params = { shop: { shop_id: new_subscrip.shop.id } }
post api_v1_shop_stats_path, params.to_json, { 'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json' }
A slight alternative to #daniel-vandersluis answer, on rails 3.0.6, with rspec 2.99 and rspec-rails 2.99:
describe "Example", :type => :request do
params = { token: 0 }
post "/user/reset_password", params.merge({format: 'json'}).to_json, { 'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json' }
end
The HTTP_ACCEPT header didn't make much difference, (it can be either HTTP_ACCEPT or just ACCEPT). But in my case, for it to work, the params had to: have the .merge({format: 'json'}) and .to_json
Another variation:
describe "Example", :type => :request do
params = { token: 0 }
post "/user/reset_password", params.merge({format: 'json'}).to_json, { 'CONTENT_TYPE' => Mime::JSON.to_s, 'HTTP_ACCEPT' => Mime::JSON }
end
It uses Mime::JSON and Mime::JSON.to_s instead of application/json for the header values.
I have been using a RestClient request as such:
response = RestClient.post server_url, post_params, accept: :json
Which has been working fine. But I need to increase the timeout as it's not completing every now and then while the server is performing the upload.
I have researched and found that the only solution is to change the syntax to something like:
response = RestClient::Request.execute(:method => :post, :url => server_url, post_params, :timeout => 9000000000)
however, I don't seem to be able to pass the hashmap of parameters ('post_params') like i was able to in the previous call. how should I write the request so that 'post_params' is included. It's a complex hashmap, so i can't augment it or get rid of it.
Help is much appreciated.
The data you send is called a payload, so you need do specify it as payload:
response = RestClient::Request.execute(:method => :post, :url => server_url, :payload => post_params, :timeout => 9000000, :headers => {:accept => :json})
Also, you may want to use a shorter timeout, otherwise there is a chance you get a Errno::EINVAL: Invalid argument.
the data you send is in payload when we try to use rest_client.post or any method like get,put what rest_client do is
def self.post(url, payload, headers={}, &block)
Request.execute(:method => :post, :url => url, :payload => payload,
:headers => headers, &block)
end
so like we want to execute
response = RestClient.post api_url,
{:file => file, :multipart => true },
{ :params =>{:foo => 'foo'} #query params
so in the execute command will take take {:file => file, :multipart => true } as payload and { :params =>{:foo => 'foo' } } as header so
for passing all these you need
response= RestClient::Request.execute(:method => :post,
:url => api_url,
:payload => {:file => file, :multipart => true },
:headers => { :params =>{:foo => 'foo'}},
:timeout => 90000000)
this should do