Browse Source

Invite new user (#134)

* Clean up user routes

* Make user registerable

* Merge

* Turbo devise strikes again

* URL naming

* Dashes not underscores

* Consistent syntax

* Turning off turbo changes our html

* Update password link not working yet

* New user path

* Password edit path

* Updating password keeps you signed in and redirects to show

* Set new user org

* Write a failing spec for user creation

* Reset user password and redirect back to org users page

* Test redirect

* Use invite template

* Request specs over feature specs

* Add email validation
pull/137/head
baarkerlounger 3 years ago committed by GitHub
parent
commit
313be0e304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/controllers/auth/passwords_controller.rb
  2. 2
      app/controllers/auth/sessions_controller.rb
  3. 25
      app/controllers/users/account_controller.rb
  4. 7
      app/controllers/users/registrations_controller.rb
  5. 50
      app/controllers/users_controller.rb
  6. 8
      app/views/devise/mailer/_password_change_forgotten.html.erb
  7. 6
      app/views/devise/mailer/_password_change_initial.html.erb
  8. 13
      app/views/devise/mailer/reset_password_instructions.html.erb
  9. 18
      app/views/devise/passwords/edit.html.erb
  10. 23
      app/views/devise/registrations/new.html.erb
  11. 15
      app/views/devise/shared/_error_messages.html.erb
  12. 2
      app/views/layouts/application.html.erb
  13. 2
      app/views/organisations/users.html.erb
  14. 2
      app/views/users/edit.html.erb
  15. 2
      app/views/users/edit_password.html.erb
  16. 28
      app/views/users/new.html.erb
  17. 6
      app/views/users/show.html.erb
  18. 29
      config/routes.rb
  19. 2
      db/schema.rb
  20. 14
      spec/features/organisation_spec.rb
  21. 46
      spec/features/user_spec.rb
  22. 6
      spec/requests/auth/passwords_controller_spec.rb
  23. 5
      spec/requests/organisations_controller_spec.rb
  24. 41
      spec/requests/user_controller_spec.rb

2
app/controllers/users/passwords_controller.rb → app/controllers/auth/passwords_controller.rb

@ -1,4 +1,4 @@
class Users::PasswordsController < Devise::PasswordsController class Auth::PasswordsController < Devise::PasswordsController
include Helpers::Email include Helpers::Email
def reset_confirmation def reset_confirmation

2
app/controllers/users/sessions_controller.rb → app/controllers/auth/sessions_controller.rb

@ -1,4 +1,4 @@
class Users::SessionsController < Devise::SessionsController class Auth::SessionsController < Devise::SessionsController
include Helpers::Email include Helpers::Email
def create def create

25
app/controllers/users/account_controller.rb

@ -1,25 +0,0 @@
class Users::AccountController < ApplicationController
def check_logged_in
if current_user.nil?
redirect_to(new_user_session_path)
end
end
def index
check_logged_in
end
def personal_details
check_logged_in
end
def update
if current_user.update(user_params)
redirect_to(users_account_path)
end
end
def user_params
params.require(:user).permit(:email, :name, :password)
end
end

7
app/controllers/users/registrations_controller.rb

@ -1,7 +0,0 @@
class Users::RegistrationsController < Devise::RegistrationsController
protected
def after_update_path_for(_resource)
users_account_path
end
end

50
app/controllers/users_controller.rb

@ -0,0 +1,50 @@
class UsersController < ApplicationController
include Devise::Controllers::SignInOut
include Helpers::Email
before_action :authenticate_user!
def update
if current_user.update(user_params)
bypass_sign_in current_user
redirect_to user_path(current_user)
end
end
def new
@resource = User.new
end
def create
@resource = User.new
if user_params["email"].empty?
@resource.errors.add :email, "Enter an email address"
elsif !email_valid?(user_params["email"])
@resource.errors.add :email, "Enter an email address in the correct format, like name@example.com"
end
if @resource.errors.present?
render :new, status: :unprocessable_entity
else
@user = User.create!(user_params.merge(org_params).merge(password_params))
@user.send_reset_password_instructions
redirect_to users_organisation_path(current_user.organisation)
end
end
def edit_password
render :edit_password
end
private
def password_params
{ password: SecureRandom.hex(8) }
end
def org_params
{ organisation: current_user.organisation }
end
def user_params
params.require(:user).permit(:email, :name, :password)
end
end

8
app/views/devise/mailer/_password_change_forgotten.html.erb

@ -0,0 +1,8 @@
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><%= govuk_link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

6
app/views/devise/mailer/_password_change_initial.html.erb

@ -0,0 +1,6 @@
<p>Hello <%= @resource.name %>!</p>
<p>An account has been created for you to submit CORE data on behalf of @resource.organisation.</p>
<p>Your username is <% @resource.email %>, use the link below to set your password.
<p><%= govuk_link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>

13
app/views/devise/mailer/reset_password_instructions.html.erb

@ -1,8 +1,5 @@
<p>Hello <%= @resource.email %>!</p> <% if @resource.last_sign_in_at.nil? %>
<%= render partial: "password_change_initial" %>
<p>Someone has requested a link to change your password. You can do this through the link below.</p> <% else %>
<%= render partial: "password_change_forgotten" %>
<p><%= govuk_link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p> <% end %>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

18
app/views/devise/passwords/edit.html.erb

@ -1,18 +0,0 @@
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Reset your password</h1>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<%= f.govuk_password_field :password,
label: { text: "New password" },
hint: @minimum_password_length ? { text: "Your password must be at least #{@minimum_password_length} characters and hard to guess." } : nil,
autocomplete: "new-password"
%>
<%= f.govuk_submit "Reset password" %>
</div>
</div>
<% end %>

23
app/views/devise/registrations/new.html.erb

@ -1,23 +0,0 @@
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email"
%>
<%= f.govuk_password_field :password,
hint: @minimum_password_length ? { text: "#{@minimum_password_length} characters minimum" } : nil,
autocomplete: "new-password"
%>
<%= f.govuk_password_field :password_confirmation,
autocomplete: "new-password"
%>
<%= f.govuk_submit "Sign up" %>
<% end %>
<%= render "devise/shared/links" %>

15
app/views/devise/shared/_error_messages.html.erb

@ -1,15 +0,0 @@
<% if resource.errors.any? %>
<div id="error_explanation">
<h2>
<%= I18n.t("errors.messages.not_saved",
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
%>
</h2>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>

2
app/views/layouts/application.html.erb

@ -41,7 +41,7 @@
elsif elsif
component.navigation_item(text: 'Case logs', href: case_logs_path) component.navigation_item(text: 'Case logs', href: case_logs_path)
component.navigation_item(text: 'Your organisation', href: "/organisations/#{current_user.organisation.id}") component.navigation_item(text: 'Your organisation', href: "/organisations/#{current_user.organisation.id}")
component.navigation_item(text: 'Your account', href: users_account_path) component.navigation_item(text: 'Your account', href: user_path(current_user))
component.navigation_item(text: 'Sign out', href: destroy_user_session_path, options: {:method => :delete}) component.navigation_item(text: 'Sign out', href: destroy_user_session_path, options: {:method => :delete})
end end
end end

2
app/views/organisations/users.html.erb

@ -3,7 +3,7 @@
<%= "Users" %> <%= "Users" %>
<% end %> <% end %>
<%= govuk_button_link_to "Invite user", new_user_path, method: :post %> <%= govuk_button_link_to "Invite user", new_user_path, html: { method: :get } %>
<%= govuk_table do |table| %> <%= govuk_table do |table| %>
<%= table.head do |head| %> <%= table.head do |head| %>
<%= head.row do |row| <%= head.row do |row|

2
app/views/users/account/personal_details.html.erb → app/views/users/edit.html.erb

@ -5,7 +5,7 @@
) %> ) %>
<% end %> <% end %>
<%= form_for(current_user, as: :user, url: account_update_path(), html: { method: :patch }) do |f| %> <%= form_for(current_user, as: :user, html: { method: :patch }) do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Change your personal details</h1> <h1 class="govuk-heading-l">Change your personal details</h1>

