Skip to content

Commit 1861e01

Browse files
[TECH] Migrer la route /api/admin/organizations/{id}/archive (PIX-16755)
#11526
2 parents c93bb13 + 5cc5586 commit 1861e01

File tree

12 files changed

+144
-201
lines changed

12 files changed

+144
-201
lines changed

api/lib/application/organizations/index.js

-28
Original file line numberDiff line numberDiff line change
@@ -116,34 +116,6 @@ const register = async function (server) {
116116
],
117117
},
118118
},
119-
{
120-
method: 'POST',
121-
path: '/api/admin/organizations/{id}/archive',
122-
config: {
123-
pre: [
124-
{
125-
method: (request, h) =>
126-
securityPreHandlers.hasAtLeastOneAccessOf([
127-
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
128-
securityPreHandlers.checkAdminMemberHasRoleSupport,
129-
securityPreHandlers.checkAdminMemberHasRoleMetier,
130-
])(request, h),
131-
assign: 'hasAuthorizationToAccessAdminScope',
132-
},
133-
],
134-
validate: {
135-
params: Joi.object({
136-
id: identifiersType.organizationId,
137-
}),
138-
},
139-
handler: organizationController.archiveOrganization,
140-
tags: ['api', 'organizations'],
141-
notes: [
142-
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
143-
"- Elle permet d'archiver une organisation",
144-
],
145-
},
146-
},
147119
{
148120
method: 'GET',
149121
path: '/api/admin/organizations/{organizationId}/children',

api/src/organizational-entities/application/organization/organization.admin.controller.js

+9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
2+
import { extractUserIdFromRequest } from '../../../shared/infrastructure/utils/request-response-utils.js';
23
import { usecases } from '../../domain/usecases/index.js';
34
import { organizationTagCsvParser } from '../../infrastructure/parsers/csv/organization-tag-csv.parser.js';
45
import { organizationForAdminSerializer } from '../../infrastructure/serializers/jsonapi/organizations-administration/organization-for-admin.serializer.js';
@@ -10,6 +11,13 @@ const addTagsToOrganizations = async function (request, h) {
1011
return h.response().code(204);
1112
};
1213

14+
const archiveOrganization = async function (request, h, dependencies = { organizationForAdminSerializer }) {
15+
const organizationId = request.params.id;
16+
const userId = extractUserIdFromRequest(request);
17+
const archivedOrganization = await usecases.archiveOrganization({ organizationId, userId });
18+
return dependencies.organizationForAdminSerializer.serialize(archivedOrganization);
19+
};
20+
1321
const attachChildOrganization = async function (request, h) {
1422
const { childOrganizationIds } = request.payload;
1523
const { organizationId: parentOrganizationId } = request.params;
@@ -59,6 +67,7 @@ const updateOrganizationInformation = async function (
5967

6068
const organizationAdminController = {
6169
addTagsToOrganizations,
70+
archiveOrganization,
6271
attachChildOrganization,
6372
addOrganizationFeatureInBatch,
6473
getOrganizationDetails,

api/src/organizational-entities/application/organization/organization.admin.route.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const register = async function (server) {
3535
}),
3636
},
3737
handler: (request, h) => organizationAdminController.getOrganizationDetails(request, h),
38-
tags: ['api', 'organizations'],
38+
tags: ['api', 'admin', 'organizational-entities', 'organizations'],
3939
notes: [
4040
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
4141
'- Elle permet de récupérer toutes les informations d’une organisation',
@@ -74,13 +74,41 @@ const register = async function (server) {
7474
},
7575
},
7676
handler: (request, h) => organizationAdminController.updateOrganizationInformation(request, h),
77-
tags: ['api', 'organizations'],
77+
tags: ['api', 'admin', 'organizational-entities', 'organizations'],
7878
notes: [
7979
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
8080
'- Elle permet de mettre à jour tout ou partie d’une organisation',
8181
],
8282
},
8383
},
84+
{
85+
method: 'POST',
86+
path: '/api/admin/organizations/{id}/archive',
87+
config: {
88+
pre: [
89+
{
90+
method: (request, h) =>
91+
securityPreHandlers.hasAtLeastOneAccessOf([
92+
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
93+
securityPreHandlers.checkAdminMemberHasRoleSupport,
94+
securityPreHandlers.checkAdminMemberHasRoleMetier,
95+
])(request, h),
96+
assign: 'hasAuthorizationToAccessAdminScope',
97+
},
98+
],
99+
validate: {
100+
params: Joi.object({
101+
id: identifiersType.organizationId,
102+
}),
103+
},
104+
handler: organizationAdminController.archiveOrganization,
105+
tags: ['api', 'admin', 'organizational-entities', 'organizations'],
106+
notes: [
107+
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
108+
"- Elle permet d'archiver une organisation",
109+
],
110+
},
111+
},
84112
{
85113
method: 'POST',
86114
path: '/api/admin/organizations/{organizationId}/attach-child-organization',

api/tests/acceptance/application/organizations/organization-controller_test.js

-43
Original file line numberDiff line numberDiff line change
@@ -638,49 +638,6 @@ describe('Acceptance | Application | organization-controller', function () {
638638
});
639639
});
640640

