RoR: testing an action that uses http token authentication - ruby-on-rails

I'm trying to test a controller that's using an http token authentication in the before filter. My problem is that it works ok wheh I use curl to pass the token, but in my tests it always fails (I'm using rspec btw). Tried a simple test to see if the token was being passed at all, but it seems like it's not doing so. Am I missing anything to get the test to actually pass the token to the controller?
Here's my before filter:
def restrict_access
authenticate_or_request_with_http_token do |token, options|
api_key = ApiKey.find_by_access_token(token)
#user = api_key.user unless api_key.nil?
#token = token #set just for the sake of testing
!api_key.nil?
end
end
And here is my test:
it "passes the token" do
get :new, nil,
:authorization => ActionController::HttpAuthentication::Token.encode_credentials("test_access1")
assigns(:token).should be "test_access1"
end

I'm assuming ApiKey is an ActiveRecord model, correct? curl command runs against development database, and tests go against test db. I can't see anything that sets up ApiKey in your snippets. Unless you have it somewhere else, try adding something along these lines:
it "passes the token" do
# use factory or just create record with AR:
ApiKey.create!(:access_token => 'test_access1', ... rest of required attributes ...)
# this part remains unchanged
get :new, nil,
:authorization => ActionController::HttpAuthentication::Token.encode_credentials("test_access1")
assigns(:token).should be "test_access1"
end
You can later move it to before :each block or support module.
UPDATE:
After seeing your comment I had to look deeper. Here's another guess. This form of get
get '/path', nil, :authorization => 'string'
should work only in integration tests. And for controller tests auth preparation should look like this:
it "passes the token" do
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.encode_credentials("test_access1")
get :new
assigns(:token).should be "test_access1"
end
Reasons behind this come from method signatures for respective test modules:
# for action_controller/test_case.rb
def get(action, parameters = nil, session = nil, flash = nil)
# for action_dispatch/testing/integration.rb
def get(path, parameters = nil, headers = nil)

Related

Rails testing controller private method with params

I have a private method in a controller
private
def body_builder
review_queue = ReviewQueueApplication.where(id: params[:review_queue_id]).first
...
...
end
I would like to test just the body_builder method, it is a method buidling the payload for an rest client api call. It needs access to the params however.
describe ReviewQueueApplicationsController, type: :controller do
describe "when calling the post_review action" do
it "should have the correct payload setup" do
#review_queue_application = ReviewQueueApplication.create!(application_id: 1)
params = ActionController::Parameters.new({ review_queue_id: #review_queue_application.id })
expect(controller.send(:body_builder)).to eq(nil)
end
end
end
If I run the above it will send the body_builder method but then it will break because the params have not been set up correctly as they would be in a call to the action.
I could always create a conditional parameter for the body_builder method so that it either takes an argument or it will use the params like this def body_builder(review_queue_id = params[:review_queue_id]) and then in the test controller.send(:body_builder, params), but I feel that changing the code to make the test pass is wrong it should just test it as it is.
How can I get params into the controller before I send the private method to it?
I think you should be able to replace
params = ActionController::Parameters.new({ review_queue_id: #review_queue_application.id })
with
controller.params = ActionController::Parameters.new({ review_queue_id: #review_queue_application.id })
and you should be good. The params is just an attribute of the controller (the actual attribute is #_params but there are methods for accessing that ivar. Try putting controller.inspect in a view).

Set header in RSpec 3 request

I'm trying to set the header for some RSpec requests that require authentication. The header is ACCESS_TOKEN. No matter how I attempt to set the header, it never gets set. I know the app works because I can manually test it, I just cant get rspec tests to work. See the full source code & tests for this problem here: https://github.com/lightswitch05/rspec-set-header-example
Since authentication is used in most of my request specs, I've created support helper module to retrieve an access token and set it in the header. Below is the summary of how I'm trying to set the header, see everything I've tried in the full source
# my_app/spec/support/session_helper.rb
module SessionHelper
def retrieve_access_token
post api_v1_session_path({email: 'test#example.com', password: 'poor_password'})
expect(response.response_code).to eq 201
expect(response.body).to match(/"access_token":".{20}"/)
parsed = JSON(response.body)
token = parsed['access_token']['access_token']
#request.headers['HTTP_ACCESS_TOKEN'] = token
end
end
an example request spec that uses this helper and should work, but always fails because the header never gets set:
# my_app/spec/requests/posts_spec.rb
# ...
context "create" do
it "creates a post" do
retrieve_access_token
post = FactoryGirl.build(:post)
post api_v1_posts_path(
post: {
title: post.title,
content: post.content
}
)
expect(response.body).to include('"id":')
expect(response.body).to include('"title":"' + post.title + '"')
expect(response.body).to include('"content":"' + post.content + '"')
expect(response.response_code).to eq 201
end
end
I know I can manually set the header in the individual get and post requests - but that is not a maintainable solution for API-wide authorization. Imagine having to change every test if the header name changed slightly.
Note: This answer is based on what you seem to be calling api_v1_session_path with post request to SessionsController for every spec you're trying to run in your requests specs.
There are two ways to solve the issue I figured you have here.
Solution #1 - Either you create another helper method in your SessionHelper or in some other helper file called support/requests_helper.rb(however you prefer). I'd create another helper in support/requests_helper.rb:
module RequestsHelper
def get_with_token(path, params={}, headers={})
headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
get path, params, headers
end
def post_with_token(path, params={}, headers={})
headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
post path, params, headers
end
# similarly for xhr..
end
then in rails_helper.rb:
# Include the sessions helper
config.include SessionHelper, type: :request
# Include the requests helper
config.include RequestsHelper, type: :request
change session_helper.rb:
# my_app/spec/support/session_helper.rb
module SessionHelper
def retrieve_access_token
post api_v1_session_path({email: 'test#example.com', password: 'poor_password'})
expect(response.response_code).to eq 201
expect(response.body).to match(/"access_token":".{20}"/)
parsed = JSON(response.body)
parsed['access_token']['access_token'] # return token here!!
end
end
Now, you can change your all requests specs like this:
describe Api::V1::PostsController do
context "index" do
it "retrieves the posts" do
get_with_token api_v1_posts_path
expect(response.body).to include('"posts":[]')
expect(response.response_code).to eq 200
end
it "requires a valid session key" do
get api_v1_posts_path
expect(response.body).to include('"error":"unauthenticated"')
expect(response.response_code).to eq 401
end
end
end
Solution #2 - Change specs/factories/access_token_factory.rb to:
FactoryGirl.define do
factory :access_token do
active true
end
# can be used when you want to test against expired access tokens:
factory :inactive_access_token do
active false
end
end
Now, change your all requests specs to use access_token:
describe Api::V1::PostsController do
context "index" do
let(:access_token){ FactoryGirl.create(:access_token) }
it "retrieves the posts" do
# You will have to send HEADERS while making request like this:
get api_v1_posts_path, nil, { 'HTTP_ACCESS_TOKEN' => access_token.access_token }
expect(response.body).to include('"posts":[]')
expect(response.response_code).to eq 200
end
it "requires a valid session key" do
get api_v1_posts_path
expect(response.body).to include('"error":"unauthenticated"')
expect(response.response_code).to eq 401
end
end
end
I'd go with "Solution #1" as it removes a burden of making you remember to send HTTP_ACCESS_TOKEN in headers every time you want to make such requests.
Common misconception is to treat controller and request tests equally.
It would be good to start from reading about controller specs and request specs. As you can see, controller specs simulate http request, while request specs perform full stack request.
You can find some good article about why you should write controller specs and what to test there here. While it is good to write them, they shouldn't be touching database in my opinion.
So while Voxdei answer is partially valid (after changing request specs to controller specs your way of setting headers will work), it misses the point in my opinion.
In request specs, you cannot just use request / controller methods, you have to pass your headers in hash as third argument of your request methods, so i.e.
post '/something', {}, {'MY-HEADER' => 'value'}
What you could do though is to stub authentication like:
before do
allow(AccessToken).to receive("authenticate").and_return(true)
end
Then you could test your authentication in one spec to be sure that it works and use such before filter in other specs. This is also probably better approach as performing additional request every time you run spec needing authentication is quite huge overhead.
I also found quite interesting pull request in grape gem which tries to add default headers behaviour so you could also try with such approach if you would really want to use default headers in request specs.
Probably because of how now Rspec treats spec files. It no longer automatically infers spec type from a file location
Try either setting this behavior back to what you used to know
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
or set it locally for each controller spec files in your project
describe MyController, type: :controller do
# your specs accessing #request
end
Surya's answer is the best. But you can DRY it up a little bit more:
def request_with_user_session(method, path, params={}, headers={})
headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
send(method, path, params, headers)
end
Here you have only one method and call the request method by the given parameter method.
I stub the function that authenticates the request to return true or any value returned by the function.
ApplicationController.any_instance.stub(:authenticate_request) { true }

RSpec set session object

I'm trying to set a session object from my controller spec.
it 'use invalid coupon' do
session[:coupon] = 'abcd'
Service.any_instance.stub(:validate_coupon).with(session[:coupon]).and_return('abcd')
get :index
expect(flash[:coupon__notice]).to be nil
end
but when I inspect the session, instead of a #coupon object, I get a #data that contains the string 'coupon', and test fails.
This is explained in the Guide to Testing Rails Applications in section 4 "Functional Tests for your Controllers. The get action takes a params hash and a session hash, e.g.:
get(:show, {'id' => "12"}, {'user_id' => 5})
You should be able to pass in nil for the params hash in your example, then pass in your desired session parameters:
get :index, nil, {coupon: 'abcd'}
I recommend a thorough reading of the Rails guide for anyone using RSpec for Rails testing. rspec-rails leverages the existing Rails test classes, a point which is not made very clear in the rspec-rails docs.
In Rails 5+, if you are using ActionController::TestCase session is passed as a keyword arg.
Setting params and session would look like:
get(:show, params: {'id' => "12"}, session: {'user_id' => 5})
Setting only the session would look like,
get(:show, session: {'user_id' => 5})
If you are using ActionDispatch::IntegrationTest, which is the new default for for controller tests, you are not able to set the session variables and should set them by walking your test through the login flow.
You can also use #request object like
#request.session['coupon'] = 'abcd'
You can also do:
request_params = { city: 'Bomet', code: '023' }
session_params = { taxes_paid: 'No' }
Then in your spec you can do:
get :edit, params: request_params, session: session_params
In your functionality, you can access your session with session[:taxes_paid]

How to set HTTP_USER_AGENT in rspec testing [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is it possible to specify a user agent in a rails integration test or spec?
I'm testing a request in my rails app using rspec. I need to be able to set the user agent before the request.
This is not working:
describe "GET /articles feed for feedburner" do
it "displays article feed if useragent is feedburner" do
# Run the generator again with the --webrat flag if you want to use webrat methods/matchers
#articles=[]
5.times do
#articles << Factory(:article, :status=>1, :created_at=>3.days.ago)
end
request.env['HTTP_USER_AGENT'] = 'feedburner'
get "/news.xml"
response.should be_success
response.content_type.should eq("application/xml")
response.should include("item[title='#{#articles.first.title}']")
end
end
How can I properly specify the user agent?
Try using this in your test:
request.stub!(:user_agent).and_return('FeedBurner/1.0')
or for newer RSpec:
allow(request).to receive(:user_agent).and_return("FeedBurner/1.0")
Replace FeedBurner/1.0 with the user agent you want to use. I don't know if that exact code will work but something like it should.
This is what I do in an integration test - notice the last hash that sets REMOTE_ADDR (without HTTP_). That is, you don't have to set HTTP header before the request, you can do so as part of the request.
# Rails integration tests don't have access to the request object (so we can't mock it), hence this hack
it 'correctly updates the last_login_ip attribute' do
post login_path, { :email => user.email, :password => user.password }, { 'REMOTE_ADDR' => 'some_address' }
user.reload
user.last_login_ip.should == 'some_address'
end
Define this somewhere (e.g. spec_helper.rb):
module DefaultUserAgent
def post(uri, params = {}, session = {})
super uri, params, {'HTTP_USER_AGENT' => MY_USER_AGENT}.merge(session)
end
def get(uri, params = {}, session = {})
super uri, params, {'HTTP_USER_AGENT' => MY_USER_AGENT}.merge(session)
end
end
Then just include DefaultUserAgent when you need it.

How to test cookies.permanent.signed in Rails 3

I have a action in some controller that set some value in a permanent signed cookie like this:
def some_action
cookies.permanent.signed[:cookie_name] = "somevalue"
end
And in some functional test, I'm trying to test if the cookie was set correctly suing this:
test "test cookies" do
assert_equal "somevalue", cookies.permanent.signed[:cookie_name]
end
However, when I run the test, I got the following error:
NoMethodError: undefined method `permanent' for #
If I try only:
test "test cookies" do
assert_equal "somevalue", cookies.signed[:cookie_name]
end
I get:
NoMethodError: undefined method `signed' for #
How to test signed cookies in Rails 3?
I came across this question while Googling for a solution to a similar issue, so I'll post here. I was hoping to set a signed cookie in Rspec before testing a controller action. The following worked:
jar = ActionDispatch::Cookies::CookieJar.build(#request)
jar.signed[:some_key] = "some value"
#request.cookies['some_key'] = jar[:some_key]
get :show ...
Note that the following didn't work:
# didn't work; the controller didn't see the signed cookie
#request.cookie_jar.signed[:some_key] = "some value"
get :show ...
In rails 3's ActionControlller::TestCase, you can set signed permanent cookies in the request object like so -
#request.cookies.permanent.signed[:foo] = "bar"
And the returned signed cookies from an action taken in a controller can be tested by doing this
test "do something" do
get :index # or whatever
jar = #request.cookie_jar
jar.signed[:foo] = "bar"
assert_equal jar[:foo], #response.cookies['foo'] #should both be some enc of 'bar'
end
Note that we need to set signed cookie jar.signed[:foo], but read unsigned cookie jar[:foo]. Only then we get the encrypted value of cookie, needed for comparison in assert_equal.
After looking at the Rails code that handles this I created a test helper for this:
def cookies_signed(name, opts={})
verifier = ActiveSupport::MessageVerifier.new(request.env["action_dispatch.secret_token".freeze])
if opts[:value]
#request.cookies[name] = verifier.generate(opts[:value])
else
verifier.verify(cookies[name])
end
end
Add this to test_help.rb, then you can set a signed cookie with:
cookies_signed(:foo, :value => 'bar')
And read it with:
cookies_signed(:foo)
A bit hackish maybe, but it does the job for me.
The problem (at least on the surface) is that in the context of a functional test (ActionController::TestCase), the "cookies" object is a Hash, whereas when you work with the controllers, it's a ActionDispatch::Cookies::CookieJar object. So we need to convert it to a CookieJar object so that we can use the "signed" method on it to convert it to a SignedCookieJar.
You can put the following into your functional tests (after a get request) to convert cookies from a Hash to a CookieJar object
#request.cookies.merge!(cookies)
cookies = ActionDispatch::Cookies::CookieJar.build(#request)
The problem also appears to be your tests.
Here is some code and tests I used to TDD the situation where you want to set a cookie's value from passing a params value into a view.
Functional Test:
test "reference get set in cookie when visiting the site" do
get :index, {:reference => "121212"}
refute_nil cookies["reference"]
end
SomeController:
before_filter :get_reference_code
ApplicationController:
def get_reference_code
cookies.signed[:reference] ||= params[:reference]
end
Notice that the refute_nil line, the cookies is a string... that is one thing that also made this test not pass, was putting a symbol in cookies[:reference] the test did not like that, so i didn't do that.

Resources