RSpec: Authenticating before test with devise_auth_token - ruby-on-rails

I am using devise_auth_token to authenticate users for an API. I would like to authenticate users before each test is run, but keep getting a 401 error. When I use postman to the endpoint with the correct headers, it works, but fails to work during tests.
before(:each) do
#user = FactoryGirl.create(:user)
end
def get_auth
headers = #user.create_new_auth_token
auth = Hash.new
auth["client"] = headers["client"]
auth["access-token"] = headers["access-token"]
auth["uid"] = headers["uid"]
auth["expiry"] = headers["expiry"]
return auth
end
it "auth user should return success" do
get 'get_tasks_for_user', params: {uid: #user.uid}, headers: get_auth
expect(response).to have_http_status(200)
end
RSpec
TasksController auth user should return success
Failure/Error: expect(response).to have_http_status 200
expected the response to have status code 200 but it was 401

You can use a helper method
#/spec/support/helpers/session_helper.rb
module SessionHelper
def set_request_headers(resp)
{ 'ACCEPT' => "application/json",
'Content-Type' => "application/json",
'access-token' => resp['access-token'],
'token-type' => resp['token-type'],
'client' => resp['client'],
'expiry' => resp['expiry'],
'uid' => resp['uid']
}
end
def subdomain_login(uid, password, subdomain)
request_params = {
'email' => uid,
'password' => password
}
host! "#{subdomain}.lvh.me"
post "/portal/auth/sign_in", params: request_params
return set_request_headers(response.headers)
end
end
Make sure you have the following entry in your /spec/rails_helper
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
config.include SessionHelper, :type => :request
end
You can use subdomain_login in your tests. Here is an Rspec example.
post '/portal/system/locations', params: request_params.to_json,
headers: subdomain_login(user_id, password, subdomain)

Related

RSpec request spec for post action that includes headers results in parms being dropped

I am trying to figure out why I can't use use both params and headers in an request spec.
What works:
RSpec.describe Api::V1::UsersController, :type => :request do
before { host! 'api.localhost:3000'}
let(:params) {
{
"user": {
"identifier_for_vendor": "BD43813E"
}
}
}
describe 'Post /users' do
context 'when request is valid' do
before {
post api_users_path,
params: params
}
it "is successful" do
expect(response).to be_successful
end
end
end
end
What does not:
RSpec.describe Api::V1::UsersController, :type => :request do
let(:params) {
{
"user": {
"identifier_for_vendor": "BD43813E"
}
}
}
let(:headers) {
{
"host": "api.localhost:3000",
"Accept": "application/vnd.domain_name.v1",
"Content-Type": "application/vnd.api+json",
"X-API-Key": "fake087uakey"
}
}
describe 'Post /users' do
context 'when request is valid' do
before {
post api_users_path,
params: params,
headers: headers
}
it "successful" do
expect(response).to be_successful
end
end
end
end
The above fails, returning the error:
1) Api::V1::UsersController Post /users when request is valid is successful
Failure/Error: params.require(:user).permit(:identifier_for_vendor)
ActionController::ParameterMissing:
param is missing or the value is empty: user
The headers are needed due to having to ensure that valid api-keys are included in the request.
Would appreciate feedback on what I am missing. Thank you
Versions:
Ruby version: 2.6.3
Rails version: 6.0.3.4
RSpec 3.10
So issue had to do with how params and headers objects are created.
Params:
I passed in:
{"user": {"identifier_for_vendor": "OFJPJ"} }
the correct object is:
{:params=>{:user=>{:identifier_for_vendor=>"OFJPJ"}}}
Headers:
I passed in:
{
"host": "api.localhost:3000",
"Accept": "application/vnd.domain_name.v1",
"Content-Type": "application/vnd.api+json",
"X-API-Key": "fake087uakey"
}
the correct object is:
{
"headers" => {
"host" => "api.localhost:3000",
"Accept" => "application/vnd.domain_name.v1",
"X-API-Key" => "api_key"
}
}
Final solution looks like this:
RSpec.describe Api::V1::UsersController, :type => :request do
describe 'Post /users' do
context 'when request is valid' do
before do
post api_users_path,
:params => params,
:headers => headers
end
it "is successful" do
expect(response).to be_successful
end
it "returns a data of type user" do
expect(json_data["type"]).to eq("user")
end
end
end
end
The key to figuring this out was reading the documentation and realizing the my formatting was wrong.