641-
describe('POST /api/admin/organizations/{id}/archive', function () {
642-
it('should return the archived organization', async function () {
643-
// given
644-
const adminUser = databaseBuilder.factory.buildUser.withRole();
645-
const organizationId = databaseBuilder.factory.buildOrganization().id;
646-
databaseBuilder.factory.buildOrganization({ id: 2 });
647-
648-
// Invitations
649-
databaseBuilder.factory.buildOrganizationInvitation({
650-
organizationId,
651-
status: OrganizationInvitation.StatusType.PENDING,
652-
});
653-
databaseBuilder.factory.buildOrganizationInvitation({
654-
organizationId,
655-
status: OrganizationInvitation.StatusType.PENDING,
656-
});
657-
658-
// Campaigns
659-
databaseBuilder.factory.buildCampaign({ id: 1, organizationId });
660-
databaseBuilder.factory.buildCampaign({ id: 2, organizationId });
661-
662-
// Memberships
663-
databaseBuilder.factory.buildUser({ id: 7 });
664-
databaseBuilder.factory.buildMembership({ id: 1, userId: 7, organizationId });
665-
databaseBuilder.factory.buildUser({ id: 8 });
666-
databaseBuilder.factory.buildMembership({ id: 2, userId: 8, organizationId });
667-
668-
await databaseBuilder.commit();
669-
670-
// when
671-
const response = await server.inject({
672-
method: 'POST',
673-
url: `/api/admin/organizations/${organizationId}/archive`,
674-
headers: generateAuthenticatedUserRequestHeaders({ userId: adminUser.id }),
675-
});
676-
677-
// then
678-
expect(response.statusCode).to.equal(200);
679-
const archivedOrganization = response.result.data.attributes;
680-
expect(archivedOrganization['archivist-full-name']).to.equal(`${adminUser.firstName} ${adminUser.lastName}`);
681-
});
682-
});
683-
684641
describe('GET /api/admin/organizations/{organizationId}/children', function () {
685642
context('error cases', function () {
686643
context('when organization does not exist', function () {

api/tests/integration/application/organizations/index_test.js

-20
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,4 @@ describe('Integration | Application | Organizations | Routes', function () {
6464
});
6565
});
6666
});
67-
68-
describe('POST /api/admin/organizations/:id/archive', function () {
69-
it('should call the controller to archive the organization', async function () {
70-
// given
71-
const method = 'POST';
72-
const url = '/api/admin/organizations/1/archive';
73-
74-
sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns(() => true);
75-
sinon.stub(organizationController, 'archiveOrganization').callsFake((request, h) => h.response('ok').code(204));
76-
const httpTestServer = new HttpTestServer();
77-
await httpTestServer.register(moduleUnderTest);
78-
79-
// when
80-
const response = await httpTestServer.request(method, url);
81-
82-
// then
83-
expect(response.statusCode).to.equal(204);
84-
expect(organizationController.archiveOrganization).to.have.been.calledOnce;
85-
});
86-
});
8767
});

api/tests/organizational-entities/acceptance/application/organization/organization.admin.route.test.js

+37
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,43 @@ describe('Acceptance | Organizational Entities | Application | Route | Admin | O
248248
});
249249
});
250250

