Skip to content

Commit

Permalink
[TECH] Migrer la route /api/admin/organizations/{id}/archive (PIX-16755)
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Feb 28, 2025
2 parents c93bb13 + 5cc5586 commit 1861e01
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 201 deletions.
28 changes: 0 additions & 28 deletions api/lib/application/organizations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,34 +116,6 @@ const register = async function (server) {
],
},
},
{
method: 'POST',
path: '/api/admin/organizations/{id}/archive',
config: {
pre: [
{
method: (request, h) =>
securityPreHandlers.hasAtLeastOneAccessOf([
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
securityPreHandlers.checkAdminMemberHasRoleSupport,
securityPreHandlers.checkAdminMemberHasRoleMetier,
])(request, h),
assign: 'hasAuthorizationToAccessAdminScope',
},
],
validate: {
params: Joi.object({
id: identifiersType.organizationId,
}),
},
handler: organizationController.archiveOrganization,
tags: ['api', 'organizations'],
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
"- Elle permet d'archiver une organisation",
],
},
},
{
method: 'GET',
path: '/api/admin/organizations/{organizationId}/children',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { extractUserIdFromRequest } from '../../../shared/infrastructure/utils/request-response-utils.js';
import { usecases } from '../../domain/usecases/index.js';
import { organizationTagCsvParser } from '../../infrastructure/parsers/csv/organization-tag-csv.parser.js';
import { organizationForAdminSerializer } from '../../infrastructure/serializers/jsonapi/organizations-administration/organization-for-admin.serializer.js';
Expand All @@ -10,6 +11,13 @@ const addTagsToOrganizations = async function (request, h) {
return h.response().code(204);
};

const archiveOrganization = async function (request, h, dependencies = { organizationForAdminSerializer }) {
const organizationId = request.params.id;
const userId = extractUserIdFromRequest(request);
const archivedOrganization = await usecases.archiveOrganization({ organizationId, userId });
return dependencies.organizationForAdminSerializer.serialize(archivedOrganization);
};

const attachChildOrganization = async function (request, h) {
const { childOrganizationIds } = request.payload;
const { organizationId: parentOrganizationId } = request.params;
Expand Down Expand Up @@ -59,6 +67,7 @@ const updateOrganizationInformation = async function (

const organizationAdminController = {
addTagsToOrganizations,
archiveOrganization,
attachChildOrganization,
addOrganizationFeatureInBatch,
getOrganizationDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const register = async function (server) {
}),
},
handler: (request, h) => organizationAdminController.getOrganizationDetails(request, h),
tags: ['api', 'organizations'],
tags: ['api', 'admin', 'organizational-entities', 'organizations'],
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
'- Elle permet de récupérer toutes les informations d’une organisation',
Expand Down Expand Up @@ -74,13 +74,41 @@ const register = async function (server) {
},
},
handler: (request, h) => organizationAdminController.updateOrganizationInformation(request, h),
tags: ['api', 'organizations'],
tags: ['api', 'admin', 'organizational-entities', 'organizations'],
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
'- Elle permet de mettre à jour tout ou partie d’une organisation',
],
},
},
{
method: 'POST',
path: '/api/admin/organizations/{id}/archive',
config: {
pre: [
{
method: (request, h) =>
securityPreHandlers.hasAtLeastOneAccessOf([
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
securityPreHandlers.checkAdminMemberHasRoleSupport,
securityPreHandlers.checkAdminMemberHasRoleMetier,
])(request, h),
assign: 'hasAuthorizationToAccessAdminScope',
},
],
validate: {
params: Joi.object({
id: identifiersType.organizationId,
}),
},
handler: organizationAdminController.archiveOrganization,
tags: ['api', 'admin', 'organizational-entities', 'organizations'],
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
"- Elle permet d'archiver une organisation",
],
},
},
{
method: 'POST',
path: '/api/admin/organizations/{organizationId}/attach-child-organization',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,49 +638,6 @@ describe('Acceptance | Application | organization-controller', function () {
});
});