2
app/views/devise/registrations/edit.html.erb → app/views/users/edit_password.html.erb

@ -5,7 +5,7 @@
) %> ) %>
<% end %> <% end %>
<%= form_for(resource, as: resource_name, url: user_registration_path(), html: { method: :patch }) do |f| %> <%= form_for(current_user, as: :user, html: { method: :patch }) do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Change your password</h1> <h1 class="govuk-heading-l">Change your password</h1>

28
app/views/users/new.html.erb

@ -0,0 +1,28 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
href: :back,
) %>
<% end %>
<%= form_for(@resource, as: :user, html: { method: :post }) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<h1 class="govuk-heading-l">Invite user to submit CORE data</h1>
<%= f.govuk_text_field :name,
autocomplete: "name"
%>
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
value: @resource.email
%>
<%= f.govuk_submit "Continue" %>
</div>
</div>
<% end %>

6
app/views/users/account/index.html.erb → app/views/users/show.html.erb

@ -11,19 +11,19 @@
<%= summary_list.row do |row| <%= summary_list.row do |row|
row.key { 'Name' } row.key { 'Name' }
row.value { current_user.name } row.value { current_user.name }
row.action(visually_hidden_text: 'name', href: '/users/account/personal-details', html_attributes: { 'data-qa': 'change-name' }) row.action(visually_hidden_text: 'name', href: edit_user_path, html_attributes: { 'data-qa': 'change-name' })
end %> end %>
<%= summary_list.row() do |row| <%= summary_list.row() do |row|
row.key { 'Email address' } row.key { 'Email address' }
row.value { current_user.email } row.value { current_user.email }
row.action(visually_hidden_text: 'email address', href: '/users/account/personal-details', html_attributes: { 'data-qa': 'change-email' }) row.action(visually_hidden_text: 'email address', href: edit_user_path, html_attributes: { 'data-qa': 'change-email' })
end %> end %>
<%= summary_list.row do |row| <%= summary_list.row do |row|
row.key { 'Password' } row.key { 'Password' }
row.value { '••••••••' } row.value { '••••••••' }
row.action(visually_hidden_text: 'password', href: edit_user_registration_path, html_attributes: { 'data-qa': 'change-password' }) row.action(visually_hidden_text: 'password', href: password_edit_user_path, html_attributes: { 'data-qa': 'change-password' })
end %> end %>
<%= summary_list.row do |row| <%= summary_list.row do |row|

