From 45250a1fa3045a3a65a6e9187f2ac72df878608c Mon Sep 17 00:00:00 2001 From: stephann <3025661+stephannv@users.noreply.github.com> Date: Sun, 20 Nov 2022 11:42:57 -0300 Subject: [PATCH] Add request helpers --- README.md | 35 ++++++++ lib/clearance/rspec.rb | 2 + lib/clearance/testing/controller_helpers.rb | 28 ++----- lib/clearance/testing/request_helpers.rb | 44 +++++++++++ lib/clearance/testing/utils.rb | 35 ++++++++ .../clearance/testing/request_helpers_spec.rb | 79 +++++++++++++++++++ spec/requests/authentication_cookie_spec.rb | 10 +-- spec/requests/cookie_options_spec.rb | 18 +---- spec/requests/token_expiration_spec.rb | 14 +--- 9 files changed, 207 insertions(+), 58 deletions(-) create mode 100644 lib/clearance/testing/request_helpers.rb create mode 100644 lib/clearance/testing/utils.rb create mode 100644 spec/clearance/testing/request_helpers_spec.rb diff --git a/README.md b/README.md index 0d41136f5..9496a6243 100644 --- a/README.md +++ b/README.md @@ -419,6 +419,41 @@ To Generate the clearance specs, run: rails generate clearance:specs ``` +### Request Test Helpers + +To test routes that are protected by `before_action :require_login`, +require Clearance's test helpers in your test suite. + +For `rspec`, add the following line to your `spec/rails_helper.rb` or +`spec/spec_helper` if `rails_helper` does not exist: + +```ruby +require "clearance/rspec" +``` + +This will make available helper methods to assist you in your request tests. + +```ruby +sign_in +sign_in_as(user, password: "12345") +sign_out +``` + +By default, these helpers will use `session_path` and `sign_out_path` to sign +in and sign out user, but if you are using custom paths, you are able to +provide your paths: + +```ruby +sign_in_as(user, password: "12345", path: custom_sign_in_path) +sign_out(user, password: "12345", path: custom_sign_out_path) +``` + +If you need the reference for signed in user, you can assign the `sign_in` +return to a variable: + +```ruby +user = sign_in +``` ### Controller Test Helpers To test controller actions that are protected by `before_action :require_login`, diff --git a/lib/clearance/rspec.rb b/lib/clearance/rspec.rb index f42b5e940..ea4fa1dc6 100644 --- a/lib/clearance/rspec.rb +++ b/lib/clearance/rspec.rb @@ -1,11 +1,13 @@ require "rspec/rails" require "clearance/testing/deny_access_matcher" require "clearance/testing/controller_helpers" +require "clearance/testing/request_helpers" require "clearance/testing/view_helpers" RSpec.configure do |config| config.include Clearance::Testing::Matchers, type: :controller config.include Clearance::Testing::ControllerHelpers, type: :controller + config.include Clearance::Testing::RequestHelpers, type: :request config.include Clearance::Testing::ViewHelpers, type: :view config.include Clearance::Testing::ViewHelpers, type: :helper diff --git a/lib/clearance/testing/controller_helpers.rb b/lib/clearance/testing/controller_helpers.rb index 431a32242..988bc9e25 100644 --- a/lib/clearance/testing/controller_helpers.rb +++ b/lib/clearance/testing/controller_helpers.rb @@ -1,3 +1,5 @@ +require "clearance/testing/utils" + module Clearance module Testing # Provides helpers to your controller specs. @@ -5,22 +7,21 @@ module Testing # `clearance/test_unit` as appropriate in your `rails_helper.rb` or # `test_helper.rb` files. module ControllerHelpers + include Clearance::Testing::Utils + # @api private def setup_controller_request_and_response super @request.env[:clearance] = Clearance::Session.new(@request.env) end - # Signs in a user that is created using FactoryGirl. + # Signs in a user that is created using FactoryBot or FactoryGirl. # The factory name is derrived from your `user_class` Clearance # configuration. # - # @raise [RuntimeError] if FactoryGirl is not defined. + # @raise [RuntimeError] if FactoryBot or FactoryGirl is not defined. def sign_in - constructor = factory_module("sign_in") - - factory = Clearance.configuration.user_model.to_s.underscore.to_sym - sign_in_as constructor.create(factory) + sign_in_as create_user end # Signs in the provided user. @@ -37,21 +38,6 @@ def sign_in_as(user) def sign_out @request.env[:clearance].sign_out end - - # Determines the appropriate factory library - # - # @api private - # @raise [RuntimeError] if both FactoryGirl and FactoryBot are not - # defined. - def factory_module(provider) - if defined?(FactoryBot) - FactoryBot - elsif defined?(FactoryGirl) - FactoryGirl - else - raise("Clearance's `#{provider}` helper requires factory_bot") - end - end end end end diff --git a/lib/clearance/testing/request_helpers.rb b/lib/clearance/testing/request_helpers.rb new file mode 100644 index 000000000..76f01a5d1 --- /dev/null +++ b/lib/clearance/testing/request_helpers.rb @@ -0,0 +1,44 @@ +require "clearance/testing/utils" + +module Clearance + module Testing + # Provides helpers to your request specs. + # These are typically used in tests by requiring `clearance/rspec` or + # `clearance/test_unit` as appropriate in your `rails_helper.rb` or + # `test_helper.rb` files. + module RequestHelpers + include Clearance::Testing::Utils + + # Signs in a user that is created using FactoryBot or FactoryGirl. + # The factory name is derrived from your `user_class` Clearance + # configuration. + # + # @raise [RuntimeError] if FactoryBot or FactoryGirl is not defined. + def sign_in + sign_in_as create_user(password: "password"), password: "password" + end + + # Signs in the provided user. + # + # @param [User class] user + # @param [String] password + # @param [String] path + + # @return user + def sign_in_as(user, password:, path: session_path) + post path, params: { session: { email: user.email, password: password } } + + user + end + + # Signs out a user that may be signed in. + # + # @param [String] path + # + # @return [void] + def sign_out(path: sign_out_path) + delete path + end + end + end +end diff --git a/lib/clearance/testing/utils.rb b/lib/clearance/testing/utils.rb new file mode 100644 index 000000000..47f800145 --- /dev/null +++ b/lib/clearance/testing/utils.rb @@ -0,0 +1,35 @@ +module Clearance + module Testing + module Utils + private + + # Creates a user using FactoryBot or FactoryGirl. + # The factory name is derrived from `user_class` Clearance + # configuration. + # + # @api private + # @raise [RuntimeError] if FactoryBot or FactoryGirl is not defined. + def create_user(**factory_options) + constructor = factory_module("sign_in") + + factory = Clearance.configuration.user_model.to_s.underscore.to_sym + constructor.create(factory, **factory_options) + end + + # Determines the appropriate factory library + # + # @api private + # @raise [RuntimeError] if both FactoryGirl and FactoryBot are not + # defined. + def factory_module(provider) + if defined?(FactoryBot) + FactoryBot + elsif defined?(FactoryGirl) + FactoryGirl + else + raise("Clearance's `#{provider}` helper requires factory_bot") + end + end + end + end +end diff --git a/spec/clearance/testing/request_helpers_spec.rb b/spec/clearance/testing/request_helpers_spec.rb new file mode 100644 index 000000000..a9f06baa9 --- /dev/null +++ b/spec/clearance/testing/request_helpers_spec.rb @@ -0,0 +1,79 @@ +require "spec_helper" + +class SecretsController < ActionController::Base + include Clearance::Controller + + before_action :require_login + + def index + render json: { status: :ok } + end +end + +describe "Secrets managament", type: :request do + before do + Rails.application.routes.draw do + instance_eval(File.read(Rails.root.join("config/routes.rb"))) # drawing clearance routes + post "/custom_sign_in" => "clearance/sessions#create", as: "custom_sign_in" + resources :secrets, only: :index + end + end + + describe "GET #index" do + context "with default authenticated user" do + it "renders action response" do + sign_in + + get secrets_path + + expect(response.body).to eq({ status: :ok }.to_json) + end + end + + context "with custom authenticated user" do + it "renders action response" do + user = create(:user, password: "my-password") + sign_in_as(user, password: "my-password") + + get secrets_path + + expect(response.body).to eq({ status: :ok }.to_json) + end + end + + context "with custom sign in path" do + it "renders action response" do + user = create(:user, password: "my-password") + sign_in_as(user, password: "my-password", path: custom_sign_in_path) + + get secrets_path + + expect(response.body).to eq({ status: :ok }.to_json) + end + end + + context "without authenticated user" do + it "redirects to sign in" do + get secrets_path + + expect(response).to redirect_to(sign_in_path) + end + end + + context "with signed out user" do + it "redirects to sign in" do + sign_in + + get secrets_path + + expect(response.body).to eq({ status: :ok }.to_json) + + sign_out + + get secrets_path + + expect(response).to redirect_to(sign_in_path) + end + end + end +end diff --git a/spec/requests/authentication_cookie_spec.rb b/spec/requests/authentication_cookie_spec.rb index e6f07d4eb..037069c52 100644 --- a/spec/requests/authentication_cookie_spec.rb +++ b/spec/requests/authentication_cookie_spec.rb @@ -18,7 +18,7 @@ def public describe "Authentication cookies in the response" do before do draw_test_routes - create_user_and_sign_in + sign_in end after do @@ -44,12 +44,4 @@ def draw_test_routes resource :session, controller: "clearance/sessions", only: [:create] end end - - def create_user_and_sign_in - user = create(:user, password: "password") - - post session_path, params: { - session: { email: user.email, password: "password" }, - } - end end diff --git a/spec/requests/cookie_options_spec.rb b/spec/requests/cookie_options_spec.rb index aeaca9270..468e22dbb 100644 --- a/spec/requests/cookie_options_spec.rb +++ b/spec/requests/cookie_options_spec.rb @@ -8,14 +8,7 @@ context "when httponly config value is false" do let(:httponly) { false } describe "sign in" do - before do - user = create(:user, password: "password") - get sign_in_path - - post session_path, params: { - session: { email: user.email, password: "password" }, - } - end + before { sign_in } it { should_have_one_remember_token } @@ -28,14 +21,7 @@ context "when httponly config value is true" do let(:httponly) { true } describe "sign in" do - before do - user = create(:user, password: "password") - get sign_in_path - - post session_path, params: { - session: { email: user.email, password: "password" }, - } - end + before { sign_in } it { should_have_one_remember_token } diff --git a/spec/requests/token_expiration_spec.rb b/spec/requests/token_expiration_spec.rb index 0db8e9d25..50e584e52 100644 --- a/spec/requests/token_expiration_spec.rb +++ b/spec/requests/token_expiration_spec.rb @@ -4,7 +4,7 @@ describe "after signing in" do before do Timecop.freeze - create_user_and_sign_in + sign_in @initial_cookies = remember_token_cookies end @@ -22,7 +22,7 @@ describe "after signing in and making a followup request" do before do - create_user_and_sign_in + sign_in @initial_cookies = remember_token_cookies Timecop.travel(1.minute.from_now) do @@ -46,14 +46,4 @@ def first_cookie def second_cookie Rack::Test::Cookie.new @followup_cookies.last end - - def create_user_and_sign_in - user = create(:user, password: "password") - - get sign_in_path - - post session_path, params: { - session: { email: user.email, password: "password" }, - } - end end