From 272aea6127eb784dc0a7ba928f3f42ba202ae267 Mon Sep 17 00:00:00 2001 From: Ridhwana Date: Mon, 17 Jul 2023 12:09:24 +0200 Subject: [PATCH] Allow admins to delete an organization (#19699) * feat: update the layout for organizations * feat: update the layout for an organization * feat: organization logos are square * feat: change the text on the org visit button * feat: initialize the dropdown menu * feat: trigger the modal * feat: update the text in the model * feat: tweak wording * WIP/feat: hook up a route with an action that calls an organization delete worker. * feat: redirect after showing the notice * feat: update the authorization required to be super admin * feat: add the notice to localization * spec: integration test * spec: fix * spec: delete worker * chore: update the link * feat: add safe navigation * feat: make sure that we show an error if the organization has credits * fix: error message for modal unspent credits * feat add the spec for the helper * fix: remove rspec preface * fix: small fixes to tests and I18n * fix: updated the number of orgs * feat: update the message * feat: update the message * chore: remove inclusion of helper * feat: change the name of the parameter * chore: newline --- .../admin/organizations_controller.rb | 12 ++ app/controllers/billboards_controller.rb | 2 +- app/controllers/organizations_controller.rb | 2 +- app/helpers/admin/organizations_helper.rb | 16 +++ app/javascript/packs/admin/organizations.jsx | 9 ++ .../packs/admin/organizations/modals.js | 45 ++++++ app/views/admin/organizations/show.html.erb | 132 ++++++++++++------ app/workers/organizations/delete_worker.rb | 13 +- config/locales/controllers/admin/en.yml | 2 + config/locales/controllers/admin/fr.yml | 2 + config/locales/views/admin/en.yml | 21 ++- config/locales/views/admin/fr.yml | 23 ++- config/routes/admin.rb | 2 +- .../organizations/manageOrganizations.spec.js | 70 ++++++++++ .../adminFlows/shared/adminUtilities.js | 20 +++ .../adminFlows/users/userIndexView.spec.js | 4 +- .../admin/organizations_helper_spec.rb | 28 ++++ spec/requests/user/user_organization_spec.rb | 2 +- spec/support/seeds/seeds_e2e.rb | 18 +++ .../organizations/delete_worker_spec.rb | 85 +++++------ 20 files changed, 415 insertions(+), 93 deletions(-) create mode 100644 app/helpers/admin/organizations_helper.rb create mode 100644 app/javascript/packs/admin/organizations.jsx create mode 100644 app/javascript/packs/admin/organizations/modals.js create mode 100644 cypress/e2e/seededFlows/adminFlows/organizations/manageOrganizations.spec.js create mode 100644 cypress/e2e/seededFlows/adminFlows/shared/adminUtilities.js create mode 100644 spec/helpers/admin/organizations_helper_spec.rb diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb index a3392692c..cd5713126 100644 --- a/app/controllers/admin/organizations_controller.rb +++ b/app/controllers/admin/organizations_controller.rb @@ -29,6 +29,18 @@ module Admin redirect_to admin_organization_path(org) end + def destroy + organization = Organization.find_by(id: params[:id]) + Organizations::DeleteWorker.perform_async(organization.id, current_user.id, false) + + flash[:settings_notice] = + I18n.t("admin.organizations_controller.deletion_scheduled", organization_name: organization.name) + redirect_to admin_organization_url(params[:id]) + rescue StandardError => e + flash[:error] = I18n.t("admin.organizations_controller.error", organization_name: organization.name, error: e) + redirect_to user_settings_path(:organization, id: organization.id) + end + private def add_note(org) diff --git a/app/controllers/billboards_controller.rb b/app/controllers/billboards_controller.rb index 0bd51ff1c..1fd37383b 100644 --- a/app/controllers/billboards_controller.rb +++ b/app/controllers/billboards_controller.rb @@ -37,6 +37,6 @@ class BillboardsController < ApplicationController def user_tags return unless feed_targeted_tag_placement?(placement_area) - current_user.cached_followed_tag_names + current_user&.cached_followed_tag_names end end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index ccfcee31e..ee5937b9d 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -76,7 +76,7 @@ class OrganizationsController < ApplicationController organization = Organization.find_by(id: params[:id]) authorize organization - Organizations::DeleteWorker.perform_async(organization.id, current_user.id) + Organizations::DeleteWorker.perform_async(organization.id, current_user.id, true) flash[:settings_notice] = I18n.t("organizations_controller.deletion_scheduled", organization_name: organization.name) diff --git a/app/helpers/admin/organizations_helper.rb b/app/helpers/admin/organizations_helper.rb new file mode 100644 index 000000000..5028af461 --- /dev/null +++ b/app/helpers/admin/organizations_helper.rb @@ -0,0 +1,16 @@ +module Admin + module OrganizationsHelper + def deletion_modal_error_message(organization) + error_message = nil + unless current_user.super_admin? + error_message = I18n.t("views.admin.organizations.delete.role_notice") + end + + if organization.credits.length.positive? + error_message = "#{error_message} #{I18n.t('views.admin.organizations.delete.credits_notice')}" + end + + error_message&.strip + end + end +end diff --git a/app/javascript/packs/admin/organizations.jsx b/app/javascript/packs/admin/organizations.jsx new file mode 100644 index 000000000..1601e1832 --- /dev/null +++ b/app/javascript/packs/admin/organizations.jsx @@ -0,0 +1,9 @@ +import { showOrganizationModal } from '././organizations/modals'; +import { initializeDropdown } from '@utilities/dropdownUtils'; + +initializeDropdown({ + triggerElementId: 'options-dropdown-trigger', + dropdownContentId: 'options-dropdown', +}); + +document.body.addEventListener('click', showOrganizationModal); diff --git a/app/javascript/packs/admin/organizations/modals.js b/app/javascript/packs/admin/organizations/modals.js new file mode 100644 index 000000000..70937fb60 --- /dev/null +++ b/app/javascript/packs/admin/organizations/modals.js @@ -0,0 +1,45 @@ +import { showWindowModal } from '@utilities/showModal'; + +const modalContents = new Map(); +/** + * Helper function to handle finding and caching modal content. Since our Preact modal helper works by duplicating HTML content, + * and our user modals rely on IDs to label form controls, we remove the original hidden content from the DOM to avoid ID conflicts. + * + * @param {string} modalContentSelector The CSS selector used to identify the correct modal content + */ +const getModalContents = (modalContentSelector) => { + if (!modalContents.has(modalContentSelector)) { + const modalContentElement = document.querySelector(modalContentSelector); + const modalContent = modalContentElement.innerHTML; + + modalContentElement.remove(); + modalContents.set(modalContentSelector, modalContent); + } + + return modalContents.get(modalContentSelector); +}; + +/** + * Helper function for views which use admin user modals. May be attached as an event listener, and its actions will only be triggered + * if the target of the event is a recognised user modal trigger. + * + * @param {Object} event + */ +export const showOrganizationModal = (event) => { + const { dataset } = event.target; + + if (!Object.prototype.hasOwnProperty.call(dataset, 'modalContentSelector')) { + // We're not trying to trigger a modal. + return; + } + + event.preventDefault(); + + const { modalTitle, modalSize, modalContentSelector } = dataset; + + showWindowModal({ + modalContent: getModalContents(modalContentSelector), + title: modalTitle, + size: modalSize, + }); +}; diff --git a/app/views/admin/organizations/show.html.erb b/app/views/admin/organizations/show.html.erb index 2e80af86e..a32cb3ad9 100644 --- a/app/views/admin/organizations/show.html.erb +++ b/app/views/admin/organizations/show.html.erb @@ -1,48 +1,86 @@ -
-
-

<%= @organization.name %>

-

Created <%= @organization.created_at.strftime("%b %e '%y") %>

-
- -
- <%= link_to "View @#{@organization.name}", "/#{@organization.slug}", class: "crayons-btn", target: "_blank", rel: "noopener" %> -
+<%= javascript_packs_with_chunks_tag "admin/organizations", defer: true %> +
+
+ +
+
+
+

+ <%= @organization.name %> +

+
+
+ <%= link_to t("views.admin.organizations.profile.visit"), "/#{@organization.slug}", class: "c-cta", target: "_blank", rel: "noopener" %> + +
+
+
+ <%= @organization.slug %> <%= t("views.admin.organizations.profile.id", organizationid: @organization.id) %> + <%= t("views.admin.organizations.profile.since_html", time: tag.time(l(@organization.created_at, format: :short_with_yy), datetime: @organization.created_at.strftime("%Y-%m-%dT%H:%M:%S%z"), title: l(@organization.created_at, format: :admin_user))) %> + <%= t("views.admin.organizations.profile.total_members", count: @organization.organization_memberships.size) %> +
+
+ +
+
+
-
-

General Info

-
-
ID:
-
<%= @organization.id %>
-
Name:
-
<%= @organization.name %>
-
Membership Count:
-
<%= @organization.organization_memberships.size %>
-
Email:
-
<%= @organization.email || "N/A" %>
-
Twitter:
- <% if @organization.twitter_username %> -
<%= link_to @organization.twitter_username, "https://twitter.com/#{@organization.twitter_username}" %>
- <% else %> -
N/A
- <% end %> -
GitHub:
- <% if @organization.github_username %> -
<%= link_to @organization.github_username, "https://github.com/#{@organization.github_username}" %>
- <% else %> -
N/A
- <% end %> -
-
-
<% current_credits = @organization.unspent_credits_count %>

Credits (current: <%= current_credits %>)

<%= form_tag update_org_credits_admin_organization_path(@organization), method: :patch, class: "flex justify-between mb-2" do %>
<%= hidden_field_tag :credit_action, :add %> - <%= number_field_tag :credits, nil, in: 1...100_000, required: true, class: "crayons-textfield w-auto mr-3", size: 5, aria: { label: "Credits" } %> - <%= text_field_tag :note, "", placeholder: "Why are you adding these credits?", size: 50, required: true, class: "crayons-textfield w-auto mr-3", aria: { label: "Reason" } %> + <%= number_field_tag :credits, nil, in: 1...100_000, required: true, class: "crayons-textfield w-auto mr-3", size: 5, aria: { label: "Credits" }, autocomplete: "off" %> + <%= text_field_tag :note, "", placeholder: "Why are you adding these credits?", size: 50, required: true, class: "crayons-textfield w-auto mr-3", aria: { label: "Reason" }, autocomplete: "off" %>
<%= submit_tag "Add Org Credits", class: "crayons-btn" %> <% end %> @@ -50,8 +88,8 @@ <%= form_tag update_org_credits_admin_organization_path(@organization), method: :patch, class: "flex justify-between mb-2" do %>
<%= hidden_field_tag :credit_action, :remove %> - <%= number_field_tag :credits, nil, in: 1..current_credits, required: true, class: "crayons-textfield w-auto mr-3", size: 5, aria: { label: "Credits" } %> - <%= text_field_tag :note, "", placeholder: "Why are you removing these credits?", size: 50, required: true, class: "crayons-textfield w-auto mr-3", aria: { label: "Reason" } %> + <%= number_field_tag :credits, nil, in: 1..current_credits, required: true, class: "crayons-textfield w-auto mr-3", size: 5, aria: { label: "Credits" }, autocomplete: "off" %> + <%= text_field_tag :note, "", placeholder: "Why are you removing these credits?", size: 50, required: true, class: "crayons-textfield w-auto mr-3", aria: { label: "Reason" }, autocomplete: "off" %>
<%= submit_tag "Remove Org Credits", class: "crayons-btn crayons-btn--danger" %> <% end %> @@ -59,3 +97,19 @@
<%= render "activity" %> + +<% error_message = deletion_modal_error_message(@organization) %> + diff --git a/app/workers/organizations/delete_worker.rb b/app/workers/organizations/delete_worker.rb index b248aa7eb..32a2775d1 100644 --- a/app/workers/organizations/delete_worker.rb +++ b/app/workers/organizations/delete_worker.rb @@ -5,7 +5,9 @@ module Organizations sidekiq_options queue: :high_priority, retry: 10 # operator_id - the user who deletes the organization - def perform(organization_id, operator_id) + # deleted_by_org_admin - the organization can be deleted by either + # the admin of an organization or by the Forem Admin. + def perform(organization_id, operator_id, deleted_by_org_admin) org = Organization.find_by(id: organization_id) return unless org @@ -14,11 +16,12 @@ module Organizations Organizations::Delete.call(org) - user.touch(:organization_info_updated_at) - EdgeCache::BustUser.call(user) + if deleted_by_org_admin + user.touch(:organization_info_updated_at) + EdgeCache::BustUser.call(user) - # notify user that the org was deleted - NotifyMailer.with(name: user.name, org_name: org.name, email: user.email).organization_deleted_email.deliver_now + NotifyMailer.with(name: user.name, org_name: org.name, email: user.email).organization_deleted_email.deliver_now + end audit_log(org, user) rescue StandardError => e diff --git a/config/locales/controllers/admin/en.yml b/config/locales/controllers/admin/en.yml index 1abf19ff6..439e99b11 100644 --- a/config/locales/controllers/admin/en.yml +++ b/config/locales/controllers/admin/en.yml @@ -66,6 +66,8 @@ en: updated: User was successfully updated to %{type} organizations_controller: credit_updated: Successfully updated credits + deletion_scheduled: 'Organization, "%{organization_name}", deletion is scheduled.' + error: 'Your organization, %{organization_name}, could not be deleted: %{error}.' pages_controller: updated: Page has been successfully updated. created: Page has been successfully created. diff --git a/config/locales/controllers/admin/fr.yml b/config/locales/controllers/admin/fr.yml index 89290ffa7..f5d4bede0 100644 --- a/config/locales/controllers/admin/fr.yml +++ b/config/locales/controllers/admin/fr.yml @@ -66,6 +66,8 @@ fr: updated: L'utilisateur a été mis à jour avec succès vers %{type} organizations_controller: credit_updated: Crédits actualisés avec succès + deletion_scheduled: 'Organization, "%{organization_name}", deletion is scheduled.' + error: 'Your organization, %{organization_name}, could not be deleted: %{error}.' pages_controller: updated: La page a été mise à jour avec succès. created: La page a été créée avec succès. diff --git a/config/locales/views/admin/en.yml b/config/locales/views/admin/en.yml index 23bae1906..301832142 100644 --- a/config/locales/views/admin/en.yml +++ b/config/locales/views/admin/en.yml @@ -147,7 +147,7 @@ en: submit_html: Banish %{user} delete: heading: Delete %{user}'s account - onsubmit: Are you sure? This actions is irreversible. + onsubmit: Are you sure? This action is irreversible. desc2: Once deleted, all data relating to %{user} will be removed from our database. notice: Only Super Admins are allowed to delete users. submit: Delete now @@ -389,3 +389,22 @@ en: facebook: Facebook github: GitHub twitter: Twitter + organizations: + profile: + id: ID %{organizationid} + since_html: Organization since %{time} + email: Email + github: GitHub + twitter: Twitter + visit: Visit organization + total_members: "%{count} members" + delete: + heading: Delete %{organization} + onsubmit: Are you sure? This action is irreversible. + desc2: "Note: Deleting organization, %{organization}, will dissasociate the existing members and articles from the organization but will not delete them." + role_notice: Only Super Admins are allowed to delete organizations. + credits_notice: You cannot delete an organization that has associated credits. + submit: Delete organizion now + options: + delete: Delete organization + icon: Options diff --git a/config/locales/views/admin/fr.yml b/config/locales/views/admin/fr.yml index a1da346a5..d6cd02357 100644 --- a/config/locales/views/admin/fr.yml +++ b/config/locales/views/admin/fr.yml @@ -17,7 +17,7 @@ fr: description: Toutes les actions du modérateur affectent le score. Le score global est une combinaison des actions des modérateurs et des réactions du public. no_flags: Le commentaire n'a pas de drapeaux. no_quality_reactions: Le commentaire n'a pas de réactions de qualité par des utilisateurs de confiance. - title: Actions du modérateur + title: Actions du modérateur shared: flags: actions: @@ -147,7 +147,7 @@ fr: submit_html: Banish %{user} delete: heading: Delete %{user}'s account - onsubmit: Are you sure? This actions is irreversible. + onsubmit: Are you sure? This action is irreversible. desc2: Once deleted, all data relating to %{user} will be removed from our database. notice: Only Super Admins are allowed to delete users. submit: Delete now @@ -389,3 +389,22 @@ fr: facebook: Facebook github: GitHub twitter: Twitter + organizations: + profile: + id: ID %{organizationid} + since_html: Organization since %{time} + email: Email + github: GitHub + twitter: Twitter + visit: Visit organization + total_members: "%{count} members" + delete: + heading: Delete %{organization} + onsubmit: Are you sure? This action is irreversible. + desc2: "Note: Deleting organization, %{organization}, will dissasociate the existing members and articles from the organization but will not delete them." + role_notice: Only Super Admins are allowed to delete organizations. + credits_notice: You cannot delete an organization that has associated credits. + submit: Delete organizion now + options: + delete: Delete organization + icon: Options diff --git a/config/routes/admin.rb b/config/routes/admin.rb index e4e47453b..5fce1d6b8 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -83,7 +83,7 @@ namespace :admin do get "/badge_achievements/award_badges", to: "badge_achievements#award" post "/badge_achievements/award_badges", to: "badge_achievements#award_badges" resources :comments, only: %i[index show] - resources :organizations, only: %i[index show] do + resources :organizations, only: %i[index show destroy] do member do patch "update_org_credits" end diff --git a/cypress/e2e/seededFlows/adminFlows/organizations/manageOrganizations.spec.js b/cypress/e2e/seededFlows/adminFlows/organizations/manageOrganizations.spec.js new file mode 100644 index 000000000..ee124161b --- /dev/null +++ b/cypress/e2e/seededFlows/adminFlows/organizations/manageOrganizations.spec.js @@ -0,0 +1,70 @@ +import { verifyAndDismissFlashMessage } from '../shared/adminUtilities'; + +function openOrganizationOptions(callback) { + cy.findByRole('button', { name: 'Options' }) + .should('have.attr', 'aria-haspopup', 'true') + .should('have.attr', 'aria-expanded', 'false') + .click() + .then(([button]) => { + expect(button.getAttribute('aria-expanded')).to.equal('true'); + const dropdownId = button.getAttribute('aria-controls'); + + cy.get(`#${dropdownId}`).within(callback); + }); +} + +describe('Manage Organization Options', () => { + describe('As a super admin', () => { + beforeEach(() => { + cy.testSetup(); + cy.fixture('users/adminUser.json').as('user'); + cy.get('@user').then((user) => { + cy.loginAndVisit(user, '/admin/content_manager/organizations'); + }); + + cy.findByRole('table').within(() => { + cy.findAllByRole('link', { name: '@Awesome Org' }).first().click(); + }); + }); + + it('should show a notice about the scheduled job', () => { + openOrganizationOptions(() => { + cy.findByRole('button', { name: 'Delete organization' }).click(); + }); + + cy.getModal().within(() => { + cy.findByRole('button', { name: 'Delete organizion now' }).click(); + }); + + verifyAndDismissFlashMessage( + `Organization, "Awesome Org", deletion is scheduled.`, + ); + }); + }); + + describe('As an organization that has existing credits', () => { + beforeEach(() => { + cy.testSetup(); + cy.fixture('users/adminUser.json').as('user'); + cy.get('@user').then((user) => { + cy.loginAndVisit(user, '/admin/content_manager/organizations'); + }); + + cy.findByRole('table').within(() => { + cy.findAllByRole('link', { name: '@Credits Org' }).first().click(); + }); + }); + + it('should show an error', () => { + openOrganizationOptions(() => { + cy.findByRole('button', { name: 'Delete organization' }).click(); + }); + + cy.getModal().within(() => { + cy.findByText( + 'You cannot delete an organization that has associated credits.', + ).should('exist'); + }); + }); + }); +}); diff --git a/cypress/e2e/seededFlows/adminFlows/shared/adminUtilities.js b/cypress/e2e/seededFlows/adminFlows/shared/adminUtilities.js new file mode 100644 index 000000000..c138c6607 --- /dev/null +++ b/cypress/e2e/seededFlows/adminFlows/shared/adminUtilities.js @@ -0,0 +1,20 @@ +/** + * E2E helper function for user admin tests that validates the correct flash notice message appears + * + * @param {string} [message] The expected flash message text + */ +export function verifyAndDismissFlashMessage(message) { + cy.findByTestId('flash-settings_notice') + .as('notice') + .then((element) => { + expect(element.text().trim()).equal(message); + }); + + cy.get('@notice').within(() => { + cy.findByRole('button', { name: 'Dismiss message' }) + .should('have.focus') + .click(); + }); + + cy.findByTestId('flash-settings_notice').should('not.exist'); +} diff --git a/cypress/e2e/seededFlows/adminFlows/users/userIndexView.spec.js b/cypress/e2e/seededFlows/adminFlows/users/userIndexView.spec.js index 72523553a..8273126f3 100644 --- a/cypress/e2e/seededFlows/adminFlows/users/userIndexView.spec.js +++ b/cypress/e2e/seededFlows/adminFlows/users/userIndexView.spec.js @@ -37,7 +37,7 @@ describe('User index view', () => { cy.findAllByText('Good standing').should('exist'); cy.findByText('Last activity').should('exist'); cy.findByText('Joined on').should('exist'); - cy.findByRole('figure').findByText('+ 1').should('exist'); + cy.findByRole('figure').findByText('+ 2').should('exist'); }); }); @@ -160,7 +160,7 @@ describe('User index view', () => { ); cy.findByAltText('Many orgs user').should('exist'); cy.findAllByText('Good standing').should('exist'); - cy.findByRole('figure').findByText('+ 1').should('exist'); + cy.findByRole('figure').findByText('+ 2').should('exist'); }); }); diff --git a/spec/helpers/admin/organizations_helper_spec.rb b/spec/helpers/admin/organizations_helper_spec.rb new file mode 100644 index 000000000..b29beb0f8 --- /dev/null +++ b/spec/helpers/admin/organizations_helper_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +describe Admin::OrganizationsHelper do + describe "#deletion_modal_error_message" do + let(:organization) { create(:organization) } + let(:super_admin) { create(:user, :super_admin) } + let(:admin) { create(:user, :admin) } + + context "when the current user is not a super admin" do + before { allow(helper).to receive(:current_user).and_return(admin) } + + it "returns the appropriate error messsage" do + error_message = helper.deletion_modal_error_message(organization) + expect(error_message).to eq("Only Super Admins are allowed to delete organizations.") + end + end + + context "when there are credits associated to an organization" do + before { allow(helper).to receive(:current_user).and_return(super_admin) } + + it "returns the appropriate error messsage" do + Credit.add_to(organization, 10) + error_message = helper.deletion_modal_error_message(organization) + expect(error_message).to eq("You cannot delete an organization that has associated credits.") + end + end + end +end diff --git a/spec/requests/user/user_organization_spec.rb b/spec/requests/user/user_organization_spec.rb index 0c444abe7..bace2b953 100644 --- a/spec/requests/user/user_organization_spec.rb +++ b/spec/requests/user/user_organization_spec.rb @@ -180,7 +180,7 @@ RSpec.describe "UserOrganization" do end it "deletes the organization" do - sidekiq_assert_enqueued_with(job: Organizations::DeleteWorker, args: [org_id, org_admin.id]) do + sidekiq_assert_enqueued_with(job: Organizations::DeleteWorker, args: [org_id, org_admin.id, true]) do delete organization_path(org_id) end end diff --git a/spec/support/seeds/seeds_e2e.rb b/spec/support/seeds/seeds_e2e.rb index 330b8633e..fed9c35cd 100644 --- a/spec/support/seeds/seeds_e2e.rb +++ b/spec/support/seeds/seeds_e2e.rb @@ -303,6 +303,24 @@ seeder.create_if_doesnt_exist(Organization, "slug", "org4") do ) end +seeder.create_if_doesnt_exist(Organization, "slug", "creditsorg") do + organization = Organization.create!( + name: "Credits Org", + summary: Faker::Company.bs, + profile_image: Rails.root.join("app/assets/images/#{rand(1..40)}.png").open, + url: Faker::Internet.url, + slug: "creditsorg", + ) + + OrganizationMembership.create!( + user_id: many_orgs_user.id, + organization_id: organization.id, + type_of_user: "member", + ) + + Credit.add_to(organization, 100) +end + ############################################################################## seeder.create_if_doesnt_exist(User, "email", "change-password-user@forem.com") do diff --git a/spec/workers/organizations/delete_worker_spec.rb b/spec/workers/organizations/delete_worker_spec.rb index f33dea905..ee51b5f7c 100644 --- a/spec/workers/organizations/delete_worker_spec.rb +++ b/spec/workers/organizations/delete_worker_spec.rb @@ -10,82 +10,87 @@ RSpec.describe Organizations::DeleteWorker, type: :worker do let(:mailer_class) { NotifyMailer } let(:mailer) { double } let(:message_delivery) { double } + let(:update_and_notify_user) { true } context "when org and user are found" do it "destroys the org" do - worker.perform(org.id, user.id) + worker.perform(org.id, user.id, update_and_notify_user) expect(Organization.exists?(id: org.id)).to be(false) end it "calls the service" do allow(delete).to receive(:call) - worker.perform(org.id, user.id) + worker.perform(org.id, user.id, update_and_notify_user) expect(delete).to have_received(:call).with(org) end - it "touches the user" do - allow(User).to receive(:find_by) { user } - allow(user).to receive(:touch) - worker.perform(org.id, user.id) - expect(user).to have_received(:touch).with(:organization_info_updated_at) - end - - it "busts user's cache" do - bust_cache = EdgeCache::BustUser - allow(bust_cache).to receive(:call) - worker.perform(org.id, user.id) - expect(bust_cache).to have_received(:call).with(user) - end - - it "sends the notification" do - allow(ForemInstance).to receive(:smtp_enabled?).and_return(true) - expect do - worker.perform(org.id, user.id) - end.to change(ActionMailer::Base.deliveries, :count).by(1) - end - - it "sends the correct notification" do - allow(mailer_class).to receive(:with).and_return(mailer) - allow(mailer).to receive(:organization_deleted_email).and_return(message_delivery) - allow(message_delivery).to receive(:deliver_now) - - worker.perform(org.id, user.id) - - expect(mailer_class).to have_received(:with).with(org_name: org.name, name: user.name, email: user.email) - expect(mailer).to have_received(:organization_deleted_email) - expect(message_delivery).to have_received(:deliver_now) - end - it "creates an audit_log record" do expect do - worker.perform(org.id, user.id) + worker.perform(org.id, user.id, update_and_notify_user) end.to change(AuditLog, :count).by(1) end it "creates a correct AuditLog record" do - worker.perform(org.id, user.id) + worker.perform(org.id, user.id, update_and_notify_user) audit_log = AuditLog.find_by(category: "user.organization.delete", slug: "organization_delete", user_id: user.id) expect(audit_log).to be_present expect(audit_log.data["organization_id"]).to eq(org.id) expect(audit_log.data["organization_slug"]).to eq(org.slug) end + + # rubocop:disable RSpec/NestedGroups + context "when update_and_notify_user is true" do + it "touches the user" do + allow(User).to receive(:find_by) { user } + allow(user).to receive(:touch) + worker.perform(org.id, user.id, update_and_notify_user) + expect(user).to have_received(:touch).with(:organization_info_updated_at) + end + + it "busts user's cache" do + bust_cache = EdgeCache::BustUser + allow(bust_cache).to receive(:call) + worker.perform(org.id, user.id, update_and_notify_user) + expect(bust_cache).to have_received(:call).with(user) + end + + it "sends the notification" do + allow(ForemInstance).to receive(:smtp_enabled?).and_return(true) + expect do + worker.perform(org.id, user.id, update_and_notify_user) + end.to change(ActionMailer::Base.deliveries, :count).by(1) + end + + it "sends the correct notification" do + allow(mailer_class).to receive(:with).and_return(mailer) + allow(mailer).to receive(:organization_deleted_email).and_return(message_delivery) + allow(message_delivery).to receive(:deliver_now) + + worker.perform(org.id, user.id, update_and_notify_user) + + expect(mailer_class).to have_received(:with).with(org_name: org.name, name: user.name, email: user.email) + expect(mailer).to have_received(:organization_deleted_email) + expect(message_delivery).to have_received(:deliver_now) + end + end + # rubocop:enable RSpec/NestedGroups end context "when an org or a user is not found" do it "doesn't fail" do - worker.perform(-1, -1) + worker.perform(-1, -1, update_and_notify_user) end it "doesn't call org delete when a user was not found" do allow(delete).to receive(:call) - worker.perform(org.id, -1) + worker.perform(org.id, -1, update_and_notify_user) expect(delete).not_to have_received(:call) end it "doesn't call org delete when an org was not found" do allow(delete).to receive(:call) - worker.perform(-1, org.id) + worker.perform(-1, org.id, update_and_notify_user) expect(delete).not_to have_received(:call) end end