RSpec request spec post an empty array - ruby-on-rails

I am current developing an API endpoint in rails. I want to be sure the endpoint response with the correct error status if the data I need is invalid. I need an array of ids. One of the invalid values is an empty array.
Valid
{ vendor_district_ids: [2, 4, 5, 6]}
Invalid
{ vendor_district_ids: []}
Request Spec with RSpec
So I want to have a request spec to control my behaviour.
require 'rails_helper'
RSpec.describe Api::PossibleAppointmentCountsController, type: :request do
let(:api_auth_headers) do
{ 'Authorization' => 'Bearer this_is_a_test' }
end
describe 'POST /api/possible_appointments/counts' do
subject(:post_request) do
post api_my_controller_path,
params: { vendor_district_ids: [] },
headers: api_auth_headers
end
before { post_request }
it { expect(response.status).to eq 400 }
end
end
As you can see I use an empty array in my param inside the subject block.
Value inside the controller
In my controller I am fetching the data with
params.require(:vendor_district_ids)
and the value is the following
<ActionController::Parameters {"vendor_district_ids"=>[""], "controller"=>"api/my_controller", "action"=>"create"} permitted: false>
The value of vendor_district_ids is an array with an empty string. I do not have the same value when I make a post with postman.
Value with postman
If I post
{ "vendor_district_ids": [] }
the controller will receive
<ActionController::Parameters {"vendor_district_ids"=>[], "controller"=>"api/my_controller", "action"=>"create"} permitted: false>
And here is the array empty.
Question
Am I doing something wrong inside the request spec or is this a bug from RSpec?

Found the answer!
Problem
The problem is found inside Rack's query_parser not actually inside rack-test as the previous answer indicates.
The actual translation of "paramName[]=" into {"paramName":[""]} happens in Rack's query_parser.
An example of the problem:
post '/posts', { ids: [] }
{"ids"=>[""]} # By default, Rack::Test will use HTTP form encoding, as per docs: https://github.com/rack/rack-test/blob/master/README.md#examples
Solution
Convert your params into JSON, by requiring the JSON gem into your application using 'require 'json' and appending your param hash with .to_json.
And specifying in your RSPEC request that the content-type of this request is JSON.
An example by modifying the example above:
post '/posts', { ids: [] }.to_json, { "CONTENT_TYPE" => "application/json" }
{"ids"=>[]} # explicitly sending JSON will work nicely

That is actually caused by rack-test >= 0.7.0 [1].
It converts empty arrays to param[]= which is later decoded as [''].
If you try running the same code with e.g. rack-test 0.6.3 you will see that vendor_district_ids isn't added to the query at all:
# rack-test 0.6.3
Rack::Test::Utils.build_nested_query('a' => [])
# => ""
# rack-test >= 0.7.0
Rack::Test::Utils.build_nested_query('a' => [])
# => "a[]="
Rack::Utils.parse_nested_query('a[]=')
# => {"a"=>[""]}
[1] https://github.com/rack-test/rack-test/commit/ece681de8ffee9d0caff30e9b93f882cc58f14cb

For everyone wondering — there's a shortcut solution:
post '/posts', params: { ids: [] }, as: :json

Related

Stub Httparty call: Wrong number of arguments (given 2, expected 1)