29
config/routes.rb

@ -1,25 +1,22 @@
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :admin_users, ActiveAdmin::Devise.config devise_for :admin_users, ActiveAdmin::Devise.config
devise_for :users, controllers: { passwords: "users/passwords", sessions: "users/sessions" }, path_names: { sign_in: "sign-in", sign_out: "sign-out" }, skip: [:registrations] devise_for :users, controllers: {
passwords: "auth/passwords",
sessions: "auth/sessions",
}, path_names: { sign_in: "sign-in", sign_out: "sign-out" }
devise_scope :user do devise_scope :user do
get "confirmations/reset", to: "users/passwords#reset_confirmation" get "confirmations/reset", to: "auth/passwords#reset_confirmation"
get "users/edit" => "devise/registrations#edit", :as => "edit_user_registration"
patch "users" => "users/registrations#update", :as => "user_registration"
patch "details" => "users/account#update", :as => "account_update"
end end
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
ActiveAdmin.routes(self) ActiveAdmin.routes(self)
root to: "test#index" root to: "test#index"
get "about", to: "about#index" get "about", to: "about#index"
get "/users/account", to: "users/account#index"
form_handler = FormHandler.instance
form = form_handler.get_form("2021_2022")
resources :users do resources :users do
collection do member do
get "account/personal-details", to: "users/account#personal_details" get "password/edit", to: "users#edit_password"
end end
end end
@ -27,17 +24,21 @@ Rails.application.routes.draw do
member do member do
get "details", to: "organisations#show" get "details", to: "organisations#show"
get "users", to: "organisations#users" get "users", to: "organisations#users"
get "users/invite", to: "users/account#new"
end end
end end
form_handler = FormHandler.instance
form = form_handler.get_form("2021_2022")
resources :case_logs, path: "/case-logs" do resources :case_logs, path: "/case-logs" do
collection do collection do
post "/bulk-upload", to: "bulk_upload#bulk_upload" post "bulk-upload", to: "bulk_upload#bulk_upload"
get "/bulk-upload", to: "bulk_upload#show" get "bulk-upload", to: "bulk_upload#show"
end end
member do member do
post "/form", to: "case_logs#submit_form" post "form", to: "case_logs#submit_form"
end end
form.pages.map do |page| form.pages.map do |page|

