Skip to content
Merged
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
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Layout/LineLength:
Exclude:
- 'lib/tasks/yarn.rake'
- 'spec/support/api_helpers.rb'
- 'app/controllers/user_details_controller.rb'

Metrics/BlockLength:
Max: 40
Expand Down
4 changes: 4 additions & 0 deletions app/assets/stylesheets/components/button/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ $button-shadow-size: $govuk-border-width-form-element;
.govuk-back-link {
background-color: transparent;
border: none;
}

.button-group {
align-items: normal;
}
26 changes: 26 additions & 0 deletions app/controllers/user_details_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ def update_email
render :edit_email, status: :service_unavailable
end

def deactivate_confirmation
return if current_user.can_deactivate?

redirect_to user_detail_path,
alert: 'You cannot deactivate your account because you are the only active user for a linked supplier.'
end

def deactivate
unless current_user.can_deactivate?
return redirect_to user_detail_path,
alert: 'You cannot deactivate your account because you are the only active user for a linked supplier.'
end

API::User.deactivate

reset_session

redirect_to root_path, notice: 'Your account has been deactivated.'
rescue JSONAPI::Consumer::Errors::UnprocessableEntity
redirect_to user_detail_path,
alert: 'You can no longer deactivate your account because you are the only active user for a linked supplier.'
rescue JSONAPI::Consumer::Errors::ConnectionError
redirect_to deactivate_confirmation_user_detail_path,
alert: 'There was a problem connecting to the user service. Please try again later.'
end

private

def email_update_validation(email, confirmation)
Expand Down
1 change: 1 addition & 0 deletions app/models/api/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module API
class User < Base
custom_endpoint :update_name, on: :collection, request_method: :patch
custom_endpoint :update_email, on: :collection, request_method: :patch
custom_endpoint :deactivate, on: :collection, request_method: :patch
custom_endpoint :user_auth_logs, on: :collection, request_method: :get
end
end
27 changes: 27 additions & 0 deletions app/views/user_details/deactivate_confirmation.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
- page_title 'Deactivate my account'

= link_to 'Back', user_detail_path, { class: 'govuk-back-link', title: 'Back to user profile page' }

.govuk-grid-row
.govuk-grid-column-three-quarters
%h1.govuk-heading-xl
Deactivate my account

%p
By choosing to proceed you will be logged out of the Report-MI service and will no longer be able
to log in using this account. Management information tasks you have previously completed on behalf
of your organisation(s) are preserved and your account access can be restored in the future by
contacting support:
= mail_to(support_email_address)

.govuk-button-group{ class: 'button-group govuk-!-margin-top-7' }
= button_to 'Continue to deactivate my account',
deactivate_user_detail_path,
method: :patch,
class: 'govuk-button',
data: { module: 'govuk-button' }

= link_to 'Cancel',
user_detail_path,
class: 'govuk-button'

2 changes: 2 additions & 0 deletions app/views/user_details/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
%td.govuk-table__cell User since
%td.govuk-table__cell= user_created_date(current_user).to_fs(:default)
%td.govuk-table__cell
- if current_user.can_deactivate?
= link_to 'Deactivate account', deactivate_confirmation_user_detail_path, class: 'govuk-link'
- if @user_logs.present?
%tr.govuk-table__row
%td.govuk-table__cell Previous logins
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
resource :user_detail, only: %i[show edit update] do
get :edit_email
patch :update_email
get :deactivate_confirmation, path: 'deactivate'
patch :deactivate
end

match '/auth/:provider/callback', to: 'sessions#create', via: %i[get post]
Expand Down
30 changes: 30 additions & 0 deletions spec/features/user_can_deactivate_account_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'rails_helper'

RSpec.describe 'deactivating an account' do
before do
mock_sso_with(email: 'email@example.com')
mock_notifications_endpoint!
mock_user_who_can_deactivate_endpoint!
mock_suppliers_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!
mock_deactivate_user_endpoint!
end

scenario 'an eligible user deactivates their account' do
visit '/'
click_button 'sign-in'
visit user_detail_path

click_link 'Deactivate account'

expect(page).to have_content 'Deactivate my account'
expect(page).to have_link('Back', href: user_detail_path)
expect(page).to have_link('Cancel', href: user_detail_path)

click_button 'Continue to deactivate my account'

