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

Optionally generate RSpec specs instead of Test::Unit tests #88

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
31 changes: 25 additions & 6 deletions lib/generators/authentication/authentication_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ def create_models
end

def create_fixture_file
copy_file "test_unit/users.yml", "test/fixtures/users.yml"
case test_framework
when :test_unit
copy_file "test_unit/users.yml", "test/fixtures/users.yml"
when :rspec
copy_file "rspec/users.yml", "spec/fixtures/users.yml"
end
end

def create_controllers
Expand Down Expand Up @@ -220,11 +225,21 @@ def add_routes
end

def create_test_files
directory "test_unit/controllers/#{format}", "test/controllers"
directory "test_unit/mailers/", "test/mailers"
directory "test_unit/system", "test/system" unless options.api?
template "test_unit/test_helper.rb", "test/test_helper.rb", force: true
template "test_unit/application_system_test_case.rb", "test/application_system_test_case.rb", force: true unless options.api?
case test_framework
when :test_unit
directory "test_unit/controllers/#{format}", "test/controllers"
directory "test_unit/mailers/", "test/mailers"
directory "test_unit/system", "test/system" unless options.api?
template "test_unit/test_helper.rb", "test/test_helper.rb", force: true
template "test_unit/application_system_test_case.rb", "test/application_system_test_case.rb", force: true unless options.api?
when :rspec
directory "rspec/requests/#{format}", "spec/requests"
directory "rspec/mailers/", "spec/mailers"
directory "rspec/system", "spec/system" unless options.api?

uncomment_lines("spec/rails_helper.rb", /'spec', 'support'/)
template "rspec/authentication.rb", "spec/support/authentication.rb", force: true
end
end

private
Expand Down Expand Up @@ -272,6 +287,10 @@ def node?
Rails.root.join("package.json").exist?
end

def test_framework
Rails.application.config.generators.test_framework
end

def ratelimit_block
<<~CODE
# Rate limit general requests by IP address in a rate of 1000 requests per minute
Expand Down
36 changes: 36 additions & 0 deletions lib/generators/authentication/templates/rspec/authentication.rb.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module Helpers
module Authentication
module Request
<%- if options.api? -%>
def sign_in_as(user)
post(sign_in_url, params: { email: user.email, password: "Secret1*3*5*" })
response.headers["X-Session-Token"]
end
<%- else -%>
def sign_in_as(user)
post(sign_in_url, params: { email: user.email, password: "Secret1*3*5*" })
end
<%- end -%>
end

module System
def sign_in_as(user)
visit sign_in_url
fill_in :email, with: user.email
fill_in :password, with: "Secret1*3*5*"
click_on "Sign in"

expect(current_path).to eq(root_url)
user
end
end
end
end

RSpec.configure do |config|
config.include Helpers::Authentication::Request, type: :request
config.include Helpers::Authentication::System, type: :system

config.include Capybara::RSpecMatchers, type: :request
config.include ActiveSupport::Testing::TimeHelpers
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "rails_helper"

RSpec.describe "Sessions", type: :request do
let(:user) { users(:lazaro_nixon) }
Comment on lines +3 to +4

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you put a subject here, the call will be with is_expected and that is so good to read, personally.

Suggested change
RSpec.describe "Sessions", type: :request do
let(:user) { users(:lazaro_nixon) }
RSpec.describe "Sessions", type: :request do
subject { response }
let(:user) { users(:lazaro_nixon) }

let(:token) { sign_in_as(user) }
let(:default_headers) { { "Authorization" => "Bearer #{token}" } }

describe "GET #index" do
it "returns HTTP success" do
get sessions_url, headers: default_headers

expect(response).to have_http_status(:success)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the expects can be like this:

Suggested change
expect(response).to have_http_status(:success)
is_expected.to have_http_status :success

end
end

describe "GET #show" do
it "returns HTTP success" do
get session_url(user.sessions.last), headers: default_headers

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here user.sessions will allways be blank. since we are using let, it will be available only when we access token or default_header which is not the case here.


expect(response).to have_http_status(:success)
end
end

describe "POST #create" do
context "with valid credentials" do
it "returns HTTP created" do
post sign_in_url, params: { email: user.email, password: "Secret1*3*5*" }

expect(response).to have_http_status(:created)
end
end

context "with invalid credentials" do
it "returns HTTP unauthorized" do
post sign_in_url, params: { email: user.email, password: "SecretWrong1*3" }

expect(response).to have_http_status(:unauthorized)
end
end
end

describe "DELETE #destroy" do
it "returns HTTP no content" do
delete session_url(user.sessions.last), headers: default_headers

expect(response).to have_http_status(:no_content)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require "rails_helper"

RSpec.describe Identity::PasswordResetsController, type: :request do
fixtures :users
let(:user) { users(:lazaro_nixon) }