I created a simple ruby file (not Rails) and I am trying to test (using Rspec) a method where I am calling an API. In the test I am trying to mock the call via WebMock but it keeps giving me this error:
Requests::FilesManager#display fetches the files from the API
Failure/Error: Requests::FilesManager.new.display
ArgumentError:
wrong number of arguments (given 2, expected 1)
The files are:
#run.rb
module Requests
require "httparty"
require 'json'
class FilesManager
include HTTParty
def initialize
end
def display
response = HTTParty.get('https://api.publicapis.org/entries', format: :json)
parsed_response = JSON.parse(response.body)
puts "The secret message was: #{parsed_response["message"]}"
end
end
end
and the spec file:
require 'spec_helper'
require_relative '../run'
RSpec.describe Requests::FilesManager do
describe "#display" do
it 'fetches the files from the API' do
stub_request(:get, "https://api.publicapis.org/entries").
to_return(status: 200, body: "", headers: {})
Requests::FilesManager.new.display
end
end
end
EDIT:
So the error seems to come from the line:
JSON.parse(response.body)
If I comment it out it disappears. The problem then is that the output of the call is not a json (even with the format: :json when calling the HTTParty). I tried other solutions but nothing seems to work in making the response json. It is just a string.
Change
response = HTTParty.get('https://api.publicapis.org/entries', format: :json)
to
response = HTTParty.get('https://api.publicapis.org/entries').
I think you don't need the format: :json more so when you explicitly format the response to JSON anyway.
You need to return a json object in the body parameter of the stubbed response:
E.g: For an empty response:
stub_request(:get, "https://api.publicapis.org/entries").
to_return(status: 200, body: "".to_json, headers: {})
OR For a valid response: (Note: You may have to require json to convert a hash to json)
require 'json'
...
stub_request(:get, "https://api.publicapis.org/entries").
to_return(status: 200, body: { entries: { '0': { message: "Hello World" } } }.to_json, headers: {})
Solved!
It seems there was an error because the json gem version that HTTParty uses is too old.
Moved on to RestClient gem for the RESTful API calls. It had another conflict in the mime gem versioning.
Finally moved to Faraday and that solved my problems:
JSON.parse(response.body, :quirks_mode => true)
tl;dr Had the same issue and ended up having to upgrade webmock.
Long form:
Webmock inserts middleware into your calls, so when HTTParty makes the calls they end up going through the Webmock interfaces first.
You can verify this by trying the call standalone (withouth all the rspec config):
bundle console
irb> require "httparty"
=> true
irb> httparty.get("https://google.com")
If that standalone call succeeds, the issue is somewhere within Webmock itself.
For me, somewhere along the line of calls through Webmock was an outdated interface that was incompatible and throwing the Wrong Number of Arguments error. And this was also crashing my debugger (RubyMine).
Upgrading Webmock solved this issue (because they had fixed it in newer versions).

Rails 5.2.3 converting all params datatype to string when testing using rspec

I am using rails 5.2.3 and testing using rspec-rails (3.8.2), when I send request to rails like this
let(:params) do
{
down_payment: 10_000,
asking_price: 100_000,
payment_schedule: 'weekly',
amortization_period: 5
}
end
it 'works' do
get :calculate, params: params, format: :json
expect(response.status).to eq 200
end
I also tried
it 'works' do
get :calculate, params: params, as: :json
expect(response.status).to eq 200
end
in rails all integers get converted to string like this
<ActionController::Parameters {"amortization_period"=>"5", "asking_price"=>"100000", "down_payment"=>"10000", "payment_schedule"=>"weekly", "format"=>"json", "controller"=>"payment_amount", "action"=>"calculate", "payment_amount"=>{}} permitted: false>
But if I use curl to send a request I can see integer not being converted to string.
curl -X GET -H "Content-Type: application/json" -d ‘{"asking_price": 100000 ,"payment_schedule": "monthly", "down_payment": 10000, "amortization_period": 5 }' http://localhost:3000/payment-amount
Thanks for any help!
JSON payloads can contain five value types: string, number, integer, boolean and null.
HTTP query strings are, by contrast, only strings.
By default, request specs use the encoding specified in the HTTP spec - i.e. all parameters are strings. This is why you see the parameters get converted.
If your production system is sending JSON, you need to tell the test to do so too - e.g. by adding as: :json as you did above.
Just add as: :json format to your requests
post(graphql_path, params: params, as: :json)

How can rspec test http calls with body and headers

