I'm trying to generate API documentation with rwsag, and I'm confused about a couple of my specs failing.
Here is my request spec:-
require 'swagger_helper' ### this includes 'rails_helper'
RSpec.describe 'api/v1/users', type: :request do
before(:each) { host! 'localhost:3001' }
path '/api/v1/users' do
post('create user') do
tags 'users'
consumes 'application/json'
parameter name: :user, in: :body, schema: {
type: :object,
properties: {
title: { type: :string },
description: { type: :string },
date: { type: :datetime },
budget: { type: :decimal },
awarded: { type: :boolean }
},
required: [ 'title', 'description' ]
}
response(200, 'successful') do
after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end
run_test!
end
end
end
Here are my two errors which don't make much sense to me:-
1) api/v1/users /api/v1/users post successful returns a 200 response
Got 0 failures and 2 other errors:
1.1) Failure/Error: super
NoMethodError:
undefined method `user' for #<RSpec::ExampleGroups::ApiV1Users::ApiV1Users::Post::Successful:0x0000aaaad7d06600>
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/request_factory.rb:197:in `build_json_payload'
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/request_factory.rb:180:in `add_payload'
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/request_factory.rb:22:in `block in build_request'
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/request_factory.rb:18:in `tap'
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/request_factory.rb:18:in `build_request'
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/example_helpers.rb:10:in `submit_request'
# /usr/local/bundle/gems/rswag-specs-2.4.0/lib/rswag/specs/example_group_helpers.rb:94:in `block in run_test!'
1.2) Failure/Error: example: JSON.parse(response.body, symbolize_names: true)
NoMethodError:
undefined method `body' for nil:NilClass
# ./spec/requests/api/v1/users_spec.rb:84:in `block (5 levels) in <main>'
And my standard controller action:-
module Api
module V1
class UsersController < ApplicationController
# POST /api/v1/users
def create
#user = Job.new(user_params)
if #user.save
render json: #user, status: :created
else
render json: #user.errors, status: :unprocessable_entity
end
end
Any idea what is causing this error? I'm also a little confused with the generated rswag spec structure - i.e. no expects anywhere.
The answer is that I was missing the below let(:user) line:-
response(201, 'successful') do
after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end
let(:user) { { title: 'foo', description: 'bar' } }
run_test!
end
Related
I have some common parameters which are called in almost all API calls so is it possible to create component for those parameters and call them in rswag api request.
Something like schema '$ref' => '#/definitions/parameters'
Thanks!
Add in your swagger_helper.rb
Example:
# spec/swagger_helper.rb
config.swagger_docs = {
'v1/swagger.json' => {
swagger: '2.0',
info: {
title: 'API V1'
},
definitions: {
errors_object: {
type: 'object',
properties: {
errors: { '$ref' => '#/definitions/errors_map' }
}
},
errors_map: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
}
}
}
}
# spec/integration/blogs_spec.rb
describe 'Blogs API' do
path '/blogs' do
post 'Creates a blog' do
response 422, 'invalid request' do
schema '$ref' => '#/definitions/errors_object'
...
end
# spec/integration/comments_spec.rb
describe 'Blogs API' do
path '/blogs/{blog_id}/comments' do
post 'Creates a comment' do
response 422, 'invalid request' do
schema '$ref' => '#/definitions/errors_object'
...
end
From: https://www.rubydoc.info/github/domaindrivendev/rswag#referenced-parameters-and-schema-definitions
You need to define your object in spec/swagger_helper.rb
Then in integration spec file define
path '/api/client/v0/blog' do
put 'Create a blog' do
tags :Blog
include_examples 'header_with_recognition_definitions'
parameter name: :input_param, in: :body, schema: { '$ref' => '#/definitions/input_parameter_object' }
response 200, 'blog was created successfully' do
include_examples 'header_with_recognition_lets'
...
run_test!
end
end
I have a rails backend api application integrated with auth0 service that only verifies validity of auth_token received from frontend application. After securing all backend api endpoints all my tests fail with a result "Not Authenticated", which is how it should be. However I cannot figure out how to get through the authentication and to not require it for rspec tests. Here are my classes:
projects_controller_spec.rb
require "rails_helper"
RSpec.describe Api::V1::ProjectsController, :type => :controller do
describe 'GET /api/v1/organizations/1/projects' do
let!(:organization) { create(:organization_with_projects) }
before { get :index, params: { organization_id: organization } }
context 'when authorized' do
it 'should return JSON objects' do
expect(json['projects'].count).to equal(3)
end
it { expect(response).to have_http_status(:ok) }
it { expect(response.content_type).to include('application/json') }
end
describe 'POST /api/v1/organizations/1/projects' do
let!(:organization) { create(:organization) }
let(:project) { organization.projects.first }
before { post :create, params: { organization_id: organization, project: attributes_for(:project) } }
context 'when authorized' do
it { expect(response).to have_http_status(:created) }
it { expect(response.content_type).to include("application/json") }
it { expect(json).to eq(serialized(project)) }
end
end
end
application_controller.rb
class ApplicationController < ActionController::API
include Pundit
include Secured
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found(error)
render json: { error: error.message }, status: :not_found
end
end
concerns/secured.rb
module Secured
extend ActiveSupport::Concern
included do
before_action :authenticate_request!
end
private
def authenticate_request!
# Create user if not existing
pundit_user
auth_token
rescue JWT::VerificationError, JWT::DecodeError
render json: { errors: ['Not Authenticated'] }, status: :unauthorized
end
def http_token
if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
end
end
def auth_token
JsonWebToken.verify(http_token)
end
def pundit_user
User.create_from_token_payload({token: auth_token[0], organization_id:
request.parameters['organization_id']})
end
end
lib/json_web_token.rb
require 'net/http'
require 'uri'
class JsonWebToken
def self.verify(token)
JWT.decode(token, nil,
true, # Verify the signature of this token
algorithm: 'RS256',
iss: 'https://xxx.auth0.com/',
verify_iss: true,
aud: Rails.application.secrets.auth0_api_audience,
verify_aud: true) do |header|
jwks_hash[header['kid']]
end
end
def self.jwks_hash
jwks_raw = Net::HTTP.get URI("https://xxx.auth0.com/.well-known/jwks.json")
jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
Hash[
jwks_keys
.map do |k|
[
k['kid'],
OpenSSL::X509::Certificate.new(
Base64.decode64(k['x5c'].first)
).public_key
]
end
]
end
end
It looks like I found a solution by adding the following line into every controller spec file:
before { allow(controller).to receive(:authenticate_request!).and_return(true) }
Hi i am working on rails application with ruby 2.5.0 and rails 5. I have written an api to check user exist with provided username and token.
check_token_controller.rb
class CheckTokenController < ApplicationController
def create
begin
user = User.where(email: check_params[:username], token: check_params[:token]).first
if user.blank?
render json: {},
status: 401
else
render json: {},
status: 200
end
rescue => e
render json: {},
status: 500
end
end
private
def check_params
permitted = %i[username token]
params.require(:data)
.require(:attributes)
.permit(permitted)
.transform_keys(&:underscore)
end
end
now i want to test this api in my spec.rb file.
*spec/controllers/check_token_controller_spec.rb
require 'rails_helper'
describe CheckTokenController do
let(:user) { instance_double('user') }
let(:save_result) { true }
let(:params) do
{ data: { attributes: { fullname: 'michael febrianto',email: 'saddam#gmail.com', token: 'rWCyRUgfLODuc8B4DvA_8w',password: 'password' } } }
end
before do
allow(User).to receive(:new).and_return(user)
allow(user).to receive(:save).and_return(save_result)
end
let(:params) do
{ data: { attributes: { username: 'saddam#gmail.com', token: 'rWCyRUgfLODuc8B4DvA_8w' } } }
end
describe 'POST create' do
subject { post :create, params: params }
context 'when success' do
it { is_expected.to have_http_status(200) }
end
context 'when failed' do
it { is_expected.to have_http_status(401) }
end
end
end
i am first time working with rspec now whenever i run this test it doesnot create any test data i checked with debugger. please help me how can i create a test data and then i can test my api. Thanks in advance.
Please go through the books:
Everyday Rails Testing with RSpec
The RSpec Book: Behaviour-Driven Development
They might help you.
I am using wash_out to create a soap webservice, this is my controller:
class SignerController < ApplicationController
soap_service
soap_action 'xmlSign', args: {
xml: :string,
certificate: :string
},
return: {
signed_xml: :string,
error: :string
},
to: :xml_sign
def xml_sign
render soap: {
signed_xml: "TODO"
}
rescue
render soap: {
error: $!.message
}
end
end
following http://blog.johnsonch.com/2013/04/18/rails-3-soap-and-testing-oh-my/ I wrote this test:
require 'rails_helper'
require 'savon'
RSpec.describe SignerController, type: :controller do
HTTPI.adapter = :rack
HTTPI::Adapter::Rack.mount 'app', Signer::Application
it 'signs an xml' do
application_base = "http://app"
client = Savon::Client.new({:wsdl => application_base + signer_wsdl_path })
result = client.call(:xmlSign, xml: 'xml', certificate: 'cert')
result.body[:xml_sign][:value].should_not be_nil
end
end
but rspec always throws this error:
NoMethodError:
undefined method `element_children' for nil:NilClass
# ./spec/controllers/signer_controller_spec.rb:12:in `block (2 levels) in <top (required)>'
I am using rails 5.1.5, wash_out 0.12, httpi 2.4.2 and savon 2.11.2
how can I fix this?
What worked for me was adding "render_views" to the controller spec. For example
RSpec.describe SoapController, type: controller do
render_views # added render_views
HTTPI.adapter = :rack
HTTPI::Adapter::Rack.mount 'application', PhoneDialogue::Application
it 'signs an xml' do
application_base = "http://app"
client = Savon::Client.new({:wsdl => application_base + soap_wsdl_path })
result = client.call(:xmlSign, xml: 'xml', certificate: 'cert')
result.body[:xml_sign][:value].should_not be_nil
end
end
I can't seem to get this test to pass and I don't understand why.
controller_spec.rb:
require 'rails_helper'
RSpec.describe QuotationRequestsController, type: :controller do
describe "GET download" do
it "streams the sample text as a text file" do
#setup
quotation_request = create(:quotation_request)
file_options = {filename: "#{quotation_request.id}-#{quotation_request.client.name.parameterize}.txt", type: 'plain/text', disposition: 'attachment'}
#exercise
get :download, id: quotation_request
#verification
expect(#controller).to receive(:send_data).with(file_options) {#controller.render nothing: true}
end
end
end
controller:
def download
#quotation_request = QuotationRequest.find(params[:id])
send_data #quotation_request.sample_text, {
filename: #quotation_request.sample_text_file,
type: "text/plain",
disposition: "attachment"
}
end
Output of test:
1) QuotationRequestsController GET download streams the sample text as a text file
Failure/Error: expect(#controller).to receive(:send_data).with(file_options) {
#controller.render nothing: true
}
(# <QuotationRequestsController:0x007ff35f926058>).send_data({
:filename=>"1-peter-johnson.txt",
:type=>"plain/text",
:disposition=>"attachment"
})
expected: 1 time with arguments: ({
:filename=>"1-peter-johnson.txt",
:type=>"plain/text", :disposition=>"attachment"
})
received: 0 times
# ./spec/controllers/quotation_requests_controller_spec.rb:380:in `block (3 levels) in <top (required)>'
# -e:1:in `<main>'
You should pass 2 arguments
expect(#controller).to receive(:send_data).with(quotation_request.sample_text, file_options) {#controller.render nothing: true}
#exercise
get :download, id: quotation_request
#verification
expect(#controller).to receive(:send_data).with(file_options) {#controller.render nothing: true}
This is backwards. The expectation should come before the method call.
You write following:
Get file
Mock file
But right case is inverted:
Mock file
Get file
try following (using before):
require 'rails_helper'
RSpec.describe QuotationRequestsController, type: :controller do
describe "GET download" do
let(:quotation_request) { create(:quotation_request) }
let(:file_options) { {filename: "#{quotation_request.id}-#{quotation_request.client.name.parameterize}.txt", type: 'plain/text', disposition: 'attachment'} }
before do
expect(#controller).to receive(:send_data)
.with(file_options) { #controller.render nothing: true }
end
it "streams the sample text as a text file" do
get :download, id: quotation_request
end
end
end
Rails 5:
expect(#controller).to receive(:send_data).with(quotation_request.sample_text, file_options) {#controller.head :ok}
Ref: https://stackoverflow.com/a/43428992