RSpec test for webhook controllers with basic auth - ruby-on-rails

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

Related

RSpec returns empty string in a fairly simple get request

I want to test show action in my Shipment controller. To do so I've prepared fairly simple specs:
RSpec.describe ShipmentsController, type: :controller do
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
ShimpentsController.rb
class ShipmentsController < ApplicationController
before_action :set_product
attr_reader :shipment
def show
#shipment = Shipment.find(params[:id])
#items = shipment&.grouped_shipment_items
end
private
def set_product
#product = Product.find(params[:product_id])
end
end
When I use postman everything went well - it returns expected json but in the RSpec test I'm getting:
response.body
=> ""
I think you need to add render_views in your controller spec file.
RSpec.describe ShipmentsController, type: :controller do
render_views
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
Reference: https://rubyinrails.com/2019/04/11/rails-test-jbuilder-json-response-with-rspec/
I think you are not making a request for JSON response with rspec. You can check by putting a breakpoint in your controller action, then checking
request.format.json?
In order to ask for JSON response from an rspec test, you should add as: :json to the end of the request. The request should look like this:
get :show, params: params, as: :json

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 undefined method `each' in call

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

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.

RSpec: Authenticating before test with devise_auth_token

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)

Resources