2
db/schema.rb

@ -163,9 +163,9 @@ ActiveRecord::Schema.define(version: 2021_12_01_144335) do
t.string "why_dont_you_know_la" t.string "why_dont_you_know_la"
t.integer "unitletas" t.integer "unitletas"
t.integer "builtype" t.integer "builtype"
t.datetime "property_void_date"
t.bigint "owning_organisation_id" t.bigint "owning_organisation_id"
t.bigint "managing_organisation_id" t.bigint "managing_organisation_id"
t.datetime "property_void_date"
t.integer "renttype" t.integer "renttype"
t.integer "needstype" t.integer "needstype"
t.integer "lettype" t.integer "lettype"

14
spec/features/organisation_spec.rb

@ -26,4 +26,18 @@ RSpec.describe "User Features" do
expect(page).to have_current_path("/organisations/#{org_id}/details") expect(page).to have_current_path("/organisations/#{org_id}/details")
end end
end end
context "Organisation users" do
it "users can be added" do
visit("/organisations/#{org_id}")
click_link("Users")
click_link("Invite user")
expect(page).to have_current_path("/users/new")
expect(page).to have_content("Invite user to submit CORE data")
fill_in("user[name]", with: "New User")
fill_in("user[email]", with: "new_user@example.com")
expect { click_button("Continue") }.to change { ActionMailer::Base.deliveries.count }.by(1)
expect(page).to have_current_path("/organisations/#{org_id}/users")
end
end
end end

46
spec/features/user_spec.rb

@ -96,7 +96,7 @@ RSpec.describe "User Features" do
end end
it "tries to access account page, redirects to log in page" do it "tries to access account page, redirects to log in page" do
visit("/users/account") visit("/users/#{user.id}")
expect(page).to have_content("Sign in to your account to submit CORE data") expect(page).to have_content("Sign in to your account to submit CORE data")
end end
end end
@ -141,42 +141,46 @@ RSpec.describe "User Features" do
visit("/case-logs") visit("/case-logs")
expect(page).to have_link("Your account") expect(page).to have_link("Your account")
click_link("Your account") click_link("Your account")
expect(page).to have_current_path("/users/account") expect(page).to have_current_path("/users/#{user.id}")
end
it "main page is present and accessible" do
visit("/users/account")
expect(page).to have_content("Your account")
end
it "personal details page is present and accessible" do
visit("/users/account/personal-details")
expect(page).to have_content("Change your personal details")
end
it "edit password page present and accessible" do
visit("users/edit")
expect(page).to have_content("Change your password")
end end
it "can navigate to change your password page from main account page" do it "can navigate to change your password page from main account page" do
visit("/users/account") visit("/users/#{user.id}")
find('[data-qa="change-password"]').click find('[data-qa="change-password"]').click
expect(page).to have_content("Change your password") expect(page).to have_content("Change your password")
fill_in("user[current_password]", with: "pAssword1") fill_in("user[current_password]", with: "pAssword1")
fill_in("user[password]", with: "Password123!") fill_in("user[password]", with: "Password123!")
click_button("Update") click_button("Update")
expect(page).to have_current_path("/users/account") expect(page).to have_current_path("/users/#{user.id}")
end end
it "allow user to change name" do it "allow user to change name" do
visit("/users/account") visit("/users/#{user.id}")
find('[data-qa="change-name"]').click find('[data-qa="change-name"]').click
expect(page).to have_content("Change your personal details") expect(page).to have_content("Change your personal details")
fill_in("user[name]", with: "Test New") fill_in("user[name]", with: "Test New")
click_button("Save changes") click_button("Save changes")
expect(page).to have_current_path("/users/account") expect(page).to have_current_path("/users/#{user.id}")
expect(page).to have_content("Test New") expect(page).to have_content("Test New")
end end
end end
context "Adding a new user" do
before(:each) do
visit("/case-logs")
fill_in("user[email]", with: user.email)
fill_in("user[password]", with: "pAssword1")
click_button("Sign in")
end
it "validates email" do
visit("users/new")
fill_in("user[name]", with: "New User")
fill_in("user[email]", with: "thisis'tanemail")
click_button("Continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#user-email-field-error")
expect(page).to have_content(/Enter an email address in the correct format, like name@example.com/)
end
end
end end

