How to set HTTP_USER_AGENT in rspec testing [duplicate] - ruby-on-rails

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.

Related

How do you fill in or bypass HTTP Basic Auth in Rails 5.2 System Tests?

I'm writing some tests for a site that is protected with http authentication on all pages.
I've managed to come across a way of bypassing it for controller tests by doing the following
get exams_url, headers: {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('admin','admin') }
But how do I get past it for a system test? I've been searching around for hours to try and find a solution for this, but all of the suggestions I've found appear to be for older versions of Rails/Capybara and do not work. There doesn't appear to be anything in the Rails testing documentation regarding this either.
Okay, this seems to do the trick for me
def visit_with_http_auth(path)
username = 'admin'
password = 'admin'
visit "http://#{username}:#{password}##{Capybara.current_session.server.host}:#
{Capybara.current_session.server.port}#{path}"
end
And then in my test methods I just do
test "visiting the index" do
visit_with_http_auth questions_path
assert_selector "h1", text: "Questions"
end
There’s no need to mess with Capybara. Just add this method to ActiveSupport::TestCase in your test_helper.rb:
def visit_authenticated(url)
uri = URI.parse(url)
uri.user = 'put_your_user_here'
uri.password = 'put_your_password_here'
visit uri
end

allow_any_instance_of and allow using the instance_double is not working properly

Just want to ask if you encounter that when you refactor your spec that before you used the allow_any_instance_of then you change it to allow it didn't work as what you expect. As we all know in the documentation the allow_any_instance_of was already deprecated and they're encourage us to use the allow. I still don't know why it didn't work.
Btw, I can't reproduce my code here as this is own by company but the structure is like this.
Before
feature `Something Page Spec here`, retry: 0, js: true do
# some `let` here
before do
sign_in user
setup_something_here
end
describe 'feature here' do
let( :user ) { create( :user ) }
before do
allow_any_instance_of( ActionDispatch::Request ).to receive( :headers ) { { 'something' => 'here' } }
end
context 'something here' do
# then some expectation here
end
end
end
After
feature `Something Page Spec here`, retry: 0, js: true do
# some `let` here
before do
sign_in user
setup_something_here
request = instance_double( ActionDispatch::Request )
allow( request ).to receive( :headers ) { { 'something' => 'here' } }
end
describe 'feature here' do
let( :user ) { create( :user ) }
context 'something here' do
# then some expectation here
end
end
end
I did pry when I stubbed it in before I can get the correct value but then in the after it's already nil. I'm confused why it didn't work. Hope there some can help me with this confusion. Thanks!
It doesn't work because you created just a local variable request that lives only inside before block.
If you want this to work you need to also stub controller request method to actually return this variable.
Something like this
request = instance_double( ActionDispatch::Request )
allow( request ).to receive( :headers ) { { 'something' => 'here' } }
allow_any_instance_of(YourRelatedController).to receive(:request).and_return(request)
You have a few issues here
You should not be mocking/doubling in feature tests since it defeats the whole purpose of feature tests (which really makes the rest of these issues moot)
allow_any_instance_of isn't deprecated, it's use is discouraged because in properly structured tests it shouldn't be needed 99.9% of the time, but it is not deprecated
With instance_double you create an instance of ActionDispatch::Request - but it's not an instance any other code in your test or app are going to use.
It's not clear what exactly your test is checking but, if you're trying to test that headers are set that may be more appropriate for a request spec than feature spec, depending on what headers and what the purpose of them is

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 }

RoR: testing an action that uses http token authentication

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)

What's the best way to set custom request headers when using Capybara in RSpec request specs?

I'm monkey patching Capybara::Session with a set_headers method that assigns to Capybara::RackTest::Browser's options attribute (which I've changed from an attr_reader to an attr_accessor).
The patches:
class Capybara::RackTest::Browser
attr_accessor :options
end
class Capybara::Session
def set_headers(headers)
if driver.browser.respond_to?(:options=) #because we've monkey patched it above
options = driver.browser.options
if options.nil? || options[:headers].nil?
options ||= {}
options[:headers] = headers
else
options[:headers].merge!(headers)
end
else
raise Capybara::NotSupportedByDriverError
end
end
end
In my request spec, I'm doing:
page.set_headers("REMOTE_ADDR" => "1.2.3.4")
visit root_path
This works, but I'm wondering if there's a better way, it seems a bit overkill to just be able to set a custom remote_ip/remote_addr on a request. Any thoughts?
If you want the headers to be globally set on all requests, you can use something like:
Capybara.register_driver :custom_headers_driver do |app|
Capybara::RackTest::Driver.new(app, :headers => {'HTTP_FOO' => 'foobar'})
end
See the rack_test_driver_spec.rb in Capybara 1.1.2 and Capybara's issue #320, Setting up HTTP headers.
Do you need to add custom header into one specific request in rspec using capybara? I used this in acceptance tests. It was the best way for me to use get method with specific header data. You can assess specific element on response page. See my example below:
get user_registration_path, { :invite => invite_token }, { 'X_GEOIP_COUNTRY_CODE' => 'US' }
expect(assigns(:ip_country)).to eq('US')
response.body.should have_selector("input#currency_usd[checked='checked']")
I hope it helps.
I've discovered an ability to modify headers when using the default Capybara::RackTest driver.
There's a method Capybara::RackTest::Browser#process which prepares a request before finaly being sent (https://www.rubydoc.info/gems/capybara/Capybara%2FRackTest%2FBrowser:process). As you can see there in the code the request headers are built from the options[:headers]. The options actually refers to the driver.options attribute. So you can set any headers by modifying this hash.
Here's an example of my feature spec with custom headers:
let(:headers) do
{
"YOUR_CUSTOM_HEADER_1" => "foo",
"YOUR_CUSTOM_HEADER_2" => "bar",
...
}
end
before(:each) do
#origin_headers = page.driver.options[:headers]
page.driver.options[:headers] ||= {}
page.driver.options[:headers].merge!(headers)
end
after(:each) do
page.driver.options[:headers] = #origin_headers
end
Tested with:
capybara: 3.13.2 (RackTest driver)
rspec: 3.8
rails: 5.2.2
P.S. Haven't tested it with selenium driver yet. But probably it works in a similar way.

Resources