describe "GET #new" do
it "returns HTTP success" do
get new_identity_password_reset_url

expect(response).to have_http_status(:success)
end
end

describe "GET #edit" do
let(:sid) { user.generate_token_for(:password_reset) }

it "returns HTTP success" do
get edit_identity_password_reset_url(sid: sid)

expect(response).to have_http_status(:success)
end
end

describe "POST #create" do
context "with a valid email" do
it "sends a password reset email" do
expect {
post identity_password_reset_url, params: { email: user.email }
}.to have_enqueued_mail(UserMailer, :password_reset)

expect(response).to redirect_to(sign_in_url)
end
end

context "with a nonexistent email" do
it "does not send a password reset email" do
expect {
post identity_password_reset_url, params: { email: "[email protected]" }
}.to_not have_enqueued_mail(UserMailer, :password_reset)

expect(response).to redirect_to(new_identity_password_reset_url)
expect(flash[:alert]).to eq("You can't reset your password until you verify your email")
end
end

context "with an unverified email" do
it "does not send a password reset email" do
user.update!(verified: false)

expect {
post identity_password_reset_url, params: { email: user.email }
}.to_not have_enqueued_mail(UserMailer, :password_reset)

expect(response).to redirect_to(new_identity_password_reset_url)
expect(flash[:alert]).to eq("You can't reset your password until you verify your email")
end
end
end

describe "PATCH #update" do
let!(:sid) { user.generate_token_for(:password_reset) }

context "with a valid token" do
it "updates the password" do
patch identity_password_reset_url, params: { sid: sid, password: "Secret6*4*2*", password_confirmation: "Secret6*4*2*" }

expect(response).to redirect_to(sign_in_url)
end
end

context "with an expired token" do
it "does not update the password" do
travel 30.minutes

patch identity_password_reset_url, params: { sid: sid, password: "Secret6*4*2*", password_confirmation: "Secret6*4*2*" }

expect(response).to redirect_to(new_identity_password_reset_url)
expect(flash[:alert]).to eq("That password reset link is invalid")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require "rails_helper"

RSpec.describe PasswordsController, type: :request do
fixtures :users
let(:user) { users(:lazaro_nixon) }

before do
sign_in_as(user)
end

describe "GET #edit" do
it "returns HTTP success" do
get edit_password_url

expect(response).to have_http_status(:success)
end
end

describe "PATCH #update" do
context "with correct password challenge" do
it "updates the password" do
patch password_url, params: { password_challenge: "Secret1*3*5*", password: "Secret6*4*2*", password_confirmation: "Secret6*4*2*" }

expect(response).to redirect_to(root_url)
end
end

context "with wrong password challenge" do
it "returns an error" do
patch password_url, params: { password_challenge: "SecretWrong1*3", password: "Secret6*4*2*", password_confirmation: "Secret6*4*2*" }

expect(response).to have_http_status(:unprocessable_entity)
expect(response.body).to have_selector("li", text: /Password challenge is invalid/)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "rails_helper"

RSpec.describe RegistrationsController, type: :request do
describe "GET #new" do
it "returns HTTP success" do
get sign_up_url

expect(response).to have_http_status(:success)
end
end

describe "POST #create" do
it "creates a new user" do
expect {
post sign_up_url, params: { email: "[email protected]", password: "Secret1*3*5*", password_confirmation: "Secret1*3*5*" }
}.to change{ User.count }.by(1)

expect(response).to redirect_to(root_url)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require "rails_helper"

RSpec.describe SessionsController, type: :request do
fixtures :users
let(:user) { users(:lazaro_nixon) }

describe "GET #index" do
it "returns HTTP success" do
sign_in_as(user)

get sessions_url

expect(response).to have_http_status(:success)
end
end

describe "GET #new" do
it "returns HTTP success" do
get sign_in_url

expect(response).to have_http_status(:success)
end
end

describe "POST #create" do
context "with valid credentials" do
it "signs the user in" do
post sign_in_url, params: { email: user.email, password: "Secret1*3*5*" }
expect(response).to redirect_to(root_url)

get root_url
expect(response).to have_http_status(:success)
end
end

context "with invalid credentials" do
it "does not sign the user in" do
post sign_in_url, params: { email: user.email, password: "SecretWrong1*3" }
expect(response).to redirect_to(sign_in_url(email_hint: user.email))

get root_url
expect(response).to redirect_to(sign_in_url)
end
end
end

describe "DELETE #destroy" do
it "signs the user out" do
sign_in_as(user)

delete session_url(user.sessions.last)
expect(response).to redirect_to(sessions_url)

follow_redirect!
expect(response).to redirect_to(sign_in_url)
end
end
end
6 changes: 6 additions & 0 deletions lib/generators/authentication/templates/rspec/users.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

lazaro_nixon:
email: [email protected]
password_digest: <%= BCrypt::Password.create("Secret1*3*5*") %>
verified: true