6
spec/requests/users/passwords_controller_spec.rb → spec/requests/auth/passwords_controller_spec.rb

@ -1,7 +1,7 @@
require "rails_helper" require "rails_helper"
require_relative "../../support/devise" require_relative "../../support/devise"
RSpec.describe Users::PasswordsController, type: :request do RSpec.describe Auth::PasswordsController, type: :request do
let(:params) { { user: { email: email } } } let(:params) { { user: { email: email } } }
context "when a password reset is requested for a valid email" do context "when a password reset is requested for a valid email" do
@ -18,7 +18,7 @@ RSpec.describe Users::PasswordsController, type: :request do
context "when a password reset is requested with an email that doesn't exist in the system" do context "when a password reset is requested with an email that doesn't exist in the system" do
before do before do
allow_any_instance_of(Users::PasswordsController).to receive(:is_navigational_format?).and_return(false) allow_any_instance_of(Auth::PasswordsController).to receive(:is_navigational_format?).and_return(false)
end end
let(:email) { "madeup_email@test.com" } let(:email) { "madeup_email@test.com" }
@ -32,7 +32,7 @@ RSpec.describe Users::PasswordsController, type: :request do
end end
context "when a password reset is requested the email" do context "when a password reset is requested the email" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now) }
let(:email) { user.email } let(:email) { user.email }
it "should contain the correct email" do it "should contain the correct email" do

5
spec/requests/organisations_controller_spec.rb

@ -4,6 +4,7 @@ RSpec.describe OrganisationsController, type: :request do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:organisation) { user.organisation } let(:organisation) { user.organisation }
let(:headers) { { "Accept" => "text/html" } } let(:headers) { { "Accept" => "text/html" } }
let(:page) { Capybara::Node::Simple.new(response.body) }
context "details tab" do context "details tab" do
before do before do
@ -40,9 +41,7 @@ RSpec.describe OrganisationsController, type: :request do
end end
it "shows a new user button" do it "shows a new user button" do
expected_html = "<a class=\"govuk-button\"" expect(page).to have_link("Invite user")
expect(response.body).to include(expected_html)
expect(response.body).to include("Invite user")
end end
it "shows a table of users" do it "shows a table of users" do

41
spec/requests/user_controller_spec.rb

@ -0,0 +1,41 @@
require "rails_helper"
require_relative "../support/devise"
RSpec.describe UsersController, type: :request do
let(:user) { FactoryBot.create(:user) }
let(:headers) { { "Accept" => "text/html" } }
let(:page) { Capybara::Node::Simple.new(response.body) }
describe "#show" do
before do
sign_in user
get "/users/#{user.id}", headers: headers, params: {}
end
it "show the user details" do
expect(page).to have_content("Your account")
end
end
describe "#edit" do
before do
sign_in user
get "/users/#{user.id}/edit", headers: headers, params: {}
end
it "show the edit personal details page" do
expect(page).to have_content("Change your personal details")
end
end
describe "#edit_password" do
before do
sign_in user
get "/users/#{user.id}/password/edit", headers: headers, params: {}
end
it "show the edit password page" do
expect(page).to have_content("Change your password")
end
end
end
Loading…
Cancel
Save