Problems for request rspec with basic http auth using Devise - ruby-on-rails

Devise: 4.20
Rails: 5.0.1
Rspec: 3.5
I had used this link https://github.com/plataformatec/devise/wiki/How-To:-Use-HTTP-Basic-Authentication, but I havproblems to test the http basic auth for my api using rspec for requests tests. Below is the example the error:
app/controllers/api/base_controller.rb
module Api
class BaseController < ApplicationController
before_action :authenticate_user!
protected
def authenticate_user!
authenticate_or_request_with_http_basic do |username, password|
resource = User.find_by_username(username)
if resource
sign_in :user, resource if resource.valid_password?(password)
else
request_http_basic_authentication
end
end
end
end
end
app/controllers/api/v1/car_controller.rb
module Api
module V1
class CarController < Api::BaseController
respond_to :json
def index
#cars = Car.all
render :json => {:content => #cars}, :status => 200
end
end
end
end
spec/requests/api/v1/car_controller_spec.rb
require 'rails_helper'
RSpec.describe "Servers API", :type => :request do
it 'sends a list of servers' do
admin = FactoryGirl.create(:admin)
#env = {}
#env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(admin.username, admin.password)
get "/api/cars", :params => {}, :headers => #env
# test for the 200 status-code
expect(response.status).to eq(200)
end
end
When I run the spec, I have the below error:
# --- Caused by: ---
# NoMethodError:
# undefined method `sign_in' for #<Api::V1::CarController:0x0000000609ef12>
# ./app/controllers/api/base_controller.rb:10 in block in authenticate_user!
Anyone can help me? Thanks.

I have similar setup where my specs are passing, would you also show your spec_helper content, looks like you are not including Devise::TestHelpers.
spec_helper
RSpec.configure do |config|
config.include Devise::TestHelpers
config.include Warden::Test::Helpers
config.before { Warden.test_mode! }
config.after { Warden.test_reset! }
config.before(:each) do
#headers = { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
end
and my test looks something like this:
RSpec.describe 'Users' do
context 'when client is authorized' do
let(:user) { FactoryGirl.create(:user) }
it 'gets user' do
#headers['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.
encode_credentials(
user.authentication_token,
email: user.email
)
get api_v1_user_url(id: user.id), {}, #headers
expect(response.status).to eq(200)
end
end
end

Related

Rspec: Access a controller concern's methods in request specs

I have a controller concern defined as follows:
module Auth
extend ActiveSupport::Concern
private
def sign_in(user)
session[:customer_id] = user.id
self.current_user = user
end
def signed_in?
!current_user.nil?
end
...
# other methods
end
And this module is included in the ApplicationController:
class ApplicationController < ActionController::Base
include Auth
helper_method :signed_in?, :current_user, :current_user?
before_action :authorize
end
The questions I have:
should we really extend extend ActiveSupport::Concern in a concern? If so wy and when not?
how can I enable the concern module so that its methods could be re-used in RSpec request specs?
I tried to add the following in spec/support/utilities.rb:
include ApplicationHelper
include Auth
as well as required all the files from spec/support directory in rails_helper.rb and included the above modules:
# Require all the files in support directory
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
...
# include ApplicationHelper
config.include ApplicationHelper, type: :feature
config.include Auth, type: :request
...
end
Now, when running a requests/customers_spec.rb:
require 'rails_helper'
RSpec.describe "/customers", type: :request do
describe "GET /index" do
it "renders a successful response" do
customer = create(:customer)
sign_in(customer)
get customers_url
expect(response).to be_successful
end
end
...
it fails with:
Failures:
1) /customers GET /index renders a successful response
Failure/Error: session[:customer_id] = user.id
NoMethodError:
undefined method `session' for nil:NilClass
# ./app/controllers/concerns/auth.rb:7:in `sign_in'
# ./spec/requests/customers_spec.rb:10:in `block (3 levels) in <top (required)>'
What am I missing?
I'm using Rails 6.0.3, rspec-rails 4.0.1.
The solution I came to was to add the following method to the spec/support/login_macros.rb:
module LoginMacros
def sign_in_with_browser(user)
visit login_path
fill_in 'E-mail', with: user.email
fill_in 'Password', with: user.password
click_button 'Login'
end
def sign_out_with_browser
click_link 'Logout'
end
# to enable user authentication in request specs
def login_without_browser(user)
post login_url, params: { email: user.email, password: 'secret' }
end
end
Then test a protected end-point in the above request spec as follows:
describe "GET /index" do
it "renders a successful response" do
customer = create(:customer)
login_without_browser(customer)
get customers_url
expect(response).to be_successful
end
end

How i run the test with rspec, using simple_token_authentication and devise