RSpec test for webhook controllers with basic auth

In my Rails/Grape app I created a webhook controller which receive JSON from CMS webhook. I'm just wondering how to test it in RSpec if I don't have any params (I guess I don't need it because I only receive JSON from webhook).
My webhook controller (it works well):
module Cms
class Webhook < Base
desc 'Take the CMS webhook'
http_basic do |user, password|
user == ENV['USER'] && password == ENV['PASSWORD']
end
post :receive do
params
end
end
end
I was trying to like:
describe Cms::Webhooks, type: :request do
subject(:call) { post endpoint, params: params, as: :json }
let(:endpoint) { '/api/cms/webhooks/receive' }
let(:params) do
{
some: 'some pass'
}
end
it 'returns a successful response' do
call
expect(response).to be_successful
end
end
I'm getting an error:
Failure/Error: expect(response).to be_successful
expected `#<ActionDispatch::TestResponse:0x00007f9058e43e60 #mon_data=#<Monitor:0x00007f9058e43de8>, #mon_data_..., #method=nil, #request_method=nil, #remote_ip=nil, #original_fullpath=nil, #fullpath=nil, #ip=nil>>.successful?` to return true, got false
Can you try this code?
describe Cms::Webhooks, type: :request do
subject(:call) { post endpoint, params: params, as: :son, headers: headers }
let(:endpoint) { '/api/cms/webhooks/receive' }
let(:params) do
{
some: 'some pass'
}
end
let(:headers) do
{
'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('your_username', 'your_password')
}
end
it 'returns a successful response' do
call
expect(response).to be_successful
end
end
Ref https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic.html

Rspec for Shopify controllers? Stuck on a 302 for authentication

My project is a Rails 5.2 app, running Ruby 2.6, and uses the shopify_gem and factory_bot_rails.
I have a controller that inherits from ShopifyController. My unit tests for controllers are stuck at a 302. I'm unable to figure out how to get past authentication...
I've tried these tutorials and other links, but no luck:
http://www.codeshopify.com/blog_posts/testing-shopify-authenticated-controllers-with-rspec-rails
https://community.shopify.com/c/Shopify-APIs-SDKs/Testing-a-Rails-app-created-through-shopify-app-gem/td-p/337337
https://github.com/Shopify/shopify_app/issues/445
https://github.com/Shopify/shopify_app/issues/731
My controller test is below
require 'rails_helper'
describe OnboardingController, type: :controller do
before do
shop = FactoryBot.create(:shop)
request.env['rack.url_scheme'] = 'https'
#request.session[:shopify] = shop.id
#request.session[:shopify_domain] = shop.shopify_domain
end
it 'onboards correctly', :focus do
get :onboard_completed
expect(response).to have_http_status(:success)
end
end
I was also playing with this code, but it failed (errors in comments):
module ShopifyHelper
def login(shop)
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:shopify,
provider: 'shopify',
uid: shop.shopify_domain,
credentials: { token: shop.shopify_token })
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:shopify]
get "/auth/shopify" # this leads to a UrlGenerationError
follow_redirect! # this is an undefined method. Seems to be a MiniTest thing
end
end
require 'rails_helper'
RSpec.describe "Home", type: :request do
def login(shop)
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:shopify,
provider: 'shopify',
uid: shop.shopify_domain,
credentials: { token: shop.shopify_token })
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:shopify]
get "/auth/shopify"
follow_redirect!
#request.session[:shopify] = shop.id
#request.session[:shopify_domain] = shop.shopify_domain
end
describe "GET /" do
it "works!" do
shop = Shop.first || create(:shop)
login(shop)
get root_path
shop.with_shopify!
expect(assigns(:products)).to eq ShopifyAPI::Product.find(:all, params: { limit: 10 })
expect(response).to render_template(:index)
expect(response).to have_http_status(200)
end
end
end
Something like this works for me, your getting the errors in your function probably because you do not have get and follow_redirect! functions defined in your ShopifyHelper module context.
Reference: http://www.codeshopify.com/blog_posts/testing-shopify-authenticated-controllers-with-rspec-rails
This ended up being the working solution
require 'rails_helper'
describe WizardController, type: :controller do
before do
shop = FactoryBot.create(:shop)
request.env['rack.url_scheme'] = 'https'
allow(shop).to receive(:wizard_completed?).and_return(false)
allow(Shop).to receive(:current_shop).and_return(shop)
# #note: my original code had "session[:shopify]" of "session[:shop]", which was the error
session[:shop_id] = shop.id
session[:shopify_domain] = shop.shopify_domain
end
it 'enter test here', :focus do
get :wizard
expect(response).to have_http_status(:success)
end
end
This worked for me:
# File: spec/support/request_helper.rb
def shopify_login(shop)
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:shopify, provider: 'shopify', uid: shop.myshopify_domain,
credentials: { token: shop.api_token })
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:shopify]
get "/auth/shopify/callback?shop=#{shop.myshopify_domain}"
follow_redirect!
#request.session[:shopify] = shop.shopify_id
#request.session[:shop_id] = shop.id
#request.session[:shopify_domain] = shop.myshopify_domain
end
Btw, testing controllers are deprecated in favour of requests.
RSpec.describe 'ShopsController', type: :request do
let(:shop) { FactoryBot.build :shop }
let(:plan) { FactoryBot.build :enterprise_plan }
let(:subscription) { FactoryBot.create :subscription, shop: shop, plan: plan }
describe 'GET#product_search' do
it 'returns a successful 200 response for listing action do' do
VCR.use_cassette('shop-search-product', record: :new_episodes) do
new_subscrip = subscription
shopify_login(new_subscrip.shop)
get product_search_path, { params: { query: 'bike' } }
json = JSON.parse(response.body)
expect(response).to be_successful
expect(json.length).to eq(7)
end
end
end
Remember to setup "admin { true }" in your shop's FactoryBot if you are using the 'shopify_app' gem.