describe('POST /api/admin/organizations/{id}/archive', function () {
it('should return the archived organization', async function () {
// given
const adminUser = databaseBuilder.factory.buildUser.withRole();
const organizationId = databaseBuilder.factory.buildOrganization().id;
databaseBuilder.factory.buildOrganization({ id: 2 });

// Invitations
databaseBuilder.factory.buildOrganizationInvitation({
organizationId,
status: OrganizationInvitation.StatusType.PENDING,
});
databaseBuilder.factory.buildOrganizationInvitation({
organizationId,
status: OrganizationInvitation.StatusType.PENDING,
});

// Campaigns
databaseBuilder.factory.buildCampaign({ id: 1, organizationId });
databaseBuilder.factory.buildCampaign({ id: 2, organizationId });

// Memberships
databaseBuilder.factory.buildUser({ id: 7 });
databaseBuilder.factory.buildMembership({ id: 1, userId: 7, organizationId });
databaseBuilder.factory.buildUser({ id: 8 });
databaseBuilder.factory.buildMembership({ id: 2, userId: 8, organizationId });

await databaseBuilder.commit();

// when
const response = await server.inject({
method: 'POST',
url: `/api/admin/organizations/${organizationId}/archive`,
headers: generateAuthenticatedUserRequestHeaders({ userId: adminUser.id }),
});

// then
expect(response.statusCode).to.equal(200);
const archivedOrganization = response.result.data.attributes;
expect(archivedOrganization['archivist-full-name']).to.equal(`${adminUser.firstName} ${adminUser.lastName}`);
});
});

