diff --git a/.env.template b/.env.template
index c80d0eff..0e694f27 100644
--- a/.env.template
+++ b/.env.template
@@ -1,5 +1,6 @@
OVERMIND_PROCFILE=Procfile.dev
HIVEMIND_PROCFILE=Procfile.dev
+PORT=3000
OVERMIND_ENV=./.env.local
HIVEMIND_ENV=./.env.local
RSPEC_RETRY_RETRY_COUNT=0
diff --git a/.github/PULL_REQUEST_TEMPLATE/default.md b/.github/PULL_REQUEST_TEMPLATE.md
similarity index 100%
rename from .github/PULL_REQUEST_TEMPLATE/default.md
rename to .github/PULL_REQUEST_TEMPLATE.md
diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
index 4ed4df5d..e955b403 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -86,6 +86,8 @@ jobs:
- name: Setup DB
run: |
bundle exec rails db:test:prepare
+ - name: Assets Precompile
+ run: bundle exec rails assets:precompile
- name: Run tests
run: |
bundle exec rspec
diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb
index 55400474..6414e533 100644
--- a/app/controllers/concerns/authentication.rb
+++ b/app/controllers/concerns/authentication.rb
@@ -4,25 +4,31 @@ module Authentication
included do
helper_method :current_user, :user_signed_in?
- before_action :authenticate_user!
+ before_action :authenticate_user, :authenticate_user!
end
- def authenticate_user!
- return current_user if user_signed_in?
-
- redirect_to root_path, alert: t("controllers.concerns.authentication.unauthorized")
+ class_methods do
+ def allow_unauthenticated_access(**options)
+ skip_before_action :authenticate_user!, **options
+ end
end
- def current_user
- Current.user ||= authenticate_user_from_session
- end
+ private
+
+ def current_user = Current.user
- def authenticate_user_from_session
- User.find_by(id: session[:user_id])
+ def user_signed_in? = Current.user.present?
+
+ def authenticate_user
+ Current.user = User.find_by(id: session[:user_id])
end
- def user_signed_in?
- current_user.present?
+ def authenticate_user!
+ authenticate_user
+
+ if !user_signed_in?
+ redirect_to new_session_path, alert: t("controllers.concerns.authentication.unauthorized")
+ end
end
def login(user)
diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb
index f6b17f11..c98a8eaa 100644
--- a/app/controllers/main_controller.rb
+++ b/app/controllers/main_controller.rb
@@ -1,5 +1,6 @@
class MainController < ApplicationController
- skip_before_action :authenticate_user!
+ allow_unauthenticated_access
+
def index
end
end
diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb
index 15f94e59..8e4429f4 100644
--- a/app/controllers/password_resets_controller.rb
+++ b/app/controllers/password_resets_controller.rb
@@ -1,5 +1,5 @@
class PasswordResetsController < ApplicationController
- skip_before_action :authenticate_user!, only: [:new, :create]
+ allow_unauthenticated_access
before_action :set_user_by_token, only: [:edit, :update]
@@ -19,7 +19,7 @@ def create
).password_reset.deliver_later
end
- redirect_to root_path, notice: t("controllers.password_resets.create.notice")
+ redirect_to new_session_path, notice: t("controllers.password_resets.create.notice")
end
def update
@@ -36,7 +36,7 @@ def set_user_by_token
@user = User.find_by_token_for(:password_reset, params[:token])
return if @user.present?
- redirect_to new_password_reset_path alert: t("controllers.password_resets.errors.invalid_token")
+ redirect_to new_password_reset_path, alert: t("controllers.password_resets.errors.invalid_token")
end
def password_params
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 82969ec9..1930c97b 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -3,7 +3,7 @@ def edit
end
def update
- if current_user.update(password_params)
+ if Current.user.update(password_params)
redirect_to edit_password_path, notice: t("controllers.passwords.update.notice")
else
render :edit, status: :unprocessable_entity
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 199ba626..37651612 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,5 +1,5 @@
class RegistrationsController < ApplicationController
- skip_before_action :authenticate_user!
+ allow_unauthenticated_access
def new
@user = User.new
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 00e79feb..fae2553e 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,5 +1,5 @@
class SessionsController < ApplicationController
- skip_before_action :authenticate_user!
+ allow_unauthenticated_access only: [:new, :create]
def new
@user = User.new
@@ -12,8 +12,7 @@ def create
login @user
redirect_to root_path, notice: t("controllers.sessions.create.notice")
else
- flash[:alert] = t("controllers.sessions.create.alert")
- render :new, status: :unprocessable_entity
+ redirect_to new_session_path, alert: t("controllers.sessions.create.alert")
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 86e22a32..8b641a70 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -28,6 +28,7 @@ class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
validates :password_digest, presence: true
+ validates :password, length: {minimum: 8}, if: -> { password.present? }
generates_token_for :password_reset, expires_in: PASSWORD_RESET_EXPIRATION do
password_salt&.last(10)
diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb
index 7a30272d..b1959002 100644
--- a/app/views/passwords/edit.html.erb
+++ b/app/views/passwords/edit.html.erb
@@ -1,6 +1,6 @@
Update Password
-<%= form_with model: current_user, url: password_path do |form| %>
+<%= form_with model: Current.user, url: password_path do |form| %>
<% if form.object.errors.any? %>
<% form.object.errors.full_messages.each do |message| %>
<%= message %>
diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb
index 82160074..f1a6c724 100644
--- a/app/views/registrations/new.html.erb
+++ b/app/views/registrations/new.html.erb
@@ -9,20 +9,20 @@
<%= form.label :email %>
- <%= form.email_field :email %>
+ <%= form.email_field :email, data: { test_id: "email_field" } %>
<%= form.label :password %>
- <%= form.password_field :password %>
+ <%= form.password_field :password, data: { test_id: "password_field" } %>
<%= form.label :password_confirmation %>
- <%= form.password_field :password_confirmation %>
+ <%= form.password_field :password_confirmation, data: { test_id: "password_confirmation_field" } %>
- <%= form.submit "Sign Up" %>
+ <%= form.submit "Sign Up", data: { test_id: "sign_up_button" } %>
<% end %>
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
index 426a07a5..ef79ab62 100644
--- a/app/views/sessions/new.html.erb
+++ b/app/views/sessions/new.html.erb
@@ -3,16 +3,16 @@
<%= form_with model: @user, url: session_path do |form| %>
<%= form.label :email %>
- <%= form.email_field :email %>
+ <%= form.email_field :email, data: { test_id: "email_field" } %>
<%= form.label :password %>
- <%= form.password_field :password %>
+ <%= form.password_field :password, data: { test_id: "password_field" } %>
- <%= form.submit "Log in" %>
+ <%= form.submit "Log in", data: { test_id: "sign_in_button" } %>
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e18f993c..cd38512d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1,37 +1,8 @@
-# Files in the config/locales directory are used for internationalization and
-# are automatically loaded by Rails. If you want to use locales other than
-# English, add the necessary files in this directory.
-#
-# To use the locales, use `I18n.t`:
-#
-# I18n.t "hello"
-#
-# In views, this is aliased to just `t`:
-#
-# <%= t("hello") %>
-#
-# To use a different locale, set it with `I18n.locale`:
-#
-# I18n.locale = :es
-#
-# This would use the information in config/locales/es.yml.
-#
-# To learn more about the API, please read the Rails Internationalization guide
-# at https://guides.rubyonrails.org/i18n.html.
-#
-# Be aware that YAML interprets the following case-insensitive strings as
-# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
-# must be quoted to be interpreted as strings. For example:
-#
-# en:
-# "yes": yup
-# enabled: "ON"
-
en:
controllers:
concerns:
authentication:
- unauthorized: "You must be logged in to do that."
+ unauthorized: "You need to sign in or sign up before continuing."
password_resets:
create:
notice: "Check your email to reset your password."
@@ -44,7 +15,7 @@ en:
notice: "Your password has been updated successfully."
sessions:
create:
- notice: "You have signed successfully."
+ notice: "Signed in successfully."
alert: "Invalid email or password."
destroy:
- notice: "You have been logged out."
\ No newline at end of file
+ notice: "You have been logged out."
diff --git a/db/seeds.rb b/db/seeds.rb
index cee8acc9..16de4ebb 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,7 +1,7 @@
conference = Conference.find_or_create_by!(name: "RailsWorld 2024")
# Users
-user = User.create!(email: "dev@example.com", password: "foobar", password_confirmation: "foobar")
+user = User.create!(email: "dev@example.com", password: "foobar2024", password_confirmation: "foobar2024")
# Tags
Tag.create!(name: "Hotwire")
diff --git a/spec/controllers/password_resets_controller_spec.rb b/spec/controllers/password_resets_controller_spec.rb
index bb2ec1c6..bd7dfe3a 100644
--- a/spec/controllers/password_resets_controller_spec.rb
+++ b/spec/controllers/password_resets_controller_spec.rb
@@ -22,17 +22,17 @@
end
it "sends a password reset email" do
- expect { post :create, params: params }.to change { ActionMailer::Base.deliveries.count }.by(1)
- expect(response).to redirect_to(root_path)
+ expect {
+ post :create, params: params
+ }.to have_enqueued_mail(PasswordMailer, :password_reset)
+ expect(response).to redirect_to(new_session_path)
end
end
end
describe "GET #edit" do
- let(:current) { instance_double(Current) }
-
before do
- allow(Current).to receive(:user).and_return(user)
+ sign_in(user)
end
context "with valid token" do
@@ -46,17 +46,16 @@
context "with invalid token" do
it "redirects to new password reset path" do
get :edit, params: {token: "invalid_token"}
- expect(response).to redirect_to(new_password_reset_path(alert: I18n.t("controllers.password_resets.errors.invalid_token")))
+ expect(response).to redirect_to(new_password_reset_path)
end
end
end
describe "PUT #update" do
let(:new_password) { "new_password" }
- let(:current) { instance_double(Current) }
before do
- allow(Current).to receive(:user).and_return(user)
+ sign_in(user)
end
context "with valid params" do
@@ -108,7 +107,7 @@
it "does not update the user password" do
expect { put :update, params: params }.not_to change { user.reload.password_digest }
- expect(response).to redirect_to(new_password_reset_path(alert: I18n.t("controllers.password_resets.errors.invalid_token")))
+ expect(response).to redirect_to(new_password_reset_path)
end
end
end
diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb
index 9ad546b1..e3b29a21 100644
--- a/spec/controllers/passwords_controller_spec.rb
+++ b/spec/controllers/passwords_controller_spec.rb
@@ -4,10 +4,9 @@
RSpec.describe PasswordsController, type: :controller do
let!(:user) { create(:user, password: "password") }
- let(:current) { instance_double(Current) }
before do
- allow(Current).to receive(:user).and_return(user)
+ sign_in(user)
end
describe "GET #edit" do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index fa0f0318..c2556edd 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -7,7 +7,6 @@
it "returns a success response" do
get :new
expect(response).to have_http_status(:ok)
- assigns(:user).should be_a_new(User)
end
end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 31e8b614..55cb7628 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -27,7 +27,6 @@
post :create, params: params
expect(session[:user_id]).to eq(user.id)
expect(response).to redirect_to(root_path)
- assigns(:user).should eq(user)
end
end
@@ -43,8 +42,8 @@
it "does not create a User session" do
expect { post :create, params: params }.not_to change(User, :count)
- expect(response).to have_http_status(:unprocessable_content)
- expect(response).to render_template(:new)
+ expect(response).to have_http_status(:found)
+ expect(response).to redirect_to(new_session_path)
end
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 14539a09..7f25f9e4 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -18,7 +18,7 @@
FactoryBot.define do
factory :user do
email { Faker::Internet.email }
- password { "password" }
+ password { "password2024" }
trait :with_profile do
profile
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 366e19e9..b23e95a3 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -30,6 +30,8 @@
abort e.to_s.strip
end
RSpec.configure do |config|
+ ActiveJob::Base.queue_adapter = :test
+
config.include FactoryBot::Syntax::Methods
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
@@ -58,5 +60,4 @@
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
- config.include FactoryBot::Syntax::Methods
end
diff --git a/spec/support/authentication_helper.rb b/spec/support/authentication_helper.rb
new file mode 100644
index 00000000..ed2091c2
--- /dev/null
+++ b/spec/support/authentication_helper.rb
@@ -0,0 +1,17 @@
+module AuthenticationHelper
+ def sign_in(user, password = "password2024")
+ if respond_to?(:visit) # System specs
+ visit new_session_path
+ find_dti("email_field").set(user.email)
+ find_dti("password_field").set(password)
+ find_dti("sign_in_button").click
+ else # Controller specs
+ session[:user_id] = user.id
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.include AuthenticationHelper, type: :system
+ config.include AuthenticationHelper, type: :controller
+end
diff --git a/spec/system/test_spec.rb b/spec/system/test_spec.rb
deleted file mode 100644
index 2510aef5..00000000
--- a/spec/system/test_spec.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require "rails_helper"
-
-RSpec.describe "System specs health check", type: :system do
- it "website is up" do
- visit "/up"
- expect(page).to have_http_status(:ok)
- end
-end
diff --git a/spec/system/user_sign_in_spec.rb b/spec/system/user_sign_in_spec.rb
new file mode 100644
index 00000000..62f87f69
--- /dev/null
+++ b/spec/system/user_sign_in_spec.rb
@@ -0,0 +1,37 @@
+require "rails_helper"
+
+RSpec.describe "User sign in", type: :system do
+ let!(:user) { create(:user, email: "test@test.com", password: "foobar2024") }
+
+ it "redirects to the login page when trying to access another page" do
+ visit edit_password_path
+ expect(page).to have_current_path(new_session_path)
+ expect(page).to have_content("You need to sign in or sign up before continuing.")
+ end
+
+ context "when the user inputs invalid credentials" do
+ it "does not sign the user in" do
+ visit new_session_path
+ find_dti("email_field").set("test@test.com")
+ find_dti("password_field").set("wrongpassword")
+ find_dti("sign_in_button").click
+ expect(page).to have_content("Invalid email or password.")
+
+ visit edit_password_path
+ expect(page).to have_current_path(new_session_path)
+ end
+ end
+
+ context "when the user inputs valid credentials" do
+ it "signs the user in" do
+ visit new_session_path
+ find_dti("email_field").set("test@test.com")
+ find_dti("password_field").set("foobar2024")
+ find_dti("sign_in_button").click
+ expect(page).to have_content("Signed in successfully.")
+
+ visit edit_password_path
+ expect(page).to have_current_path(edit_password_path)
+ end
+ end
+end
diff --git a/spec/system/user_sign_up_spec.rb b/spec/system/user_sign_up_spec.rb
new file mode 100644
index 00000000..8d3915fe
--- /dev/null
+++ b/spec/system/user_sign_up_spec.rb
@@ -0,0 +1,52 @@
+require "rails_helper"
+
+RSpec.describe "User sign up", type: :system do
+ before { visit new_registration_path }
+
+ context "when the password & password confirmation doesn't match up" do
+ it "does not create a new user" do
+ find_dti("email_field").set("test@test.com")
+ find_dti("password_field").set("hello")
+ find_dti("password_confirmation_field").set("world")
+ find_dti("sign_up_button").click
+
+ expect(page).to have_content("Password confirmation doesn't match Password")
+ end
+ end
+
+ context "when the email is already taken" do
+ let!(:user) { create(:user, email: "test@test.com") }
+
+ it "does not create a new user" do
+ find_dti("email_field").set("test@test.com")
+ find_dti("password_field").set("foobar2024")
+ find_dti("password_confirmation_field").set("foobar2024")
+ find_dti("sign_up_button").click
+
+ expect(page).to have_content("Email has already been taken")
+ end
+ end
+
+ context "when the password doesn't met the length criteria" do
+ it "does not create a new user" do
+ find_dti("email_field").set("test@test.com")
+ find_dti("password_field").set("foobar")
+ find_dti("password_confirmation_field").set("foobar")
+ find_dti("sign_up_button").click
+
+ expect(page).to have_content("Password is too short (minimum is 8 characters)")
+ end
+ end
+
+ context "when the user inputs valid credentials" do
+ it "creates a new user" do
+ find_dti("email_field").set("test@test.com")
+ find_dti("password_field").set("foobar2024")
+ find_dti("password_confirmation_field").set("foobar2024")
+ find_dti("sign_up_button").click
+
+ visit edit_password_path
+ expect(page).to have_current_path(edit_password_path)
+ end
+ end
+end