251+
describe('POST /api/admin/organizations/{id}/archive', function () {
252+
it('returns the archived organization', async function () {
253+
// given
254+
const organizationId = databaseBuilder.factory.buildOrganization().id;
255+
await databaseBuilder.commit();
256+
257+
// when
258+
const response = await server.inject({
259+
method: 'POST',
260+
url: `/api/admin/organizations/${organizationId}/archive`,
261+
headers: generateAuthenticatedUserRequestHeaders({ userId: admin.id }),
262+
});
263+
264+
// then
265+
expect(response.statusCode).to.equal(200);
266+
const archivedOrganization = response.result.data.attributes;
267+
expect(archivedOrganization['archivist-full-name']).to.equal(`${admin.firstName} ${admin.lastName}`);
268+
});
269+
270+
it('is forbidden for role certif', async function () {
271+
// given
272+
const certifUser = databaseBuilder.factory.buildUser.withRole({ role: ROLES.CERTIF });
273+
const organizationId = databaseBuilder.factory.buildOrganization().id;
274+
await databaseBuilder.commit();
275+
276+
// when
277+
const response = await server.inject({
278+
method: 'POST',
279+
url: `/api/admin/organizations/${organizationId}/archive`,
280+
headers: generateAuthenticatedUserRequestHeaders({ userId: certifUser.id }),
281+
});
282+
283+
// then
284+
expect(response.statusCode).to.equal(403);
285+
});
286+
});
287+
251288
describe('POST /api/admin/organizations/add-organization-features', function () {
252289
context('When a CSV file is loaded', function () {
253290
let feature, firstOrganization, otherOrganization;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { usecases } from '../../../../../src/organizational-entities/domain/usecases/index.js';
2+
import { databaseBuilder, expect, sinon } from '../../../../test-helper.js';
3+
4+
describe('Integration | Organizational Entities | Domain | UseCase | archive-organization', function () {
5+
it('archives the organization', async function () {
6+
// given
7+
const now = new Date('2022-02-22');
8+
const clock = sinon.useFakeTimers({ now, toFake: ['Date'] });
9+
const superAdminUser = databaseBuilder.factory.buildUser();
10+
const organization = databaseBuilder.factory.buildOrganization();
11+
await databaseBuilder.commit();
12+
13+
// when
14+
const archivedOrganization = await usecases.archiveOrganization({
15+
organizationId: organization.id,
16+
userId: superAdminUser.id,
17+
});
18+
19+
// then
20+
expect(archivedOrganization.archivedAt).to.deep.equal(now);
21+
expect(archivedOrganization.archivistFirstName).to.deep.equal(superAdminUser.firstName);
22+
expect(archivedOrganization.archivistLastName).to.deep.equal(superAdminUser.lastName);
23+
24+
clock.restore();
25+
});
26+
});

api/tests/organizational-entities/unit/application/organization/organization.admin.controller.test.js

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { organizationAdminController } from '../../../../../src/organizational-entities/application/organization/organization.admin.controller.js';
22
import { usecases } from '../../../../../src/organizational-entities/domain/usecases/index.js';
33
import { DomainTransaction } from '../../../../../src/shared/domain/DomainTransaction.js';
4-
import { domainBuilder, expect, hFake, sinon } from '../../../../test-helper.js';
4+
import {
5+
domainBuilder,
6+
expect,
7+
generateAuthenticatedUserRequestHeaders,
8+
hFake,
9+
sinon,
10+
} from '../../../../test-helper.js';
511

612
describe('Unit | Organizational Entities | Application | Controller | Admin | organization', function () {
713
describe('#addOrganizationFeatureInBatch', function () {
@@ -31,6 +37,40 @@ describe('Unit | Organizational Entities | Application | Controller | Admin | or
3137
});
3238
});
3339

40+
describe('#archiveOrganization', function () {
41+
it('calls the usecase to archive the organization with the user id', async function () {
42+
// given
43+
const organizationId = 1234;
44+
const userId = 10;
45+
const request = {
46+
headers: generateAuthenticatedUserRequestHeaders({ userId }),
47+
params: { id: organizationId },
48+
};
49+
50+
const archivedOrganization = Symbol('archivedOrganization');
51+
const archivedOrganizationSerialized = Symbol('archivedOrganizationSerialized');
52+
sinon.stub(usecases, 'archiveOrganization').resolves(archivedOrganization);
53+
const organizationForAdminSerializerStub = {
54+
serialize: sinon.stub(),
55+
};
56+
57+
organizationForAdminSerializerStub.serialize
58+
.withArgs(archivedOrganization)
59+
.returns(archivedOrganizationSerialized);
60+
61+
const dependencies = {
62+
organizationForAdminSerializer: organizationForAdminSerializerStub,
63+
};
64+
65+
// when
66+
const response = await organizationAdminController.archiveOrganization(request, hFake, dependencies);
67+
68+
// then
69+
expect(usecases.archiveOrganization).to.have.been.calledOnceWithExactly({ organizationId, userId });
70+
expect(response).to.deep.equal(archivedOrganizationSerialized);
71+
});
72+
});
73+
3474
describe('#getOrganizationDetails', function () {
3575
it('should call the usecase and serialize the response', async function () {
3676
// given

api/tests/unit/application/organizations/index_test.js

-28
Original file line numberDiff line numberDiff line change
@@ -109,32 +109,4 @@ describe('Unit | Router | organization-router', function () {
109109
expect(response.statusCode).to.equal(403);
110110
});
111111
});
112-
113-
describe('POST /api/admin/organizations/{id}/archive', function () {
114-
it('returns forbidden access if admin member has CERTIF role', async function () {
115-
// given
116-
sinon.stub(organizationController, 'archiveOrganization').resolves('ok');
117-
118-
sinon.stub(securityPreHandlers, 'checkAdminMemberHasRoleCertif').callsFake((request, h) => h.response(true));
119-
sinon
120-
.stub(securityPreHandlers, 'checkAdminMemberHasRoleSuperAdmin')
121-
.callsFake((request, h) => h.response({ errors: new Error('forbidden') }).code(403));
122-
sinon
123-
.stub(securityPreHandlers, 'checkAdminMemberHasRoleSupport')
124-
.callsFake((request, h) => h.response({ errors: new Error('forbidden') }).code(403));
125-
sinon
126-
.stub(securityPreHandlers, 'checkAdminMemberHasRoleMetier')
127-
.callsFake((request, h) => h.response({ errors: new Error('forbidden') }).code(403));
128-
129-
const httpTestServer = new HttpTestServer();
130-
await httpTestServer.register(moduleUnderTest);
131-
132-
// when
133-
const response = await httpTestServer.request('POST', '/api/admin/organizations/1/archive');
134-
135-
// then
136-
expect(response.statusCode).to.equal(403);
137-
sinon.assert.notCalled(organizationController.archiveOrganization);
138-
});
139-
});
140112
});

api/tests/unit/application/organizations/organization-controller_test.js

+1-35
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { organizationController } from '../../../../lib/application/organizations/organization-controller.js';
22
import { usecases } from '../../../../lib/domain/usecases/index.js';
33
import { Organization } from '../../../../src/shared/domain/models/index.js';
4-
import { expect, generateAuthenticatedUserRequestHeaders, hFake, sinon } from '../../../test-helper.js';
4+
import { expect, hFake, sinon } from '../../../test-helper.js';
55

66
describe('Unit | Application | Organizations | organization-controller', function () {
77
describe('#findPaginatedFilteredOrganizations', function () {
@@ -126,40 +126,6 @@ describe('Unit | Application | Organizations | organization-controller', functio
126126
});
127127
});
128128

129-
describe('#archiveOrganization', function () {
130-
it('should call the usecase to archive the organization with the user id', async function () {
131-
// given
132-
const organizationId = 1234;
133-
const userId = 10;
134-
const request = {
135-
headers: generateAuthenticatedUserRequestHeaders({ userId }),
136-
params: { id: organizationId },
137-
};
138-
139-
const archivedOrganization = Symbol('archivedOrganization');
140-
const archivedOrganizationSerialized = Symbol('archivedOrganizationSerialized');
141-
sinon.stub(usecases, 'archiveOrganization').resolves(archivedOrganization);
142-
const organizationForAdminSerializerStub = {
143-
serialize: sinon.stub(),
144-
};
145-
146-
organizationForAdminSerializerStub.serialize
147-
.withArgs(archivedOrganization)
148-
.returns(archivedOrganizationSerialized);
149-
150-
const dependencies = {
151-
organizationForAdminSerializer: organizationForAdminSerializerStub,
152-
};
153-
154-
// when
155-
const response = await organizationController.archiveOrganization(request, hFake, dependencies);
156-
157-
// then
158-
expect(usecases.archiveOrganization).to.have.been.calledOnceWithExactly({ organizationId, userId });
159-
expect(response).to.deep.equal(archivedOrganizationSerialized);
160-
});
161-
});
162-
163129
describe('#findChildrenOrganizationsForAdmin', function () {
164130
it('calls findChildrenOrganizationsForAdmin usecase and returns a serialized list of organizations', async function () {
165131
// given

0 commit comments

Comments
 (0)