Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add request helpers #986

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
2 changes: 2 additions & 0 deletions lib/clearance/rspec.rb
Original file line number Diff line number Diff line change
@@ -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

Expand Down
28 changes: 7 additions & 21 deletions lib/clearance/testing/controller_helpers.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
require "clearance/testing/utils"

module Clearance
module Testing
# Provides helpers to your controller 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 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.
Expand All @@ -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
46 changes: 46 additions & 0 deletions lib/clearance/testing/request_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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
35 changes: 35 additions & 0 deletions lib/clearance/testing/utils.rb
Original file line number Diff line number Diff line change
@@ -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
80 changes: 80 additions & 0 deletions spec/clearance/testing/request_helpers_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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
clearance_routes_file = File.read(Rails.root.join("config", "routes.rb"))
instance_eval(clearance_routes_file) # drawing clearance routes
post "/my_sign_in" => "clearance/sessions#create", as: "my_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: my_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
10 changes: 1 addition & 9 deletions spec/requests/authentication_cookie_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
18 changes: 2 additions & 16 deletions spec/requests/cookie_options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand All @@ -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 }

Expand Down
14 changes: 2 additions & 12 deletions spec/requests/token_expiration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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