Invalid access_token when using RSpec request specs to authorize a request

I'm trying to test CredentialsController, which works fine in production, using RSpec request specs.
Code
Controller
class CredentialsController < ApplicationController
before_action :doorkeeper_authorize!
def me
render json: current_user
end
end
(GET /me routes to CredentialsController#me.)
Request Specs
describe 'Credentials', type: :request do
context 'unauthorized' do
it "should 401" do
get '/me'
expect(response).to have_http_status(:unauthorized)
end
end
context 'authorized' do
let!(:application) { FactoryBot.create(:application) }
let!(:user) { FactoryBot.create(:user) }
let!(:token) { FactoryBot.create(:access_token, application: application, resource_owner_id: user.id) }
it 'succeeds' do
get '/me', params: {}, headers: {access_token: token.token}
expect(response).to be_successful
end
end
end
The unauthorized test passes, but the authorized test fails:
expected #<ActionDispatch::TestResponse:0x00007fd339411248 #mon_mutex=#<Thread::Mutex:0x00007fd339410438>, #mo..., #method=nil, #request_method=nil, #remote_ip=nil, #original_fullpath=nil, #fullpath=nil, #ip=nil>>.successful? to return true, got false
The headers indicate a problem with the token:
0> response.headers['WWW-Authenticate']
=> "Bearer realm=\"Doorkeeper\", error=\"invalid_token\", error_description=\"The access token is invalid\""
token looks okay to me, though:
0> token
=> #<Doorkeeper::AccessToken id: 7, resource_owner_id: 8, application_id: 7, token: "mnJh2wJeEEDe0G-ukNIZ6oupKQ7StxJqKPssjZTWeAk", refresh_token: nil, expires_in: 7200, revoked_at: nil, created_at: "2020-03-19 20:17:26", scopes: "public", previous_refresh_token: "">
0> token.acceptable?(Doorkeeper.config.default_scopes)
=> true
Factories
Access Token
FactoryBot.define do
factory :access_token, class: "Doorkeeper::AccessToken" do
application
expires_in { 2.hours }
scopes { "public" }
end
end
Application
FactoryBot.define do
factory :application, class: "Doorkeeper::Application" do
sequence(:name) { |n| "Project #{n}" }
sequence(:redirect_uri) { |n| "https://example#{n}.com" }
end
end
User
FactoryBot.define do
factory :user do
sequence(:email) { |n| "email#{n}#example.com" }
password { "test123" }
password_confirmation { "test123" }
end
end
Questions
Why am I getting invalid_token on this request?
Do my Doorkeeper factories look correct?
I was passing the token wrong. Instead of:
get '/me', params: {}, headers: {access_token: token.token}
I had to use:
get '/me', params: {}, headers: { 'Authorization': 'Bearer ' + token.token}
You can check your Access Token factory's scopes, It should be same as initializer's default_scopes
e.g.
config/initializers/doorkeeper.rb
default_scopes :read
Below, your Access Token factory's scopes should be
factory :access_token, class: "Doorkeeper::AccessToken" do
sequence(:resource_owner_id) { |n| n }
application
expires_in { 2.hours }
scopes { "read" }
end
Additionally, if you encountered response status: 406 while get '/me'....
It means that the requested format (by default HTML) is not supported. Instead of '.json' you can also send Accept="application/json" in the HTTP header.
get '/me', params: {}, headers: {
'Authorization': 'Bearer ' + token.token,
'Accept': 'application/json'}
I resolved my problem with this solution, maybe you can try it.

What is the proper way to grab Authorization header from controller request object?

I have two RSpec tests, a controller spec and a request spec, where I am making a GET request to the index action of the same controller. In both specs I am sending an Authorization header that contains an Oauth2 bearer token.
The problem I'm having is that depending on the type of spec, the header is stored on a different property of the request object. In the case of the request spec, it is available at request.env["Authorization"] and in the case of the controller spec, it is available at request.session["Authorization"].
Why is "Authorization" stored in different places for different types of specs? Is there somewhere I can find it for both specs?
This bearer_token method is in the parent controller class where I'm grabbing the token from the header:
Works with env in the request specs:
def bearer_token
pattern = /^Bearer /
header = request.env["Authorization"] # <= env
header.gsub(pattern, '') if header && header.match(pattern)
end
Works with session in the controller specs:
def bearer_token
pattern = /^Bearer /
header = request.session["Authorization"] # <= session
header.gsub(pattern, '') if header && header.match(pattern)
end
Here is my request spec:
describe '' do
let(:user) { Fabricate(:user) }
describe 'accessing content with valid token' do
let(:token) { OauthToken.create(user: user) }
let(:auth_headers) { {
'Authorization' => "Bearer #{token.access_token}",
'HTTPS' => 'on'
} }
before { get api_v2_cats_path, {}, auth_headers }
specify { response.status.should == 200 }
end
end
Here is my controller spec
describe Api::V2::CatsController do
let(:user) { Fabricate(:user) }
describe ".index" do
let(:token) { OauthToken.create(user: user) }
let(:auth_headers) { {
'Authorization' => "Bearer #{token.access_token}",
'HTTPS' => 'on'
} }
it "should be valid" do
get :index, { format: :json, page_size: 1 }, auth_headers
#json = JSON.parse(response.body)
#json.should_not be_nil
end
end
end
I assumed that the API would be the same for the get method between a request and controller spec. In the controller spec, the third argument is a hash of sessions variables, not header variables. You can set the headers directly on the #request object like so:
describe Api::V2::CatsController do
let(:user) { Fabricate(:user) }
describe ".index" do
let(:token) { OauthToken.create(user: user) }
let(:auth_headers) { {
'Authorization' => "Bearer #{token.access_token}",
'HTTPS' => 'on'
} }
before do
#request.env.merge!(auth_headers)
end
it "should be valid" do
get :index, { format: :json, page_size: 1 }
#json = JSON.parse(response.body)
#json.should_not be_nil
end
end
end
Then the correct way to get the authorization header is using:
def bearer_token
pattern = /^Bearer /
header = request.env["Authorization"] # <= env
header.gsub(pattern, '') if header && header.match(pattern)
end
I found this.
https://github.com/rails/rails/commit/cf9d6a95e805bdddfa9c6b541631d51b3165bf23#diff-10b31f2069dfc4810229c8d60c3a4cda
in your controller, you can do something like this to get the header value.
def index
header_value = request.authorization
end

Resources