I'm new on rswag. I am trying to use it for generate login and logout paths. I could create an integration file for /login. But logout is making me crazy:
require 'swagger_helper'
describe 'Sessions API' do
path '/login' do
post 'Create user session' do
tags 'Sessions'
produces 'application/json'
consumes 'application/json'
description 'Generate an Authorization Token from User data'
parameter name: :params, in: :body, schema: {
type: :object,
properties: {
email: { type: :string },
password: { type: :string }
},
required:[:email, :password]
}
let(:session){User.create(email: 'user#example.co', password: '123456')}
response 200, 'success' do
consumes 'application/json'
let(:params){{ email: session.email, password: session.password }}
run_test!
end
response 400, 'bad request' do
let(:params) { 'unauthorized' }
run_test!
end
end
end
# here is where my pain starts
path '/logout' do
delete 'destroy user session' do
tags 'Sessions'
consumes 'application/json'
description 'Delete an Authorization Token given from User data'
response 204, 'No content' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
run_test!
end
end
end
end
This is my rspec test for logout (this works)
describe "Testing logout" do
it 'returns status no content' do
request.headers['Content-Type'] = 'application/json'
post :create, params: { email: #user1.email, password: #user1.password }
delete :destroy
expect(response.cookies["auth_token"]).to be_nil
expect(response).to have_http_status(:no_content)
end
end
I've been looking for docs about how to manage destroy methods in rswag, but nothing has been useful. How could I create this DELETE destroy method in my rswag-specification?
Related
I am trying to get devise and devise-jwt gems to work so I can implement Authorization into my API only Rails app.
I have installed both devise and devise-jwt gems.
I followed the instructions on this blog post:
https://medium.com/#mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03
I have implemented the request specs that the author has included in his post and I can't get it approved on "Deleted",
I have to pass the authorizate token on delete, but I'm not getting it.
Any suggestion?
require 'rails_helper'
require "json"
RSpec.describe "POST /users", type: :request do
let(:user) { create(:user) }
let(:url) { '/users/sign_in' }
let(:params) do
{
user: {
email: user.email,
password: user.password
}
}
end
context 'when params are correct' do
before do
post url, params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
it 'returns 200' do
expect(response).to have_http_status(200)
end
it 'returns JTW token in authorization header' do
expect(response.headers['authorization']).to be_present
end
end
context 'when login params are incorrect' do
before { post url }
it 'returns unathorized status' do
expect(response.status).to eq 401
end
end
end
RSpec.describe 'DELETE /logout', type: :request do
let(:url) { '/users/sign_out' }
it 'returns 204, no content' do
delete url
expect(response).to have_http_status(201)
end
end
I need to pass the user's token on delete, any suggestions on how I can be doing this?
I am trying to get devise and devise-jwt gems to work so I can implement Authorization into my API only Rails app.
I have installed both devise and devise-jwt gems.
I followed the instructions on this blog post:
https://medium.com/#mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03
I implemented the requests specs the author included in his post, and I can't get them to pass. If I put a byebug into the session controller, I see that it's saying the "User needs to sign in or sign up before continuing."
Any thoughts on what I'm doing incorrectly?
Here are the relevant files:
routes.rb
Rails.application.routes.draw do
namespace :api, path: '', defaults: {format: :json} do
namespace :v1 do
devise_for :users,
path: '',
path_names: {
sign_in: 'signin',
sign_out: 'signout',
registration: 'signup'
}
...
end
end
controllers/api/v1/sessions_controller.rb
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
private
def respond_with(resource, _opts = {})
render json: resource
end
def respond_to_on_destroy
head :no_content
end
end
models/user.rb
class User < ApplicationRecord
devise :confirmable, :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :jwt_authenticatable, jwt_revocation_strategy: JwtBlacklist
...
end
models/jwt_blacklist.rb
class JwtBlacklist < ApplicationRecord
include Devise::JWT::RevocationStrategies::Blacklist
self.table_name = 'jwt_blacklist'
end
config/initializers/devise.rb
Devise.setup do |config|
# Setup for devise JWT token authentication
config.jwt do |jwt|
jwt.secret = Rails.application.secret_key_base
jwt.dispatch_requests = [
['POST', %r{^*/signin$}]
]
jwt.revocation_requests = [
['DELETE', %r{^*/signout$}]
]
jwt.expiration_time = 1.day.to_i
end
config.navigational_formats = []
...
end
spec/request/authentication_spec.rb
require 'rails_helper'
describe 'POST /v1/signin', type: :request do
let(:user) { create(:user) }
let(:url) { '/v1/signin' }
let(:params) do
{
user: {
email: user.email,
password: user.password
}
}
end
context 'when params are correct' do
before do
post url, params: params
end
it 'returns 200' do
expect(response).to have_http_status(200)
end
it 'returns JTW token in authorization header' do
expect(response.headers['Authorization']).to be_present
end
it 'returns valid JWT token' do
decoded_token = decoded_jwt_token_from_response(response)
expect(decoded_token.first['sub']).to be_present
end
end
context 'when login params are incorrect' do
before { post url }
it 'returns unathorized status' do
expect(response.status).to eq 401
end
end
end
describe 'DELETE /v1/signout', type: :request do
let(:url) { '/v1/signout' }
it 'returns 204, no content' do
delete url
expect(response).to have_http_status(204)
end
end
I would expect the tests to pass, but I get the following errors:
Test Failures
Failures:
1) POST /v1/signin when params are correct returns 200
Failure/Error: expect(response).to have_http_status(200)
expected the response to have status code 200 but it was 401
# ./spec/request/authentication_spec.rb:21:in `block (3 levels) in <top (required)>'
2) POST /v1/signin when params are correct returns JTW token in authorization header
Failure/Error: expect(response.headers['Authorization']).to be_present
expected `nil.present?` to return true, got false
# ./spec/request/authentication_spec.rb:25:in `block (3 levels) in <top (required)>'
3) POST /v1/signin when params are correct returns valid JWT token
Failure/Error: decoded_token = decoded_jwt_token_from_response(response)
NoMethodError:
undefined method `decoded_jwt_token_from_response' for #<RSpec::ExampleGroups::POSTV1Signin::WhenParamsAreCorrect:0x00007fec3d3ae158>
# ./spec/request/authentication_spec.rb:29:in `block (3 levels) in <top (required)>'
Finished in 0.76386 seconds (files took 3.31 seconds to load)
5 examples, 3 failures
Failed examples:
rspec ./spec/request/authentication_spec.rb:20 # POST /v1/signin when params are correct returns 200
rspec ./spec/request/authentication_spec.rb:24 # POST /v1/signin when params are correct returns JTW token in authorization header
rspec ./spec/request/authentication_spec.rb:28 # POST /v1/signin when params are correct returns valid JWT token
I don't know if you found a solution; but I leave an approach I've made; It might helpfull.
Taking special attetion to the problem, The solution was to change:
decoded_token = decoded_jwt_token_from_response(response)
To:
decoded_token = JWT.decode(response.headers['authorization'].split(' ').last, Rails.application.credentials.jwt_secret, true)
Beacuse I din't find any in the documentation or other place and I chose to decode with method provided by JWT.
Also if you see I handle the requests in a different way, but I think that is not a problem at all.
require 'rails_helper'
require "json"
RSpec.describe "POST /login", type: :request do
let(:user) { User.create!( username: 'usertest',
email: 'usertest#email.com',
password: 'passwordtest123',
password_confirmation: 'passwordtest123') }
let(:url) { '/users/login' }
let(:params) do
{
user: {
login: user.email,
password: user.password
}
}
end
context 'when params are correct' do
before do
post url, params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
it 'returns 200' do
expect(response).to have_http_status(200)
end
it 'returns JTW token in authorization header' do
expect(response.headers['authorization']).to be_present
end
it 'returns valid JWT token' do
token_from_request = response.headers['Authorization'].split(' ').last
decoded_token = JWT.decode(token_from_request, Rails.application.credentials.jwt_secret, true)
expect(decoded_token.first['sub']).to be_present
end
end
context 'when login params are incorrect' do
before { post url }
it 'returns unathorized status' do
expect(response.status).to eq 401
end
end
end
RSpec.describe 'DELETE /logout', type: :request do
let(:url) { '/users/logout' }
it 'returns 204, no content' do
delete url
expect(response).to have_http_status(204)
end
end
RSpec.describe 'POST /signup', type: :request do
let(:url) { '/users/signup' }
let(:params) do
{
user: {
username: 'usertest2',
email: 'usertest2#email.com',
password: 'passwordtest123',
password_confirmation: 'passwordtest123'
}
}
end
context 'when user is unauthenticated' do
before {
post url,
params: params.to_json,
headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
}
it 'returns 201' do
expect(response.status).to eq 201
end
it 'returns a new user' do
expect(response).to have_http_status :created
end
end
context 'when user already exists' do
before do
post url,
params: params.to_json,
headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
post url,
params: params.to_json,
headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
it 'returns bad request status' do
expect(response.status).to eq 400
end
it 'returns validation errors' do
expect(response_body['errors'].first['title']).to eq('Bad Request')
end
end
end
PD: I leave the spec code for register, with a couple differences (requests, url, username params in User model (that's is why I use the login param y the login request), I made all in a sigle spec.rb file, ...) to https://medium.com/#mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03. Kepp that in mind.
I believe you need to use the helper sign_in user before making the request for it to be authorized. Check https://github.com/heartcombo/devise, Controller tests
I am trying to test some PUT/PATCH endpoint from my API, but my 'record_to_update' is not chaging as expect.
I organize the spec as follow using Rails 5.2 and RSpec 3.8:
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
context 'when request with no valid headers' do
...
...
...
end
context 'when resquest with valid headers' do
before do
request.accept = 'application/vnd.api+json'
request.content_type = 'application/vnd.api+json'
2.times do
create(:user)
end
end
describe 'PATCH/PUT /users/:id' do
let(:record_to_update) { create(:user) }
let(:user_params) do
{ id: first_user.id, name: 'goku', email: 'goku#bol.com' }
end
before do
put :update, params: { id: record_to_update, user: user_params }
record_to_update.reload
end
it 'should update user' do
expect(record_to_update.name).to eq('goku')
expect(response.status).to eq 200
end
end
end
end
As I said the problem here that I am facing is that 'record_to_update' is not chaging. I already test the same PUT/PATCH endpoint in the same API with Postman and successfully updated from there. What am I doing wrong here?
Thanks in advance.
you should move the before block to it 'should update user'
before do
put :update, params: { id: record_to_update.id, user: user_params }
end
So it becoming
it 'update user' do
put :update, params: { id: record_to_update, user: user_params }
expect ...
record.reload!
....
end
I have a Rails 5 API only app and using knock to do JWT authenticate.
After complete the model and model spec, I start to do the request spec.
But I have no idea how to complete the authentication inside the request spec in the right way,
My users controller,
module V1
class UsersController < ApplicationController
before_action :authenticate_user, except: [:create]
end
end
Application controller,
class ApplicationController < ActionController::API
include Knock::Authenticable
include ActionController::Serialization
end
My stupidest solution (call the get token request to get the JWT before the rest request),
context 'when the request contains an authentication header' do
it 'should return the user info' do
user = create(:user)
post '/user_token', params: {"auth": {"email": user.email, "password": user.password }}
body = response.body
puts body # {"jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0ODgxMDgxMDYsInN1YiI6MX0.GDBHPzbivclJfwSTswXhDkV0TCFCybJFDrjBnLIfN3Q"}
# use the retrieved JWT for future requests
end
end
Any advice is appreciated.
def authenticated_header(user)
token = Knock::AuthToken.new(payload: { sub: user.id }).token
{ 'Authorization': "Bearer #{token}" }
end
describe 'GET /users?me=true' do
URL = '/v1/users?me=true'
AUTH_URL = '/user_token'
context 'when the request with NO authentication header' do
it 'should return unauth for retrieve current user info before login' do
get URL
expect(response).to have_http_status(:unauthorized)
end
end
context 'when the request contains an authentication header' do
it 'should return the user info' do
user = create(:user)
get URL, headers: authenticated_header(user)
puts response.body
end
end
end
With the help of Lorem's answer, I was able to implement something similar for my request spec. Sharing it here for others to see an alternate implementation.
# spec/requests/locations_spec.rb
require 'rails_helper'
RSpec.describe 'Locations API' do
let!(:user) { create(:user) }
let!(:locations) { create_list(:location, 10, user_id: user.id) }
describe 'GET /locations' do
it 'reponds with invalid request without JWT' do
get '/locations'
expect(response).to have_http_status(401)
expect(response.body).to match(/Invalid Request/)
end
it 'responds with JSON with JWT' do
jwt = confirm_and_login_user(user)
get '/locations', headers: { "Authorization" => "Bearer #{jwt}" }
expect(response).to have_http_status(200)
expect(json.size).to eq(10)
end
end
end
confirm_and_login_user(user) is defined in a request_spec_helper which is included as a module in rails_helper.rb:
# spec/support/request_spec_helper.rb
module RequestSpecHelper
def json
JSON.parse(response.body)
end
def confirm_and_login_user(user)
get '/users/confirm', params: {token: user.confirmation_token}
post '/users/login', params: {email: user.email, password: 'password'}
return json['auth_token']
end
end
I'm using the jwt gem for generating my tokens as described in this SitePoint tutorial (https://www.sitepoint.com/introduction-to-using-jwt-in-rails/)
Lorem's answer mostly worked for me. I got unrecognized keyword setting headers: on the get. I modified the authenticated_header method and put it in support/api_helper.rb so I could reuse it. The modification is to merge the auth token into request.headers.
# spec/support/api_helper.rb
module ApiHelper
def authenticated_header(request, user)
token = Knock::AuthToken.new(payload: { sub: user.id }).token
request.headers.merge!('Authorization': "Bearer #{token}")
end
end
In each spec file testing the api, I include api_helper.rb. And I call authenticated_header just before the get statement when testing the case of valid authentication...
# spec/controllers/api/v2/search_controller_spec.rb
RSpec.describe API::V2::SearchController, type: :controller do
include ApiHelper
...
describe '#search_by_id' do
context 'with an unauthenticated user' do
it 'returns unauthorized' do
get :search_by_id, params: { "id" : "123" }
expect(response).to be_unauthorized
end
end
context 'with an authenticated user' do
let(:user) { create(:user) }
it 'renders json listing resource with id' do
expected_result = { id: 123, title: 'Resource 123' }
authenticated_header(request, user)
get :search_by_id, params: { "id" : "123" }
expect(response).to be_successful
expect(JSON.parse(response.body)).to eq expected_result
end
end
The key lines in this second test are...
authenticated_header(request, user)
get :search_by_id, params: { "id" : "123" }
I want to test a create action where the user is authenticated via a remote API. Therefore, I do not have a user model. I have this in rspec:
describe "POST create" do
before { post :create, :user => {username: "john", password: "password"} }
it { expect(response).to be_success }
end
This does not work. I looked at this rspec docs but could not find anything. How do I resolve this?
I assume this is a controller-spec, if you do like this it should work:
RSpec.describe UserControler, type: :controller do
describe 'POST #create' do
it 'creates a new user' do
expect {
post :create, user: { username: 'john', password: 'password' }
}.to change(User, :count).by(1)
end
end
end
To just check if the request is a success you can do:
it 'creates a new user' do
post :create, user: { username: 'john', password: 'password' }
expect(response).to be_success
end
Hope this helps :)
The post line needs to be in the it block
describe "POST create" do
before { }
it "returns 200 OK" do
post :create, :user => {username: "john", password: "password"}
expect(response).to be_success
end
end