expect(page).to have_current_path(root_path)
expect(page).to have_content 'Your account has been deactivated.'
end
end
16 changes: 16 additions & 0 deletions spec/fixtures/mocks/user_deactivated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"data": {
"id": "111c2bdd-4090-466b-8dec-66cdf0fc4871",
"type": "users",
"attributes": {
"multiple_suppliers?": false,
"can_deactivate?": true,
"name": "User Name",
"email": "user@example.com",
"created_at": "2025-12-09T15:25:43.025Z"
}
},
"jsonapi": {
"version": "1.0"
}
}
18 changes: 18 additions & 0 deletions spec/fixtures/mocks/user_who_can_deactivate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"data": [
{
"id": "efa8ebb8-de51-4718-b085-a583f8d41e3f",
"type": "users",
"attributes": {
"multiple_suppliers?": false,
"can_deactivate?": true,
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-10-01T12:00:00Z"
}
}
],
"jsonapi": {
"version": "1.0"
}
}
18 changes: 18 additions & 0 deletions spec/fixtures/mocks/user_who_cannot_deactivate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"data": [
{
"id": "efa8ebb8-de51-4718-b085-a583f8d41e3f",
"type": "users",
"attributes": {
"multiple_suppliers?": false,
"can_deactivate?": false,
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-10-01T12:00:00Z"
}
}
],
"jsonapi": {
"version": "1.0"
}
}
1 change: 1 addition & 0 deletions spec/fixtures/mocks/user_with_multiple_suppliers.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "users",
"attributes": {
"multiple_suppliers?": true,
"can_deactivate?": true,
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-10-01T12:00:00Z"
Expand Down
71 changes: 70 additions & 1 deletion spec/requests/user_details_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
RSpec.describe 'the user details page' do
before do
mock_notifications_endpoint!
mock_user_with_multiple_suppliers_endpoint!
mock_suppliers_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!
Expand All @@ -12,6 +11,7 @@
describe 'visiting the user profile page' do
it 'shows the user details' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!

get user_detail_path

Expand All @@ -23,11 +23,77 @@
expect(response.body).to include 'Bandersnatch'
expect(response.body).to include 'San Junipero'
end

context 'when the user can deactivate their account' do
it 'shows the deactivate account option' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!

get user_detail_path

expect(response).to be_successful
expect(response.body).to include 'Deactivate account'
end
end

context 'when the user cannot deactivate their account' do
it 'does not show the deactivate account option' do
stub_signed_in_user
mock_user_who_cannot_deactivate_endpoint!

get user_detail_path

expect(response).to be_successful
expect(response.body).not_to include 'Deactivate my account'
end
end
end

describe 'GET /user_detail/deactivate' do
context 'when the user can deactivate their account' do
it 'renders the deactivate confirmation page' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!

get deactivate_confirmation_user_detail_path

expect(response).to be_successful
expect(response.body).to include 'Deactivate my account'
end
end

context 'when deactivating the account is successful' do
it 'redirects to the sign out page with a notice' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!
mock_deactivate_user_endpoint!

patch deactivate_user_detail_path

expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq 'Your account has been deactivated.'
end
end

context 'when the user cannot deactivate their account' do
it 'redirects to the user profile page with an alert' do
stub_signed_in_user
mock_user_who_cannot_deactivate_endpoint!

get deactivate_confirmation_user_detail_path

expect(response).to redirect_to(user_detail_path)
follow_redirect!

expect(response.body).to include 'You cannot deactivate your account because you are the only active user for'
end
end
end

describe 'GET /user_detail/edit' do
it 'renders the edit form' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!

get edit_user_detail_path

Expand All @@ -41,6 +107,7 @@
describe 'PATCH /user_detail' do
it 'updates the user name and redirects to the profile page on success' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!
mock_update_user_name_endpoint!

patch user_detail_path, params: { name: 'New Name' }
Expand All @@ -53,6 +120,7 @@

it 're-renders the edit form with an error message on failure' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!
mock_update_user_name_endpoint_failure!

patch user_detail_path, params: { name: 'New Name' }
Expand All @@ -63,6 +131,7 @@

it 'handles connection errors gracefully' do
stub_signed_in_user
mock_user_with_multiple_suppliers_endpoint!
mock_update_user_name_endpoint_connection_error!

patch user_detail_path, params: { name: 'New Name' }
Expand Down
24 changes: 24 additions & 0 deletions spec/support/api_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,30 @@ def mock_user_with_multiple_suppliers_endpoint!
)
end

def mock_user_who_cannot_deactivate_endpoint!
stub_request(:get, api_url("users?filter[auth_id]=#{JWT.encode(mock_auth_id, 'test')}"))
.to_return(
headers: json_headers,
body: json_fixture_file('user_who_cannot_deactivate.json')
)
end

def mock_user_who_can_deactivate_endpoint!
stub_request(:get, api_url("users?filter[auth_id]=#{JWT.encode(mock_auth_id, 'test')}"))
.to_return(
headers: json_headers,
body: json_fixture_file('user_who_can_deactivate.json')
)
end

def mock_deactivate_user_endpoint!
stub_request(:patch, api_url('users/deactivate'))
.to_return(
headers: json_headers,
body: json_fixture_file('user_deactivated.json')
)
end

def mock_customer_effort_score_endpoint!
feedback_params = {
data: {
Expand Down