From afefd952e86d0808eaae1083d2bee468007a7fe1 Mon Sep 17 00:00:00 2001 From: Theotime2005 Date: Thu, 16 Jan 2025 12:10:26 +0100 Subject: [PATCH] feat(api): Add a script to delete most organizations Co-Authored-By: EmmanuelleBonnemay <143821056+EmmanuelleBonnemay@users.noreply.github.com> Co-Authored-By: Benjamin Petetot <516360+bpetetot@users.noreply.github.com> --- .../scripts/delete-organizations-script.js | 64 +++++++++++++++++++ .../delete-organizations-script.test.js | 40 ++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 api/src/organizational-entities/scripts/delete-organizations-script.js create mode 100644 api/tests/organizational-entities/unit/scripts/delete-organizations-script.test.js diff --git a/api/src/organizational-entities/scripts/delete-organizations-script.js b/api/src/organizational-entities/scripts/delete-organizations-script.js new file mode 100644 index 00000000000..d651e311380 --- /dev/null +++ b/api/src/organizational-entities/scripts/delete-organizations-script.js @@ -0,0 +1,64 @@ +import Joi from 'joi'; + +import { csvFileParser } from '../../shared/application/scripts/parsers.js'; +import { Script } from '../../shared/application/scripts/script.js'; +import { ScriptRunner } from '../../shared/application/scripts/script-runner.js'; +import * as dataProtectionOfficerRepository from '../infrastructure/repositories/data-protection-officer.repository.js'; +import { organizationForAdminRepository } from '../infrastructure/repositories/organization-for-admin.repository.js'; +import * as organizationTagRepository from '../infrastructure/repositories/organization-tag.repository.js'; + +const columnsSchema = [{ name: 'Organization ID', schema: Joi.number().required() }]; + +export class DeleteOrganizationsScript extends Script { + constructor() { + super({ + description: 'Delete all organizations and associated tags', + permanent: false, + options: { + file: { + type: 'string', + describe: 'File path to CSV file with organizations to delete', + demandOption: true, + requiresArg: true, + coerce: csvFileParser(columnsSchema), + }, + dryRun: { + type: 'boolean', + describe: 'Run the script without actually deleting anything', + default: false, + }, + }, + }); + } + + async handle({ + options, + logger, + dependencies = { organizationForAdminRepository, organizationTagRepository, dataProtectionOfficerRepository }, + }) { + const { file, dryRun } = options; + + let count = 0; + for (const row of file) { + const organizationId = row['Organization ID']; + if (!dryRun) { + logger.info(organizationId); + // Delete data protection officer via data-protection-officer.repository + await dependencies.dataProtectionOfficerRepository.deleteDpoByOrganizationId(organizationId); + // delete organization tags via organization-tags.repository + await dependencies.organizationTagRepository.deleteTagsByOrganizationId(organizationId); + // delete organizationvia organization-for-admin.repository + await dependencies.organizationForAdminRepository.deleteById(organizationId); + } + count++; + } + + if (dryRun) { + logger.info(`Would delete ${count} organizations.`); + } else { + logger.info(`Deleted ${count} organizations.`); + } + } +} + +await ScriptRunner.execute(import.meta.url, DeleteOrganizationsScript); diff --git a/api/tests/organizational-entities/unit/scripts/delete-organizations-script.test.js b/api/tests/organizational-entities/unit/scripts/delete-organizations-script.test.js new file mode 100644 index 00000000000..e69e1ad7f35 --- /dev/null +++ b/api/tests/organizational-entities/unit/scripts/delete-organizations-script.test.js @@ -0,0 +1,40 @@ +import { DeleteOrganizationsScript } from '../../../../src/organizational-entities/scripts/./delete-organizations-script.js'; +import { expect, sinon } from '../../../test-helper.js'; + +describe('DeleteOrganizationsScript', function () { + describe('Handle', function () { + let script; + let logger; + let organizationForAdminRepository; + let organizationTagRepository; + let dataProtectionOfficerRepository; + + beforeEach(function () { + script = new DeleteOrganizationsScript(); + logger = { info: sinon.spy() }; + organizationForAdminRepository = { deleteById: sinon.stub() }; + organizationTagRepository = { deleteTagsByOrganizationId: sinon.stub() }; + dataProtectionOfficerRepository = { deleteDpoByOrganizationId: sinon.stub() }; + }); + + it('handles data correctly', async function () { + const file = [{ 'Organization ID': 1 }, { 'Organization ID': 2 }]; + + await script.handle({ + options: { file }, + logger, + dependencies: { + organizationForAdminRepository, + organizationTagRepository, + dataProtectionOfficerRepository, + }, + }); + expect(organizationForAdminRepository.deleteById.calledWith(1)).to.be.true; + expect(organizationForAdminRepository.deleteById.calledWith(2)).to.be.true; + expect(organizationTagRepository.deleteTagsByOrganizationId.calledWith(1)).to.be.true; + expect(organizationTagRepository.deleteTagsByOrganizationId.calledWith(2)).to.be.true; + expect(dataProtectionOfficerRepository.deleteDpoByOrganizationId.calledWith(1)).to.be.true; + expect(dataProtectionOfficerRepository.deleteDpoByOrganizationId.calledWith(2)).to.be.true; + }); + }); +});