describe('GET /api/admin/organizations/{organizationId}/children', function () {
context('error cases', function () {
context('when organization does not exist', function () {
Expand Down
20 changes: 0 additions & 20 deletions api/tests/integration/application/organizations/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,4 @@ describe('Integration | Application | Organizations | Routes', function () {
});
});
});

describe('POST /api/admin/organizations/:id/archive', function () {
it('should call the controller to archive the organization', async function () {
// given
const method = 'POST';
const url = '/api/admin/organizations/1/archive';

sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns(() => true);
sinon.stub(organizationController, 'archiveOrganization').callsFake((request, h) => h.response('ok').code(204));
const httpTestServer = new HttpTestServer();
await httpTestServer.register(moduleUnderTest);

// when
const response = await httpTestServer.request(method, url);

// then
expect(response.statusCode).to.equal(204);
expect(organizationController.archiveOrganization).to.have.been.calledOnce;
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,43 @@ describe('Acceptance | Organizational Entities | Application | Route | Admin | O
});
});

describe('POST /api/admin/organizations/{id}/archive', function () {
it('returns the archived organization', async function () {
// given
const organizationId = databaseBuilder.factory.buildOrganization().id;
await databaseBuilder.commit();

// when
const response = await server.inject({
method: 'POST',
url: `/api/admin/organizations/${organizationId}/archive`,
headers: generateAuthenticatedUserRequestHeaders({ userId: admin.id }),
});

// then
expect(response.statusCode).to.equal(200);
const archivedOrganization = response.result.data.attributes;
expect(archivedOrganization['archivist-full-name']).to.equal(`${admin.firstName} ${admin.lastName}`);
});

it('is forbidden for role certif', async function () {
// given
const certifUser = databaseBuilder.factory.buildUser.withRole({ role: ROLES.CERTIF });
const organizationId = databaseBuilder.factory.buildOrganization().id;
await databaseBuilder.commit();

// when
const response = await server.inject({
method: 'POST',
url: `/api/admin/organizations/${organizationId}/archive`,
headers: generateAuthenticatedUserRequestHeaders({ userId: certifUser.id }),
});

// then
expect(response.statusCode).to.equal(403);
});
});

describe('POST /api/admin/organizations/add-organization-features', function () {
context('When a CSV file is loaded', function () {
let feature, firstOrganization, otherOrganization;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { usecases } from '../../../../../src/organizational-entities/domain/usecases/index.js';
import { databaseBuilder, expect, sinon } from '../../../../test-helper.js';

describe('Integration | Organizational Entities | Domain | UseCase | archive-organization', function () {
it('archives the organization', async function () {
// given
const now = new Date('2022-02-22');
const clock = sinon.useFakeTimers({ now, toFake: ['Date'] });
const superAdminUser = databaseBuilder.factory.buildUser();
const organization = databaseBuilder.factory.buildOrganization();
await databaseBuilder.commit();

// when
const archivedOrganization = await usecases.archiveOrganization({
organizationId: organization.id,
userId: superAdminUser.id,
});

// then
expect(archivedOrganization.archivedAt).to.deep.equal(now);
expect(archivedOrganization.archivistFirstName).to.deep.equal(superAdminUser.firstName);
expect(archivedOrganization.archivistLastName).to.deep.equal(superAdminUser.lastName);

clock.restore();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { organizationAdminController } from '../../../../../src/organizational-entities/application/organization/organization.admin.controller.js';
import { usecases } from '../../../../../src/organizational-entities/domain/usecases/index.js';
import { DomainTransaction } from '../../../../../src/shared/domain/DomainTransaction.js';
import { domainBuilder, expect, hFake, sinon } from '../../../../test-helper.js';
import {
domainBuilder,
expect,
generateAuthenticatedUserRequestHeaders,
hFake,
sinon,
} from '../../../../test-helper.js';

describe('Unit | Organizational Entities | Application | Controller | Admin | organization', function () {
describe('#addOrganizationFeatureInBatch', function () {
Expand Down Expand Up @@ -31,6 +37,40 @@ describe('Unit | Organizational Entities | Application | Controller | Admin | or
});
});

describe('#archiveOrganization', function () {
it('calls the usecase to archive the organization with the user id', async function () {
// given
const organizationId = 1234;
const userId = 10;
const request = {
headers: generateAuthenticatedUserRequestHeaders({ userId }),
params: { id: organizationId },
};

const archivedOrganization = Symbol('archivedOrganization');
const archivedOrganizationSerialized = Symbol('archivedOrganizationSerialized');
sinon.stub(usecases, 'archiveOrganization').resolves(archivedOrganization);
const organizationForAdminSerializerStub = {
serialize: sinon.stub(),
};

organizationForAdminSerializerStub.serialize
.withArgs(archivedOrganization)
.returns(archivedOrganizationSerialized);

const dependencies = {
organizationForAdminSerializer: organizationForAdminSerializerStub,
};

// when
const response = await organizationAdminController.archiveOrganization(request, hFake, dependencies);

// then
expect(usecases.archiveOrganization).to.have.been.calledOnceWithExactly({ organizationId, userId });
expect(response).to.deep.equal(archivedOrganizationSerialized);
});
});

describe('#getOrganizationDetails', function () {
it('should call the usecase and serialize the response', async function () {
// given
Expand Down
28 changes: 0 additions & 28 deletions api/tests/unit/application/organizations/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,32 +109,4 @@ describe('Unit | Router | organization-router', function () {
expect(response.statusCode).to.equal(403);
});
});

describe('POST /api/admin/organizations/{id}/archive', function () {
it('returns forbidden access if admin member has CERTIF role', async function () {
// given
sinon.stub(organizationController, 'archiveOrganization').resolves('ok');

sinon.stub(securityPreHandlers, 'checkAdminMemberHasRoleCertif').callsFake((request, h) => h.response(true));
sinon
.stub(securityPreHandlers, 'checkAdminMemberHasRoleSuperAdmin')
.callsFake((request, h) => h.response({ errors: new Error('forbidden') }).code(403));
sinon
.stub(securityPreHandlers, 'checkAdminMemberHasRoleSupport')
.callsFake((request, h) => h.response({ errors: new Error('forbidden') }).code(403));
sinon
.stub(securityPreHandlers, 'checkAdminMemberHasRoleMetier')
.callsFake((request, h) => h.response({ errors: new Error('forbidden') }).code(403));

const httpTestServer = new HttpTestServer();
await httpTestServer.register(moduleUnderTest);

// when
const response = await httpTestServer.request('POST', '/api/admin/organizations/1/archive');

// then
expect(response.statusCode).to.equal(403);
sinon.assert.notCalled(organizationController.archiveOrganization);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { organizationController } from '../../../../lib/application/organizations/organization-controller.js';
import { usecases } from '../../../../lib/domain/usecases/index.js';
import { Organization } from '../../../../src/shared/domain/models/index.js';
import { expect, generateAuthenticatedUserRequestHeaders, hFake, sinon } from '../../../test-helper.js';
import { expect, hFake, sinon } from '../../../test-helper.js';

describe('Unit | Application | Organizations | organization-controller', function () {
describe('#findPaginatedFilteredOrganizations', function () {
Expand Down Expand Up @@ -126,40 +126,6 @@ describe('Unit | Application | Organizations | organization-controller', functio
});
});

describe('#archiveOrganization', function () {
it('should call the usecase to archive the organization with the user id', async function () {
// given
const organizationId = 1234;
const userId = 10;
const request = {
headers: generateAuthenticatedUserRequestHeaders({ userId }),
params: { id: organizationId },
};

const archivedOrganization = Symbol('archivedOrganization');
const archivedOrganizationSerialized = Symbol('archivedOrganizationSerialized');
sinon.stub(usecases, 'archiveOrganization').resolves(archivedOrganization);
const organizationForAdminSerializerStub = {
serialize: sinon.stub(),
};

organizationForAdminSerializerStub.serialize
.withArgs(archivedOrganization)
.returns(archivedOrganizationSerialized);

const dependencies = {
organizationForAdminSerializer: organizationForAdminSerializerStub,
};

// when
const response = await organizationController.archiveOrganization(request, hFake, dependencies);

// then
expect(usecases.archiveOrganization).to.have.been.calledOnceWithExactly({ organizationId, userId });
expect(response).to.deep.equal(archivedOrganizationSerialized);
});
});

describe('#findChildrenOrganizationsForAdmin', function () {
it('calls findChildrenOrganizationsForAdmin usecase and returns a serialized list of organizations', async function () {
// given
Expand Down
Loading

0 comments on commit 1861e01

Please sign in to comment.