I'm using rails4 + rspec 3. I want to make HTTP calls, and pass both params (such as JSON body or query string), and also HTTP headers. I was able to pass one of these two, but not both.
when I try something like:
post api_v1_post_path(#myid), {} , {"X-Some-Header" => "MyValue"}
it works fine and the headers fine, but if I do something like:
post api_v1_post_path(#myid), {"myparam" => "myvalue"} , {"X-Some-Header" => "MyValue"}
I get the following error:
Failure/Error: post api_v1_post_path(#myid), {"myparam" =>"myvalue"}, headers
ActionDispatch::ParamsParser::ParseError:
795: unexpected token at 'myparam'
Any ideas?
It seems that the POST params are expected to be JSON encoded. 795: unexpected token at 'myparam' is caused when the app tries to JSON decode the params that are not encoded.
Use .to_json with the post params.
post api_v1_post_path(#myid), {"myparam" => "myvalue"}.to_json , {"X-Some-Header" => "MyValue"}
You may want to use let:
describe 'Test' do
let( :params ){{ myparam: 'myvalue' }}
let( :headers ){{ 'X-Some-Header' => 'MyValue' }}
it 'succeeds' do
post api_v1_post_path(#myid), params.to_json , headers

How to assert HTTP request is made with correct URI when using VCR and WebMock?

I am testing a Ruby on Rails project using WebMock and VCR. I would like to assert that a request is made to the correct host, but I can't find an example of how to do that. Here's some pseudo-code for what I'm trying to do:
VCR.use_cassette('example.com request') do
subject.domain = 'example.com'
expect { subject.get('/something') }.to make_a_get_request_for('http://example.com/something')
end
Assuming your VCR is recording to YAML (the default, I believe?), this one-liner returns an array of HTTP URIs that were called during the current cassette block:
YAML.load_file(VCR.current_cassette.file)["http_interactions"].pluck("request").pluck("uri")
E.g.,
YAML.load_file(VCR.current_cassette.file)["http_interactions"].pluck("request").pluck("uri")
[
[0] "https://stackoverflow.com/wayland-project/libinput/e0008d3d/tools/libinput-debug-gui.c",
[1] "https://stackoverflow.com/wayland-project/libinput"
]
You can use an array matching method of your choice to assert the expected request URI from there.
Other members of the request object you might want to pluck instead of uri:
{
"method" => "get",
"uri" => "https://stackoverflow.com/adomain",
"body" => {
"encoding" => "US-ASCII",
"string" => ""
},
"headers" => {
"Accept" => [
...
}
}

Ruby: HTTParty: can't format XML POST data correctly?

NOTE: "object" is a placeholder work, as I don't think I should be saying what the controller does specifically.
so, I have multiple ways of calling my apps API, the following works in the command line:
curl -H 'Content-Type: application/xml' -d '<object><name>Test API object</name><password>password</password><description>This is a test object</description></object>' "http://acme.example.dev/objects.xml?api_key=1234"
the above command generates the following request in the devlog:
Processing ObjectsController#create to xml (for 127.0.0.1 at 2011-07-07 09:17:51) [POST]
Parameters: {"format"=>"xml", "action"=>"create", "api_key"=>"1234", "controller"=>"objects",
"object"=>{"name"=>"Test API object", "description"=>"This is a test object", "password"=>"[FILTERED]"}}
Now, I'm trying to write tests for the actions using the API, to make sure the API works, as well as the controllers.
Here is my current (broken) httparty command:
response = post("create", :api_key => SharedTest.user_api_key, :xml => data, :format => "xml")
this command generates the following request in the testlog:
Processing ObjectsController#create to xml (for 0.0.0.0 at 2011-07-07 09:37:35) [POST]
Parameters: {
"xml"=>"<object><name><![CDATA[first post]]></name>
<description><![CDATA[Things are not as they used to be]]></description>
<password><![CDATA[WHEE]]></password>
</object>",
"format"=>"xml",
"api_key"=>"the_hatter_wants_to_have_tea1",
"action"=>"create",
"controller"=>"objects
So, as you can see, the command line command actually generates the object hash from the xml, whereas the httparty command ends up staying in xml, which causes problems for the create method, as it needs a hash.
Any ideas / proper documentation?
Current documentation says that post takes an url, and "options" and then never says what options are available
**EDIT:
as per #Casper's suggestion, my method now looks like this:
def post_through_api_to_url(url, data, api_key = SharedTest.user_api_key)
response = post("create", {
:query => {
:api_key => api_key
},
:headers => {
"Content-Type" => "application/xml"
},
:body => data
})
ap #request.env["REQUEST_URI"]
assert_response :success
return response
end
unfortunately, the assert_response fails, because the authentication via the api key fails.
looking at the very of of the request_uri, the api_key isn't being set properly... it shows:
api_key%5D=the_hatter_wants_to_have_tea1"
but it should just be equals, without the %5D (right square bracket)
I think this is how you're supposed to use it:
options = {
:query => {
:api_key => 1234
},
:headers => {
"Content-Type" => "application/xml"
},
:body => "<xmlcode>goes here</xmlcode>"
}
post("/create", options)
Forgive me for being basic about it but if you only want to send one variable as a parameter, why don't you do as Casper suggests, but just do:
post("/create?api_key=1234", options)
Or rather than testing HTTParty's peculiarities in accessing your API, perhaps write your tests using Rack::Test? Very rough example...
require "rack/test"
require "nokogiri"
class ObjectsTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyApp.new
end
def create_an_object(o)
authorize "x", "1234" # or however you want to authenticate using query params
header 'Accept', 'text/xml'
header 'Content-Type', 'text/xml'
body o.to_xml
post "/create"
xml = Nokogiri::XML(last_response.body)
assert something_logic_about(xml)
end
end

Resources