I run my test but the sign_in don't authenticate.
require 'rails_helper'
describe "get all contacts route", :type => :request do
let!(:contacts) {FactoryBot.create_list(:random_contacts, 20)}
before(:each) do
get '/api/v1/contacts'
user = FactoryBot.create(:user)
request.env["devise.mapping"] = Devise.mappings[:user]
request.headers['X-User-Email'] = "#{user.email}"
request.headers['X-User-Token'] = "#{user.authentication_token}"
sign_in user
end
it 'returns all contacts' do
expect(JSON.parse(response.body).size).to eq(20)
end
it 'returns status code 200' do
expect(response).to have_http_status(:success)
end
end
The return of the test is:
"{\"error\":\"You need to sign in or sign up before continuing.\"}"
https://github.com/andbri321/one_bit_contacts/tree/feature/test_contact
You might want to create some sign in/out helpers. Something along the lines of
module DeviseRequestSpecHelpers
include Warden::Test::Helpers
def sign_in(resource_or_scope, resource = nil)
resource ||= resource_or_scope
scope = Devise::Mapping.find_scope!(resource_or_scope)
login_as(resource, scope: scope)
end
def sign_out(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
logout(scope)
end
end
And then load them in your RSpec config like so:
RSpec.configure do |config|
config.include DeviseRequestSpecHelpers, type: :request
end
Then use these helpers before the tests are run.
Look here for more info.

Having probleman with ActionController::UrlGenerationError on RSPEC

Im writing some tests for my controller, but im getting the error below when i'm running rspec ./spec/controllers, when i just run the spec direct in the file i'm getting everything green.
1) VersionOne::UsersController GET #actives should return all actives
Failure/Error: get :actives
ActionController::UrlGenerationError:
No route matches {:action=>"actives", :controller=>"version_one/users"}
# ./spec/controllers/version_one/users_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
2) VersionOne::UsersController GET #archived should return all archives
Failure/Error: get :archives
ActionController::UrlGenerationError:
No route matches {:action=>"archives", :controller=>"version_one/users"}
# ./spec/controllers/version_one/users_controller_spec.rb:23:in `block (3 levels) in <top (required)>'
3) VersionOne::UsersController POST #create should create a new collaborator with success
Failure/Error: post :create, params: params
This is my controller
class UsersController < ApiControler
def actives
users = User.actives
render json: users
end
def archives
users = User.archiveds
render json: users
end
def create
user = User.build(user_params)
render json: user
end
end
This are my routes
scope 'api' do
scope 'v1', module: 'version_one' do
resources 'users' do
collection do
get 'actives'
get 'archives'
end
member do
match 'active'
match 'archive'
end
end
end
end
This are my tests
RSpec.describe VersionOne::UsersController, type: :controller do
before(:all) do
7.times { Collaborator.build(attributes_for(:user)) }
8.times { Admin.build(attributes_for(:user)) }
5.times { Collaborator.build(attributes_for(:user)).archived! }
end
describe "GET #actives" do
it "should return all actives" do
get :actives
body = JSON.parse(response.body)
expect(response).to have_http_status(:success)
expect(body["pagination"]["per_page"]).to eq(20)
expect(body["pagination"]["total_objects"]).to eq(15)
end
end
describe "GET #archived" do
it "should return all archives" do
get :archives
body = JSON.parse(response.body)
expect(response).to have_http_status(:success)
expect(body["pagination"]["per_page"]).to eq(20)
expect(body["pagination"]["total_objects"]).to eq(5)
end
end
describe "POST #create" do
it "should create a new collaborator with success" do
params = { kind: "Collaborator", user: { name: "User", email: "user#email.net", password: "123456", cpf: "36156291830" } }
post :create, params: params
body = JSON.parse(response.body)
expect(response).to have_http_status(:success)
expect(body).to include(
"name" => "User",
"cpf" => "36156291830",
"email" => "user#email.net",
)
end
end
end
this is my .rspec
--color
--format documentation
--require rails_helper
this is my rails_helper
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'shoulda-matchers'
require 'rack/test'
require 'faker'
require 'rake'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include Shoulda::Matchers::ActiveModel, type: :model
config.include Shoulda::Matchers::ActiveRecord, type: :model
config.fixture_path = "#{::Rails.root}/spec/factories"
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
config.use_transactional_fixtures = false
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
am'i forgetting something?
You do not have namespaces for UsersController
try:
module Api
module VersionOne
class UsersController < ApiController
# your code
end
end
end
You may also need to change ApiController to Api::VersionOne::ApiController

Integration Testing a rails API using devise_token_auth for authenticaton and cancancan for authorization using Rspec

