With Rspec, I am trying to build a spec testing some basic http requests. I'm making a rookie mistake somewhere and need help finding it.
I am purposely making the spec fail with a nonsense expectation so the error message will tell me what I'm getting -- once I figure things out I'll correct the expectation:
user = create(:member)
json_data = {email: user.email, password: user.password}.to_json
post "api/v1/users/sign_in", json_data, format: :json
expect(last_response.body).to eq "foobar"
api/v1/users/sign_in routes to the following controller:
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params
end
end
This gives the error:
expected: "foobar"
got: "{\"{\\"email\\":\\"7abdiel_roob#smithrau.biz\\",\\"password\\":\\"12345678\\"}\"=>nil,
\"action\"=>\"create\", \"controller\"=>\"api/v1/sessions\"}"
Ok great. My data is getting to the server and the server sends it back, which is what I want. In my next step I try to grab the email. I change the controller to
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params[:email]
end
end
and I get
expected: "foobar"
got: " "
I looks to me that the params hash is using the JSOn data I sent in the request as the name of a key, not actually a value. Or maybe this is a strong_params thing? I've tried many things and can't seem to pull the data I want out of the params object.
What is happening is that you are double encoding the JSON data which you are sending in your spec.
json_data = {email: user.email, password: user.password}
post "api/v1/users/sign_in", json_data, format: :json
RSpec will automatically encode the request body as JSON for you.
Related
In my rails UsersController - users#sign_up action, I perform verification to ensure the user has a valid recaptcha v3 token before moving on to the rest of the controller logic. If the recaptcha verification fails then the controller returns and responds with an error message. However, my rspec tests are failing because I am unsure how to mock / bypass the verification in the controller.
spec/requests/auth_spec.rb:
RSpec.describe "Authentication Requests", type: :request do
context "sign up user" do
it "fails to sign up a user without email address" do
headers = { :CONTENT_TYPE => "application/json" }
post "/api/v1/sign_up", :params => { :email => nil, :password => "password123"}.to_json, :headers => headers
expect(response.header['Content-Type']).to include('application/json')
expect(response_body_to_json).to eq({"error"=>"Failed to create user"})
end
end
end
The test is failing when I post to /api/v1/sign_up because there are missing params for the recaptcha token. As far as I understand, it isn't possible to mock a recaptcha v3 token. Therefore it would be preferable to have verify_recaptcha return true for the rspec test.
controllers/api/v1/users_controller:
def sign_up
# Rspec fails here with missing params error
return if !verify_recaptcha('sign_up', recaptcha_params[:token])
#user = User.new(user_credential_params)
if #user.valid?
# Handle success/fail logic
end
end
private
def user_credential_params
params.permit(:email, :password)
end
def recaptcha_params
params.permit(:token)
end
controllers/concerns/users_helper.rb:
def verify_recaptcha(recaptcha_action, token)
secret_key = Rails.application.credentials.RECAPTCHA[:SECRET_KEY]
uri = URI.parse("https://www.google.com/recaptcha/api/siteverify?secret=#{secret_key}&response=#{token}")
response = Net::HTTP.get_response(uri)
json = JSON.parse(response.body)
recaptcha_valid = json['success'] && json['score'] > 0.5 && json['action'] == recaptcha_action
if !recaptcha_valid
render :json => { :error_msg => 'Authentication Failure' }, :status => :unauthorized
return false
end
return true
end
Can I stub / mock the verify_recaptcha method that comes from the users_helper concern to return true? Is there a better way to accomplish this?
I did due diligence before asking this question and I found this post: mocking/stubbing a controller recaptcha method with rspec in rails.
This was the answer for that post:
allow(controller).to receive(:verify_recaptcha).and_return(true)
The above didnt work for me because individual had their verify_recaptcha method inside of ApplicationController.rb (which seems a little dirty in my opinion). Given that my verify_recaptcha method is inside of a concern, I am not sure how to access the concern via Rspec.
You can try adding UserController.expects(:verify_recaptcha).returns(true) to your test.
This will bypass the recaptcha or Just try finding where the verify_recaptcha method exists and then write controller or class name before the expect method in
UserController.expects(:verify_recaptcha).returns(true)
I am adding a RESTful API to the Rails Tutorial sample app. The controller actions should take XML as input and respond with XML. I am trying to follow a TDD approach but have been unable to find a definitive method of making a post request. Here are my tests, as best as I've written them so far:
it "should increment the Relationship count" do
expect do
# valid_xml = "<xml><:followed_id>#{other_user.id}</:followed_id></xml>"
valid_xml = { followed_id: other_user.id }.to_xml
post :create, relationship: valid_xml, format: 'xml', content_type: 'application/xml'
end.to change(Relationship, :count).by(1)
end
it "should respond with success" do
# valid_xml = "<xml><:followed_id>#{other_user.id}</:followed_id></xml>"
valid_xml = { followed_id: other_user.id }.to_xml
post :create, relationship: valid_xml, format: :xml, content_type: 'application/xml'
expect(response).to be_success
end
This particular test is verifying that posting XML will create a new Relationship. As you can see, I have tried several ways to specify that the input is XML. Here is the controller action:
class RelationshipsController < ApplicationController
before_action :signed_in_user
respond_to :html, :js, :xml
def create
# Problem is that the xml is not being read as such; it's a string
relationship = params[:relationship]
puts relationship
puts relationship[:followed_id]
#user = User.find(relationship[:followed_id])
current_user.follow!(#user)
respond_with #user
end
For the given tests, the controller prints out relationship as XML but errors on the following line, where it attempts to get the value of key :followed_id. The error is TypeError: no implicit conversion of Symbol into Integer for both tests.
I assume that this means that the type of relationship is a string, not a hash, and Ruby thinks that the bracket is supposed to be an index. Does anyone know where I am going wrong, either in the test or the controller action?
Are there any better ways to test a RESTful API or gems I should use?
The XML parameters parser was removed from core in Rails 4.0. This was due to several vulnerabilities and the fact that most APIĀ“s worth using today are JSON or moving towards JSON.
The removed functionality is available from the actionpack-xml_parser gem.
I am trying to configure my controller to process the params sent through a POST from another website. My log shows that the parameters that I receive are as follows:
{"page_id"=>"8b62f4ac-8588-11e3-a094-12314000b04c", "page_name"=>"test form", "variant"=>"b", "page_url"=>"http://get.xxxxxxx.com/test-form", "data.json"=>"{\"name\":[\"Dave\"],\"email\":[\"xxxx#me.com\"],\"phone\":[\"4447177265\"],\"ip_address\":[\"64.114.175.126\"],\"time_submitted\":[\"07:34 AM UTC\"]}", "data.xml"=>"\n\n Dave\n xxxx#me.com\n 2507177265\n 64.114.175.126\n 07:34 AM UTC\n"}
Initially I thought that Rails would automatically parse the JSON in the params and I could access them in the normal way. So I wrote the Registrations Controller like this:
class Api::RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token
respond_to :json
def create
#user = User.new(user_params)
if #user.save
render json: #user.as_json( email: #user.email), status: 201
return
else
warden.custom_failure!
render json: #user.errors, status: 422
end
end
def user_params
params.require(:'data.json').permit(:email, :name, :phone, :comments, :residency, :qualification, :acknowledgement) if params.present?
end
end
However, it is simply not working at all. I get an error undefined method 'permit' for string. So obviously I'm not accessing the JSON correctly. Is it possible that because the JSON is escaped that it's throwing the errors?
I've been googling and asking in IRC for a couple of days but I'm not any farther ahead.
I can pass a properly formatted JSON to the controller and it works fine (with changes to the require arguments)
I'm stumped since I need to be able to create a new user with the JSON data. Any help would be HUGELY appreciated. I just don't know what direction to even go from here.
The params.require(:'data.json') returns a JSON body which is a string, however your controller does not interpret the string but expects a Hash.
You can convert the JSON string to a Hash object using the parse class method for JSON like so:
require 'json'
JSON::parse(json_string)
I've this code that I'm trying to get working.
class CommitRequestsController < ApplicationController
respond_to :json
def create
#commit_request = CommitRequest.new(params[:commit_request])
respond_with(repository, #commit_request)
end
private
def repository
Repository.find(params[:repository_id])
end
end
I also have this spec
CommitRequest.any_instance.stubs(:valid?).returns(false)
post(:create, {
format: "json",
repository_id: repository.id,
commit_request: {}
})
response.status.should_not eq(201)
The problem is that the spec is always failing.
It returns 201, even tho the created object isn't valid.
Removing the mock line results in the same problem,
even tho the created object is invalid (for real this time).
I'm using Rails 3.2.
EDIT
Edited to change to a question about the test rather than code, as I see the application behaves correctly.
I'm writing a Rails 3 app which is purely a RESTful web service (i.e. no views). I have a User model, where the username is unqiue
class User < ActiveRecord::Base
validates_uniqueness_of :username
end
In my controller, I have the following code to handle a new user being created:
def create
#user = User.new(ActiveSupport::JSON.decode(request.raw_post))
if #user.save
puts "Added user #{#user.username}"
format.json { render :json => "" }
else
puts "Failed to add user: #{#user.errors.to_json}"
render json: #user.errors, status: :unprocessable_entity
end
end
I then have a functional test which creates a user with the same username as an existing user:
test "should not create user with duplicate username" do
#jim = users(:jim)
post '/users', #jim.to_json, "CONTENT_TYPE" => "application/json"
assert_response :unprocessable_entity
end
When I run the test, the controller outputs "Failed to add user: {"username":["has already been taken"]}" as expected, but the test fails:
Expected response to be a <:unprocessable_entity>, but was <200>
However, with curl I get the response I expect:
curl -i -X POST -d '{"username": "james", "email": "test#test.com" }'
HTTP/1.1 422
{"username":["has already been taken"]}
So where am I going wrong with the assertion in the test?
You probably should use respond_with. It will take care of lots of REST logic, including setting status codes.
Also, Rails may be overkill for an application that's just a REST service. You might want to consider Sinatra instead.
The reason was that I'd switched to using RackTest to allow me to post JSON in the body (i.e. not as a form parameter.) As a result, I should have been making assertions on last_response (the RackTest MockResponse used in the post call) instead of using assert_response:
test "should not create user with duplicate username" do
#jim = users(:jim)
post '/users.json', #jim.to_json, "CONTENT_TYPE" => "application/json"
assert_status :unprocessable_entity
end
def assert_status(expected_status)
assert_equal last_response.status, Rack::Utils.status_code(expected_status)
end
I am quite surprised assert_response :success passes when no methods have been called which yield a value for #response. If that assertion failed, or threw an exception, it would have been easier to track down my bug, but c'est la vie!