Sometimes when I run tests I am getting this error:
TypeError: no implicit conversion of nil into String, which is caused by this code:
params = {
.....
}
auth_headers = {
'Authorization': "Basic #{Base64.encode64('XXX:XXX')}"
}
post back_request_url, params: params, headers: auth_headers
back_request_url path is valid, same params. When the authorization fails, I am getting (as it should be) simply 401 - but, when the authorization passes - then this error happens.
This is something in rails - this action doesn't enter into action provided by back_request_url.
What it could be?
My version of rails is 5.1.3
#FIXED
The issue - Authorization parameters (name and password) has been set in the beginning of the test:
test 'failure no id' do
MyGem.auth_name = 'xxx'
MyGem.auth_pass = 'xxx'
params = {
...
....
(no id)
}
post back_request_url, params: params, headers: #auth_headers
assert_response 400
end
And the action:
class BackRequestController < ApplicationController
protect_from_forgery except: [:new]
http_basic_authenticate_with name: MyGem.auth_name, password: MyGem.auth_pass
...
And they wasn't sometimes set and refreshed, when the test has begun.
Solution
Simply :)
class BackRequestControllerTest < ActionDispatch::IntegrationTest
include Engine.routes.url_helpers
setup do
MyGemauth_name = 'xxx'
MyGem.auth_pass = 'xxx'
#auth_headers = {
'Authorization': "Basic #{Base64.encode64(MyGem.auth_name. + ':' + MyGem.auth_pass)}"
}
end
...
...
...
(TESTS)
Related
I am trying to write a test to ensure that my service, WeeklyReportCardService, is instantiated and that it's method :send_weekly_report_card_for_repositioning is called.
Here's the controller:
def update
Audited.audit_class.as_user($user) do
if #check_in.update(check_in_params)
client = Client.find_by(id: check_in_params[:client_id])
if #check_in.repositioning.present? && #check_in.weigh_in.present? && #check_in.client&.location&.name == "World Wide"
# I see this in the console so the if statement returns true
p "hitting send!!"
WeeklyReportCardService.new.send_weekly_report_card_for_repositioning(#check_in.repositioning)
end
render json: #check_in, status: :ok, serializer: API::CheckInsIndexSerializer
else
render json: #check_in.errors.full_messages, sattus: :unprocessable_entity
end
end
end
Here's my test:
RSpec.describe API::CheckInsController, type: :request do
fit "should send if the client's location is World Wide" do
program = create(:program, :with_client)
worldwide = create(:location, name: "World Wide")
program.client.update(location_id: worldwide.id)
check_in = create(:check_in, client_id: program.client.id, program_id: program.id)
create(:repositioning, check_in_id: check_in.id)
create(:weigh_in, check_in_id: check_in.id)
url = root_url[0..-2] + api_check_in_path(check_in.id) + "?sendReportCardEmail=true"
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
expect_any_instance_of(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
end
end
and the error I see is:
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
Exactly one instance should have received the following message(s) but didn't: send_weekly_report_card_for_repositioning
What else do I need to do to ensure that function is called?
You're doing it in the wrong order. You need to set the expectation first before the method is expected to be called:
expect_any_instance_of(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
If you need to set the expecations afterwards you need to replace the method or object with a spy which is useful if you prefer the arrange-act-assert (or given-when-then)
pattern for structuring tests.
You should also note that the use of any instance is strongly discouraged and you can avoid it by providing a simple class method:
class WeeklyReportCardService
def self.send_weekly_report_card_for_repositioning(...)
new.send_weekly_report_card_for_repositioning(...)
end
end
RSpec.describe API::CheckInsController, type: :request do
it "should send if the client's location is World Wide" do
expect(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
end
end
Or alternatively by stubbing the WeeklyReportCardService#new method to return a mock or spy.
In my Grape API I've got an endpoint which is responsible of receiving data from CMS webhook - it works well but below specs are failed:
describe ::Webhooks::Cms::ReceiveWebhook, type: :request do
subject(:call) { post endpoint, params: params, headers: basic_authorized_headers(auth_params) }
let(:endpoint) { 'webhooks/Cms' }
let(:params) { { some: 'params' } }
let(:auth_params) do
{
username: Rails.application.credentials.cms_user,
password: Rails.application.credentials.cms_password,
}
end
it 'returns a successful response' do
call
expect(response).to be_successful
end
end
helper with basic_authorized_headers method from headers:
module AuthRequestHelpers
def basic_authorized_headers(username: nil, password: nil)
"#{username}:#{password}"
end
end
I'm getting error:
Failure/Error: subject(:call) { post endpoint, params: params, headers: basic_authorized_headers(auth_params) }
NoMethodError:
undefined method `each' for "test#test.com:password":String
Here is my controller:
module Cms
class ReceiveWebhook < Base
desc 'Receive data of CRUD actions from CMS webhook'
http_basic do |user, password|
user == Rails.application.credentials.cms_user &&
password == Rails.application.credentials.cms_password
end
post :cms do
status 200
end
end
end
post expects a hash for the headers param, you're passing a string.
subject(:call) { post endpoint, params: params, headers: { 'Authorization' => basic_authorized_headers(auth_params) } }
Also, usually basic auth requires the "Basic" keyword, and that the credentials be encoded in Base64:
module AuthRequestHelpers
def basic_authorized_headers(username: nil, password: nil)
encoded_credentials = Base64.encode64("#{username}:#{password}")
"Basic #{encoded_credentials}"
end
end
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.
I don't understand what I do wrong. I want to send a simple request to an API, and it didn't work:
class Paytrace
require 'rest-client'
attr_reader :auth_token, :authorize
def initialize()
#auth_token = auth_token
end
def auth_token
response = RestClient.post 'https://api.paytrace.com/oauth/token', { grant_type: :password, username: "loginname", password: "htmlkoi8r" }
puts response
end
def authorize
headers = {:Authorization => "Bearer #{auth_token['access_token']}"}
response1 = RestClient.get('https://api.paytrace.com/v1/transactions/sale/keyed', headers)
puts response1
end
end
a = Paytrace.new
a.authorize
console.log
lucker#lucker-pc:~/git/paytrace-testh$ ruby integration.rb
{"access_token":"c6d69786f6075633:8647d6c6b6f6968327:092e8cfc553726d2b8198577ea2836f41173aae68a53aa1d2af2b2c7f65dcdc7","token_type":"Bearer","expires_in":7200,"created_at":1556098344}
{"access_token":"c6d69786f6075633:8647d6c6b6f6968327:232c92f977a301d033eec321c3d82b73bb65ebec33f9fcc8f6c2d7575c8b0d88","token_type":"Bearer","expires_in":7200,"created_at":1556098346}
Traceback (most recent call last): 1: from integration.rb:25:in
<main>' integration.rb:16:inauthorize': undefined method `[]' for
nil:NilClass (NoMethodError)
Why is the access_token generated twice?
Why is there an undefined method '[]' for nil:NilClass?
Your method auth_token is not returning a response, but a nil (puts returns nil).
Btw, you don't need attr_reader :authorize since you have a method with that name.
Also, as you are setting attr_reader :auth_token, the method auth_token must be rename (and maybe become private).
Change your code to:
class Paytrace
require 'rest-client'
attr_reader :auth_token
def initialize()
#auth_token = get_auth_token
end
def authorize
headers = {:Authorization => "Bearer #{auth_token['access_token']}"}
RestClient.get('https://api.paytrace.com/v1/transactions/sale/keyed', headers)
end
private
def get_auth_token
RestClient.post 'https://api.paytrace.com/oauth/token', { grant_type: :password, username: "loginname", password: "htmlkoi8r" }
end
end
a = Paytrace.new
puts a.auth_token
puts a.authorize
Seems like there are 3 mistakes in this code the 2 puts. line 12 and 20
and the
headers = {:Authorization => "Bearer #{auth_token['access_token']}"}
should be
headers = {:Authorization => "Bearer #{auth_token[:access_token]}"}
or
headers = {:Authorization => "Bearer #{#auth_token[:access_token]}"}
try this code
class Paytrace
require 'rest-client'
attr_reader :auth_token, :authorize
def initialize()
#auth_token = auth_token
end
def auth_token
response = RestClient.post 'https://api.paytrace.com/oauth/token', { grant_type: :password, username: "loginname", password: "htmlkoi8r" }
# puts response
end
def authorize
headers = {:Authorization => "Bearer #{#auth_token[:access_token]}"}
response1 = RestClient.get('https://api.paytrace.com/v1/transactions/sale/keyed', headers)
# puts response1
end
end
a = Paytrace.new
a.authorize
take care the your response hash if you check is
{:access_token=>"c6d69786f6075633:8647d6c6b6f6968327:092e8cfc553726d2b8198577ea2836f41173aae68a53aa1d2af2b2c7f65dcdc7",
:token_type=>"Bearer",
:expires_in=>7200,
:created_at=>1556098344}
and not
{"access_token":"c6d69786f6075633:8647d6c6b6f6968327:092e8cfc553726d2b8198577ea2836f41173aae68a53aa1d2af2b2c7f65dcdc7","token_type":"Bearer","expires_in":7200,"created_at":1556098344}
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