I have a rails-api application that I'm testing using Rspec. The Application uses devise_token_auth gem for authentication and cancancan gem for authorization.
devise_token_auth requires that the client include these authentication headers in every request:
access-token, client, expiry, uid. These headers are available in a response after successful authentication using email and password.
I have decided to use a solution provided in this answer to set these headers during testing.
In ability.rb I have this:
## models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.role? :registered
can :create, Post, user_id: user.id
can :update, Post, user_id: user.id
can :destroy, Post, user_id: user.id
can :read, Post
end
end
end
posts#show action in PostsController looks like this:
## controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource
def show
render json: #post
end
end
I have rescued CanCan::AccessDenied error to render a json message and a status of 403 forbidden in ApplicationController
rescue_from CanCan::AccessDenied do |exception|
render json: {"message" => "unauthorized"}.to_json, :status => 403
end
I have this in spec/support/session_helper.rb
module SessionHelper
def retrieve_access_headers
##I have a user with these credentials and has a "registered" role. in the the test db.
post "/auth/sign_in", params: {:email => "registered_user#gmail.com", :password => "g00dP#ssword"}, headers: {'HTTP_ACCEPT' => "application/json"}
##These two pass
expect(response.response_code).to eq 200
expect(response.body).to match(/"email": "registered_user#gmail.com"/)
access_headers = {"access-token" => response.headers["access-token"],
"client" => response.headers["client"],
"expiry" => response.headers["expiry"],
"uid" => response.headers["uid"],
"token-type" => response.headers["token-type"],
'HTTP_ACCEPT' => "application/json"
}
return access_headers
end
end
I have this in spec/support/requests_helper.rb
module RequestsHelper
def get_with_token(path, params={}, headers={})
headers.merge!(retrieve_access_headers)
get path, params: params, headers: headers
#### this outputs the expected headers on a json string and they seem fine ####
puts "headers: "+headers.to_json
end
end
I have included the two helpers in rails_helper.rb as shown below:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.include SessionHelper, type: :request
config.include RequestsHelper, type: :request
end
Finally I have a request spec in spec/request/posts/show_spec.rb
require 'rspec/its'
require 'spec_helper'
require 'rails_helper'
RSpec.describe 'GET /posts/:id', :type => :request do
let(:post) {create(:post)}
let(:id) {post.id}
let(:request_url) {"/posts/#{id}"}
context 'with a registered user' do
it 'has a status code of 200' do
get_with_token request_url
expect(response).to have_http_status(:success)
end
end
end
I expect this to pass but it fails with message:
Failure/Error: expect(response).to have_http_status(:success)
expected the response to have a success status code (2xx) but it was 403
The application works as expected on a browser.
What I'm a doing wrong?

rails 4 mocking session in rspec

gemfile:
source 'https://rubygems.org'
gem 'rails', '4.1.1'
gem 'mysql2
spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment",__FILE__)
require 'rspec/rails'
require "capybara/rspec"
include Capybara::DSL
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
spec/support/controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
# user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
def load_key
session[:master_key] = SecureRandom.hex(24)
end
end
spec/factories/factory.rb
FactoryGirl.define do
factory :user do
email "test#test.com"
password "12345678"
end
end
categories_controller.rb
class CategoriesController < ApplicationController
before_filter :authenticate_user!, :load_key!
def index
#categories = Category.where("user_id is null or user_id = ?", current_user).order(updated_at: :desc)
end
private
def category_params
params.require(:category).permit(:title)
end
end
spec/controllers/categories_controller_spec.rb
require 'rails_helper'
describe CategoriesController do
login_user
load_key
it "get list of categories" do
get :index
expect(response).to render_template("index")
end
end
application_controller.rb
class ApplicationController < ActionController::Base
def load_key!
redirect_to(key_path) unless session[:master_key]
end
end
I would like to bypass a load_key! before_filer in CategoriesController. I added method load_key to controller_macros.rb. But it gives me the error:
undefined local variable or method 'session' for RSpec::ExampleGroups::CategoriesController:Class (NameError) from d:/sites/key_manager/spec/support/controller_macros.rb:19:in `load_key'
Looks like session variable is unavailable under rspec.
The answer was a quite simple.
I should use request.session rather than session.
In this case I should transfer a session variable assign from controller_macros.rb to categories_controller_spec.rb:
categories_controller_spec.rb
require 'rails_helper'
require 'spec_helper'
describe CategoriesController, :type => :controller do
login_user
it "redirect to key form when key was't loaded" do
get :index
expect(response).to redirect_to(key_path)
end
it "view password list #index" do
request.session[:master_key] = SecureRandom.hex(24) # <======================
get :index
expect(response).to render_template(:index)
end
end
controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
# user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
end

Resources