diff --git a/api/lib/application/target-profiles/index.js b/api/lib/application/target-profiles/index.js index 7437c1500fb..4df624f26f6 100644 --- a/api/lib/application/target-profiles/index.js +++ b/api/lib/application/target-profiles/index.js @@ -161,61 +161,6 @@ const register = async function (server) { ], }, }, - { - method: 'POST', - path: '/api/admin/target-profiles/{id}/badges', - config: { - pre: [ - { - method: (request, h) => - securityPreHandlers.hasAtLeastOneAccessOf([ - securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, - securityPreHandlers.checkAdminMemberHasRoleSupport, - securityPreHandlers.checkAdminMemberHasRoleMetier, - ])(request, h), - assign: 'hasAuthorizationToAccessAdminScope', - }, - ], - validate: { - params: Joi.object({ - id: identifiersType.targetProfileId, - }), - payload: Joi.object({ - data: Joi.object({ - attributes: Joi.object({ - key: Joi.string().required(), - 'alt-message': Joi.string().required(), - 'image-url': Joi.string().required(), - message: Joi.string().required().allow('').allow(null), - title: Joi.string().required().allow('').allow(null), - 'is-certifiable': Joi.boolean().required(), - 'is-always-visible': Joi.boolean().required(), - 'campaign-threshold': Joi.number().min(0).max(100).allow(null), - 'capped-tubes-criteria': Joi.array().items({ - name: Joi.string(), - threshold: Joi.string().required(), - cappedTubes: Joi.array() - .min(1) - .items({ - id: Joi.string(), - level: Joi.number().min(0), - }), - }), - }) - .or('campaign-threshold', 'capped-tubes-criteria') - .required(), - type: Joi.string().required(), - }).required(), - }).required(), - }, - handler: targetProfileController.createBadge, - tags: ['api', 'admin', 'badges'], - notes: [ - "- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" + - '- Elle permet de créer un résultat thématique rattaché au profil cible.', - ], - }, - }, { method: 'PATCH', path: '/api/admin/target-profiles/{id}', diff --git a/api/lib/application/target-profiles/target-profile-controller.js b/api/lib/application/target-profiles/target-profile-controller.js index 55c9380d44e..e3cf09b93f6 100644 --- a/api/lib/application/target-profiles/target-profile-controller.js +++ b/api/lib/application/target-profiles/target-profile-controller.js @@ -1,8 +1,5 @@ import { usecases as devcompUsecases } from '../../../src/devcomp/domain/usecases/index.js'; import * as trainingSummarySerializer from '../../../src/devcomp/infrastructure/serializers/jsonapi/training-summary-serializer.js'; -import { evaluationUsecases } from '../../../src/evaluation/domain/usecases/index.js'; -import { deserializer as badgeCreationDeserializer } from '../../../src/evaluation/infrastructure/serializers/jsonapi/badge-creation-serializer.js'; -import * as badgeSerializer from '../../../src/evaluation/infrastructure/serializers/jsonapi/badge-serializer.js'; import * as targetProfileSerializer from '../../../src/prescription/target-profile/infrastructure/serializers/jsonapi/target-profile-serializer.js'; import * as targetProfileSummaryForAdminSerializer from '../../../src/prescription/target-profile/infrastructure/serializers/jsonapi/target-profile-summary-for-admin-serializer.js'; import { usecases } from '../../domain/usecases/index.js'; @@ -63,22 +60,12 @@ const findPaginatedTrainings = async function (request, h, dependencies = { trai return dependencies.trainingSummarySerializer.serialize(trainings, meta); }; -const createBadge = async function (request, h) { - const targetProfileId = request.params.id; - const badgeCreation = await badgeCreationDeserializer.deserialize(request.payload); - - const createdBadge = await evaluationUsecases.createBadge({ targetProfileId, badgeCreation }); - - return h.response(badgeSerializer.serialize(createdBadge)).created(); -}; - const targetProfileController = { findPaginatedFilteredTargetProfileSummariesForAdmin, getTargetProfileForAdmin, updateTargetProfile, createTargetProfile, findPaginatedTrainings, - createBadge, }; export { targetProfileController }; diff --git a/api/src/evaluation/application/badges/badges-controller.js b/api/src/evaluation/application/badges/badges-controller.js index b108627d8a0..ffce01cdf48 100644 --- a/api/src/evaluation/application/badges/badges-controller.js +++ b/api/src/evaluation/application/badges/badges-controller.js @@ -1,12 +1,13 @@ -import { evaluationUsecases as usecases } from '../../../evaluation/domain/usecases/index.js'; +import { evaluationUsecases } from '../../../evaluation/domain/usecases/index.js'; import { sharedUsecases } from '../../../shared/domain/usecases/index.js'; +import { deserializer as badgeCreationDeserializer } from '../../infrastructure/serializers/jsonapi/badge-creation-serializer.js'; import * as badgeSerializer from '../../infrastructure/serializers/jsonapi/badge-serializer.js'; const updateBadge = async function (request, h) { const badgeId = request.params.id; const badge = badgeSerializer.deserialize(request.payload); - const updatedBadge = await usecases.updateBadge({ badgeId, badge }); + const updatedBadge = await evaluationUsecases.updateBadge({ badgeId, badge }); return h.response(badgeSerializer.serialize(updatedBadge)).code(204); }; @@ -18,6 +19,15 @@ const deleteUnassociatedBadge = async function (request, h) { return h.response().code(204); }; -const badgesController = { updateBadge, deleteUnassociatedBadge }; +const createBadge = async function (request, h) { + const targetProfileId = request.params.id; + const badgeCreation = await badgeCreationDeserializer.deserialize(request.payload); + + const createdBadge = await evaluationUsecases.createBadge({ targetProfileId, badgeCreation }); + + return h.response(badgeSerializer.serialize(createdBadge)).created(); +}; + +const badgesController = { updateBadge, deleteUnassociatedBadge, createBadge }; export { badgesController }; diff --git a/api/src/evaluation/application/badges/index.js b/api/src/evaluation/application/badges/index.js index d75e4aebabf..3e683453279 100644 --- a/api/src/evaluation/application/badges/index.js +++ b/api/src/evaluation/application/badges/index.js @@ -77,6 +77,61 @@ const register = async function (server) { ], }, }, + { + method: 'POST', + path: '/api/admin/target-profiles/{id}/badges', + config: { + pre: [ + { + method: (request, h) => + securityPreHandlers.hasAtLeastOneAccessOf([ + securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, + securityPreHandlers.checkAdminMemberHasRoleSupport, + securityPreHandlers.checkAdminMemberHasRoleMetier, + ])(request, h), + assign: 'hasAuthorizationToAccessAdminScope', + }, + ], + validate: { + params: Joi.object({ + id: identifiersType.targetProfileId, + }), + payload: Joi.object({ + data: Joi.object({ + attributes: Joi.object({ + key: Joi.string().required(), + 'alt-message': Joi.string().required(), + 'image-url': Joi.string().required(), + message: Joi.string().required().allow('').allow(null), + title: Joi.string().required().allow('').allow(null), + 'is-certifiable': Joi.boolean().required(), + 'is-always-visible': Joi.boolean().required(), + 'campaign-threshold': Joi.number().min(0).max(100).allow(null), + 'capped-tubes-criteria': Joi.array().items({ + name: Joi.string(), + threshold: Joi.string().required(), + cappedTubes: Joi.array() + .min(1) + .items({ + id: Joi.string(), + level: Joi.number().min(0), + }), + }), + }) + .or('campaign-threshold', 'capped-tubes-criteria') + .required(), + type: Joi.string().required(), + }).required(), + }).required(), + }, + handler: badgesController.createBadge, + tags: ['api', 'admin', 'badges'], + notes: [ + "- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" + + '- Elle permet de créer un résultat thématique rattaché au profil cible.', + ], + }, + }, ]); }; diff --git a/api/tests/acceptance/application/target-profiles/index_test.js b/api/tests/acceptance/application/target-profiles/index_test.js index 08d83589802..d6e7649af7a 100644 --- a/api/tests/acceptance/application/target-profiles/index_test.js +++ b/api/tests/acceptance/application/target-profiles/index_test.js @@ -1,5 +1,3 @@ -import lodash from 'lodash'; - import { createServer, databaseBuilder, @@ -9,8 +7,6 @@ import { mockLearningContent, } from '../../../test-helper.js'; -const { omit } = lodash; - describe('Acceptance | Route | target-profiles', function () { let server; @@ -196,213 +192,6 @@ describe('Acceptance | Route | target-profiles', function () { }); }); - describe('POST /api/admin/target-profiles/{id}/badges', function () { - context('Badge with capped tubes', function () { - let user, targetProfileId; - beforeEach(async function () { - user = databaseBuilder.factory.buildUser.withRole(); - targetProfileId = databaseBuilder.factory.buildTargetProfile().id; - databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'tubeId1', level: 3 }); - databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'tubeId2', level: 2 }); - databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'tubeId3', level: 4 }); - await databaseBuilder.commit(); - }); - - it('should create a badge with capped tubes criterion', async function () { - const badgeCreation = { - key: 'badge1', - 'alt-message': 'alt-message', - 'image-url': 'https//images.example.net', - message: 'Bravo !', - title: 'Le super badge', - 'is-certifiable': false, - 'is-always-visible': true, - 'campaign-threshold': '99', - 'capped-tubes-criteria': [ - { - name: 'awesome tube group', - threshold: '50', - cappedTubes: [ - { - id: 'tubeId1', - level: 2, - }, - { - id: 'tubeId2', - level: 2, - }, - ], - }, - ], - }; - const options = { - method: 'POST', - url: `/api/admin/target-profiles/${targetProfileId}/badges/`, - headers: { authorization: generateValidRequestAuthorizationHeader(user.id) }, - payload: { - data: { - type: 'badge-creations', - attributes: badgeCreation, - }, - }, - }; - - // when - const response = await server.inject(options); - const cappedTubesCriterion = await knex('badge-criteria').select().where('scope', 'CappedTubes').first(); - const campaignCriterion = await knex('badge-criteria').select().where('scope', 'CampaignParticipation').first(); - - // then - const expectedResult = { - data: { - attributes: { - 'alt-message': 'alt-message', - 'image-url': 'https//images.example.net', - 'is-certifiable': false, - 'is-always-visible': true, - key: 'badge1', - message: 'Bravo !', - title: 'Le super badge', - }, - type: 'badges', - }, - }; - - const expectedCappedTubesCriterion = { - name: 'awesome tube group', - scope: 'CappedTubes', - threshold: 50, - badgeId: parseInt(response.result.data.id, 10), - cappedTubes: [ - { id: 'tubeId1', level: 2 }, - { id: 'tubeId2', level: 2 }, - ], - }; - - const expectedCampaignCriterion = { - name: null, - scope: 'CampaignParticipation', - threshold: 99, - badgeId: parseInt(response.result.data.id, 10), - cappedTubes: null, - }; - expect(response.statusCode).to.equal(201); - expect(omit(response.result, 'data.id')).to.deep.equal(expectedResult); - expect(omit(cappedTubesCriterion, 'id')).to.deep.equal(expectedCappedTubesCriterion); - expect(omit(campaignCriterion, 'id')).to.deep.equal(expectedCampaignCriterion); - }); - - it('should not create a badge if capped tubes criterion tube id is not into target profile', async function () { - const badgeCreation = { - key: 'badge1', - 'alt-message': 'alt-message', - 'image-url': 'https//images.example.net', - message: 'Bravo !', - title: 'Le super badge', - 'is-certifiable': false, - 'is-always-visible': true, - 'campaign-threshold': '99', - 'capped-tubes-criteria': [ - { - threshold: '50', - cappedTubes: [ - { - id: 'tubeId1', - level: 2, - }, - { - id: 'wrongId', - level: 2, - }, - ], - }, - ], - }; - const options = { - method: 'POST', - url: `/api/admin/target-profiles/${targetProfileId}/badges/`, - headers: { authorization: generateValidRequestAuthorizationHeader(user.id) }, - payload: { - data: { - type: 'badge-creations', - attributes: badgeCreation, - }, - }, - }; - - // when - const response = await server.inject(options); - - // then - const expectedError = { - errors: [ - { - detail: 'Le sujet wrongId ne fait pas partie du profil cible', - status: '422', - title: 'Unprocessable entity', - }, - ], - }; - expect(response.statusCode).to.equal(422); - expect(response.result).to.deep.equal(expectedError); - }); - it('should not create a badge if capped tubes criterion level is not into target profile', async function () { - const badgeCreation = { - key: 'badge1', - 'alt-message': 'alt-message', - 'image-url': 'https//images.example.net', - message: 'Bravo !', - title: 'Le super badge', - 'is-certifiable': false, - 'is-always-visible': true, - 'campaign-threshold': '99', - 'capped-tubes-criteria': [ - { - threshold: '50', - cappedTubes: [ - { - id: 'tubeId1', - level: 8, - }, - { - id: 'wrongId', - level: 2, - }, - ], - }, - ], - }; - const options = { - method: 'POST', - url: `/api/admin/target-profiles/${targetProfileId}/badges/`, - headers: { authorization: generateValidRequestAuthorizationHeader(user.id) }, - payload: { - data: { - type: 'badge-creations', - attributes: badgeCreation, - }, - }, - }; - - // when - const response = await server.inject(options); - - // then - const expectedError = { - errors: [ - { - detail: 'Le niveau 8 dépasse le niveau max du sujet tubeId1', - status: '422', - title: 'Unprocessable entity', - }, - ], - }; - expect(response.statusCode).to.equal(422); - expect(response.result).to.deep.equal(expectedError); - }); - }); - }); - describe('GET /api/admin/target-profiles/{id}', function () { let user; diff --git a/api/tests/evaluation/acceptance/application/badges/index_test.js b/api/tests/evaluation/acceptance/application/badges/index_test.js new file mode 100644 index 00000000000..10dcb8bfe9a --- /dev/null +++ b/api/tests/evaluation/acceptance/application/badges/index_test.js @@ -0,0 +1,226 @@ +import lodash from 'lodash'; + +import { + createServer, + databaseBuilder, + expect, + generateValidRequestAuthorizationHeader, + knex, +} from '../../../../test-helper.js'; + +const { omit } = lodash; + +describe('Acceptance | Route | target-profiles', function () { + let server; + + beforeEach(async function () { + server = await createServer(); + }); + + describe('POST /api/admin/target-profiles/{id}/badges', function () { + context('Badge with capped tubes', function () { + let user, targetProfileId; + beforeEach(async function () { + user = databaseBuilder.factory.buildUser.withRole(); + targetProfileId = databaseBuilder.factory.buildTargetProfile().id; + databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'tubeId1', level: 3 }); + databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'tubeId2', level: 2 }); + databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'tubeId3', level: 4 }); + await databaseBuilder.commit(); + }); + + it('should create a badge with capped tubes criterion', async function () { + const badgeCreation = { + key: 'badge1', + 'alt-message': 'alt-message', + 'image-url': 'https//images.example.net', + message: 'Bravo !', + title: 'Le super badge', + 'is-certifiable': false, + 'is-always-visible': true, + 'campaign-threshold': '99', + 'capped-tubes-criteria': [ + { + name: 'awesome tube group', + threshold: '50', + cappedTubes: [ + { + id: 'tubeId1', + level: 2, + }, + { + id: 'tubeId2', + level: 2, + }, + ], + }, + ], + }; + const options = { + method: 'POST', + url: `/api/admin/target-profiles/${targetProfileId}/badges/`, + headers: { authorization: generateValidRequestAuthorizationHeader(user.id) }, + payload: { + data: { + type: 'badge-creations', + attributes: badgeCreation, + }, + }, + }; + + // when + const response = await server.inject(options); + const cappedTubesCriterion = await knex('badge-criteria').select().where('scope', 'CappedTubes').first(); + const campaignCriterion = await knex('badge-criteria').select().where('scope', 'CampaignParticipation').first(); + + // then + const expectedResult = { + data: { + attributes: { + 'alt-message': 'alt-message', + 'image-url': 'https//images.example.net', + 'is-certifiable': false, + 'is-always-visible': true, + key: 'badge1', + message: 'Bravo !', + title: 'Le super badge', + }, + type: 'badges', + }, + }; + + const expectedCappedTubesCriterion = { + name: 'awesome tube group', + scope: 'CappedTubes', + threshold: 50, + badgeId: parseInt(response.result.data.id, 10), + cappedTubes: [ + { id: 'tubeId1', level: 2 }, + { id: 'tubeId2', level: 2 }, + ], + }; + + const expectedCampaignCriterion = { + name: null, + scope: 'CampaignParticipation', + threshold: 99, + badgeId: parseInt(response.result.data.id, 10), + cappedTubes: null, + }; + expect(response.statusCode).to.equal(201); + expect(omit(response.result, 'data.id')).to.deep.equal(expectedResult); + expect(omit(cappedTubesCriterion, 'id')).to.deep.equal(expectedCappedTubesCriterion); + expect(omit(campaignCriterion, 'id')).to.deep.equal(expectedCampaignCriterion); + }); + + it('should not create a badge if capped tubes criterion tube id is not into target profile', async function () { + const badgeCreation = { + key: 'badge1', + 'alt-message': 'alt-message', + 'image-url': 'https//images.example.net', + message: 'Bravo !', + title: 'Le super badge', + 'is-certifiable': false, + 'is-always-visible': true, + 'campaign-threshold': '99', + 'capped-tubes-criteria': [ + { + threshold: '50', + cappedTubes: [ + { + id: 'tubeId1', + level: 2, + }, + { + id: 'wrongId', + level: 2, + }, + ], + }, + ], + }; + const options = { + method: 'POST', + url: `/api/admin/target-profiles/${targetProfileId}/badges/`, + headers: { authorization: generateValidRequestAuthorizationHeader(user.id) }, + payload: { + data: { + type: 'badge-creations', + attributes: badgeCreation, + }, + }, + }; + + // when + const response = await server.inject(options); + + // then + const expectedError = { + errors: [ + { + detail: 'Le sujet wrongId ne fait pas partie du profil cible', + status: '422', + title: 'Unprocessable entity', + }, + ], + }; + expect(response.statusCode).to.equal(422); + expect(response.result).to.deep.equal(expectedError); + }); + it('should not create a badge if capped tubes criterion level is not into target profile', async function () { + const badgeCreation = { + key: 'badge1', + 'alt-message': 'alt-message', + 'image-url': 'https//images.example.net', + message: 'Bravo !', + title: 'Le super badge', + 'is-certifiable': false, + 'is-always-visible': true, + 'campaign-threshold': '99', + 'capped-tubes-criteria': [ + { + threshold: '50', + cappedTubes: [ + { + id: 'tubeId1', + level: 8, + }, + { + id: 'wrongId', + level: 2, + }, + ], + }, + ], + }; + const options = { + method: 'POST', + url: `/api/admin/target-profiles/${targetProfileId}/badges/`, + headers: { authorization: generateValidRequestAuthorizationHeader(user.id) }, + payload: { + data: { + type: 'badge-creations', + attributes: badgeCreation, + }, + }, + }; + + // when + const response = await server.inject(options); + + // then + const expectedError = { + errors: [ + { + detail: 'Le niveau 8 dépasse le niveau max du sujet tubeId1', + status: '422', + title: 'Unprocessable entity', + }, + ], + }; + expect(response.statusCode).to.equal(422); + expect(response.result).to.deep.equal(expectedError); + }); + }); + }); +}); diff --git a/api/tests/evaluation/unit/application/badges/index_test.js b/api/tests/evaluation/unit/application/badges/index_test.js index b4615a866af..e794cbd3b66 100644 --- a/api/tests/evaluation/unit/application/badges/index_test.js +++ b/api/tests/evaluation/unit/application/badges/index_test.js @@ -1,9 +1,178 @@ +import _ from 'lodash'; + import { badgesController } from '../../../../../src/evaluation/application/badges/badges-controller.js'; import * as badgesRouter from '../../../../../src/evaluation/application/badges/index.js'; import { securityPreHandlers } from '../../../../../src/shared/application/security-pre-handlers.js'; import { expect, HttpTestServer, sinon } from '../../../../test-helper.js'; describe('Unit | Application | Badges | Routes', function () { + describe('POST /api/admin/target-profiles/{id}/badges', function () { + const method = 'POST'; + const url = '/api/admin/target-profiles/123/badges'; + const payload = { + data: { + type: 'badges', + attributes: { + key: 'KEY', + 'alt-message': 'alt-message', + 'image-url': 'https://example.net/image.svg', + message: 'message', + title: 'title', + 'is-certifiable': false, + 'is-always-visible': true, + 'campaign-threshold': 20, + 'capped-tubes-criteria': [ + { + name: 'dummy name', + threshold: '30', + cappedTubes: [ + { + id: '1', + level: 2, + }, + ], + }, + ], + }, + }, + }; + + context('when user has role "SUPER_ADMIN", "SUPPORT" or "METIER"', function () { + beforeEach(function () { + sinon + .stub(securityPreHandlers, 'hasAtLeastOneAccessOf') + .withArgs([ + securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, + securityPreHandlers.checkAdminMemberHasRoleSupport, + securityPreHandlers.checkAdminMemberHasRoleMetier, + ]) + .callsFake(() => (request, h) => h.response(true)); + sinon.stub(badgesController, 'createBadge').callsFake((request, h) => h.response('ok').code(201)); + }); + + it('should return a response with an HTTP status code 201', async function () { + // given + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(badgesRouter); + + // when + const { statusCode } = await httpTestServer.request(method, url, payload); + + // then + expect(statusCode).to.equal(201); + }); + + context('when request payload is empty', function () { + it('should return a 400 HTTP response', async function () { + // given + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(badgesRouter); + + // when + const { statusCode } = await httpTestServer.request(method, url, { data: { attributes: {} } }); + + // then + expect(statusCode).to.equal(400); + }); + }); + + context('when request payload has no campaign-threshold and no capped-tubes-criteria attributes', function () { + it('should return a 400 HTTP response', async function () { + // given + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(badgesRouter); + + // when + const wrongPayload = _.omit(payload.data.attributes, 'campaign-threshold'); + const { statusCode } = await httpTestServer.request(method, url, { data: { attributes: wrongPayload } }); + + // then + expect(statusCode).to.equal(400); + }); + }); + + context('when capped-tubes-criteria has no threshold', function () { + it('should return a 400 HTTP response', async function () { + // given + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(badgesRouter); + + // when + const payloadCopy = _.cloneDeep(payload); + payloadCopy.data.attributes['capped-tubes-criteria'] = [ + { + cappedTubes: [ + { + id: '1', + level: 2, + }, + ], + }, + ]; + const response = await httpTestServer.request(method, url, payloadCopy); + + // then + expect(response.statusCode).to.equal(400); + expect(response.result.errors[0].detail).to.equal( + '"data.attributes.capped-tubes-criteria[0].threshold" is required', + ); + }); + }); + + context('when capped-tubes-criteria has no capped tubes', function () { + it('should return a 400 HTTP response', async function () { + // given + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(badgesRouter); + + // when + const payloadCopy = _.cloneDeep(payload); + payloadCopy.data.attributes['capped-tubes-criteria'] = [ + { + threshold: '20', + cappedTubes: [], + }, + ]; + const response = await httpTestServer.request(method, url, payloadCopy); + + // then + expect(response.statusCode).to.equal(400); + expect(response.result.errors[0].detail).to.equal( + '"data.attributes.capped-tubes-criteria[0].cappedTubes" must contain at least 1 items', + ); + }); + }); + }); + + context('when user has role "CERTIF"', function () { + it('should return a response with an HTTP status code 403', async function () { + // given + sinon + .stub(securityPreHandlers, 'hasAtLeastOneAccessOf') + .withArgs([ + securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, + securityPreHandlers.checkAdminMemberHasRoleSupport, + securityPreHandlers.checkAdminMemberHasRoleMetier, + ]) + .callsFake( + () => (request, h) => + h + .response({ errors: new Error('forbidden') }) + .code(403) + .takeover(), + ); + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(badgesRouter); + + // when + const { statusCode } = await httpTestServer.request(method, url, payload); + + // then + expect(statusCode).to.equal(403); + }); + }); + }); + describe('PATCH /api/admin/badges/{id}', function () { const validPayload = {}; diff --git a/api/tests/unit/application/target-profiles/index_test.js b/api/tests/unit/application/target-profiles/index_test.js index 0b00dbafac7..7c6e2dbe0c5 100644 --- a/api/tests/unit/application/target-profiles/index_test.js +++ b/api/tests/unit/application/target-profiles/index_test.js @@ -1,5 +1,3 @@ -import _ from 'lodash'; - import * as moduleUnderTest from '../../../../lib/application/target-profiles/index.js'; import { targetProfileController } from '../../../../lib/application/target-profiles/target-profile-controller.js'; import { securityPreHandlers } from '../../../../src/shared/application/security-pre-handlers.js'; @@ -217,173 +215,6 @@ describe('Unit | Application | Target Profiles | Routes', function () { }); }); - describe('POST /api/admin/target-profiles/{id}/badges', function () { - const method = 'POST'; - const url = '/api/admin/target-profiles/123/badges'; - const payload = { - data: { - type: 'badges', - attributes: { - key: 'KEY', - 'alt-message': 'alt-message', - 'image-url': 'https://example.net/image.svg', - message: 'message', - title: 'title', - 'is-certifiable': false, - 'is-always-visible': true, - 'campaign-threshold': 20, - 'capped-tubes-criteria': [ - { - name: 'dummy name', - threshold: '30', - cappedTubes: [ - { - id: '1', - level: 2, - }, - ], - }, - ], - }, - }, - }; - - context('when user has role "SUPER_ADMIN", "SUPPORT" or "METIER"', function () { - beforeEach(function () { - sinon - .stub(securityPreHandlers, 'hasAtLeastOneAccessOf') - .withArgs([ - securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, - securityPreHandlers.checkAdminMemberHasRoleSupport, - securityPreHandlers.checkAdminMemberHasRoleMetier, - ]) - .callsFake(() => (request, h) => h.response(true)); - sinon.stub(targetProfileController, 'createBadge').callsFake((request, h) => h.response('ok').code(201)); - }); - - it('should return a response with an HTTP status code 201', async function () { - // given - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const { statusCode } = await httpTestServer.request(method, url, payload); - - // then - expect(statusCode).to.equal(201); - }); - - context('when request payload is empty', function () { - it('should return a 400 HTTP response', async function () { - // given - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const { statusCode } = await httpTestServer.request(method, url, { data: { attributes: {} } }); - - // then - expect(statusCode).to.equal(400); - }); - }); - - context('when request payload has no campaign-threshold and no capped-tubes-criteria attributes', function () { - it('should return a 400 HTTP response', async function () { - // given - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const wrongPayload = _.omit(payload.data.attributes, 'campaign-threshold'); - const { statusCode } = await httpTestServer.request(method, url, { data: { attributes: wrongPayload } }); - - // then - expect(statusCode).to.equal(400); - }); - }); - - context('when capped-tubes-criteria has no threshold', function () { - it('should return a 400 HTTP response', async function () { - // given - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const payloadCopy = _.cloneDeep(payload); - payloadCopy.data.attributes['capped-tubes-criteria'] = [ - { - cappedTubes: [ - { - id: '1', - level: 2, - }, - ], - }, - ]; - const response = await httpTestServer.request(method, url, payloadCopy); - - // then - expect(response.statusCode).to.equal(400); - expect(response.result.errors[0].detail).to.equal( - '"data.attributes.capped-tubes-criteria[0].threshold" is required', - ); - }); - }); - - context('when capped-tubes-criteria has no capped tubes', function () { - it('should return a 400 HTTP response', async function () { - // given - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const payloadCopy = _.cloneDeep(payload); - payloadCopy.data.attributes['capped-tubes-criteria'] = [ - { - threshold: '20', - cappedTubes: [], - }, - ]; - const response = await httpTestServer.request(method, url, payloadCopy); - - // then - expect(response.statusCode).to.equal(400); - expect(response.result.errors[0].detail).to.equal( - '"data.attributes.capped-tubes-criteria[0].cappedTubes" must contain at least 1 items', - ); - }); - }); - }); - - context('when user has role "CERTIF"', function () { - it('should return a response with an HTTP status code 403', async function () { - // given - sinon - .stub(securityPreHandlers, 'hasAtLeastOneAccessOf') - .withArgs([ - securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, - securityPreHandlers.checkAdminMemberHasRoleSupport, - securityPreHandlers.checkAdminMemberHasRoleMetier, - ]) - .callsFake( - () => (request, h) => - h - .response({ errors: new Error('forbidden') }) - .code(403) - .takeover(), - ); - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const { statusCode } = await httpTestServer.request(method, url, payload); - - // then - expect(statusCode).to.equal(403); - }); - }); - }); - describe('PATCH /api/admin/target-profiles/{id}', function () { const method = 'PATCH'; const url = '/api/admin/target-profiles/123';