From 697abc154c586ebd39c0f8752f1b5ee7ece12e03 Mon Sep 17 00:00:00 2001 From: Steph0 Date: Tue, 10 Sep 2024 14:50:03 +0200 Subject: [PATCH 1/6] :sparkles: api: create whitelist column in certification-centers table Co-authored-by: P-Jeremy < jemyplu@gmail.com> --- .../20240910144200_certification_whitelist.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 api/db/migrations/20240910144200_certification_whitelist.js diff --git a/api/db/migrations/20240910144200_certification_whitelist.js b/api/db/migrations/20240910144200_certification_whitelist.js new file mode 100644 index 00000000000..ff27faf3491 --- /dev/null +++ b/api/db/migrations/20240910144200_certification_whitelist.js @@ -0,0 +1,21 @@ +const TABLE_NAME = 'certification-centers'; +const COLUMN_NAME = 'isScoBlockedAccessWhitelist'; + +const up = async function (knex) { + await knex.schema.table(TABLE_NAME, function (table) { + table + .boolean(COLUMN_NAME) + .defaultTo(false) + .comment( + 'As of now, the center is currently eligible to SCO access closure when the property is set to false. Otherwise, the center is whitelisted (not closed)', + ); + }); +}; + +const down = async function (knex) { + await knex.schema.table(TABLE_NAME, function (table) { + table.dropColumn(COLUMN_NAME); + }); +}; + +export { down, up }; From 0faa99d2a9a3c29e7532e36c92ae3878bbfaa19f Mon Sep 17 00:00:00 2001 From: Steph0 Date: Tue, 10 Sep 2024 15:50:31 +0200 Subject: [PATCH 2/6] :sparkles: api: tag a certification center as whitelisted Co-authored-by: P-Jeremy < jemyplu@gmail.com> --- .../factory/build-certification-center.js | 2 ++ .../domain/usecases/import-sco-whitelist.js | 11 ++++++++ .../repositories/center-repository.js | 13 ++++++++++ .../repositories/center-repository_test.js | 25 ++++++++++++++++++- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 api/src/certification/configuration/domain/usecases/import-sco-whitelist.js diff --git a/api/db/database-builder/factory/build-certification-center.js b/api/db/database-builder/factory/build-certification-center.js index 2871704ea15..36a7f09bb44 100644 --- a/api/db/database-builder/factory/build-certification-center.js +++ b/api/db/database-builder/factory/build-certification-center.js @@ -8,6 +8,7 @@ const buildCertificationCenter = function ({ createdAt = new Date('2020-01-01'), updatedAt, isV3Pilot = false, + isScoBlockedAccessWhitelist = false, } = {}) { const values = { id, @@ -17,6 +18,7 @@ const buildCertificationCenter = function ({ createdAt, updatedAt, isV3Pilot, + isScoBlockedAccessWhitelist, }; return databaseBuffer.pushInsertable({ tableName: 'certification-centers', diff --git a/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js b/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js new file mode 100644 index 00000000000..40d0e5e4202 --- /dev/null +++ b/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js @@ -0,0 +1,11 @@ +/** + * @typedef {import ('../../domain/usecases/index.js').CenterRepository} CenterRepository + */ + +/** + * @param {Object} params + * @param {CenterRepository} params.centerRepository + */ +export const importScoWhitelist = async ({ externalIds = [], centerRepository }) => { + return centerRepository.addToWhitelistByExternalIds({externalIds}); +}; diff --git a/api/src/certification/configuration/infrastructure/repositories/center-repository.js b/api/src/certification/configuration/infrastructure/repositories/center-repository.js index 83fdc46ce81..ccb61c79c81 100644 --- a/api/src/certification/configuration/infrastructure/repositories/center-repository.js +++ b/api/src/certification/configuration/infrastructure/repositories/center-repository.js @@ -19,3 +19,16 @@ export const findSCOV2Centers = async function ({ pageNumber = DEFAULT_PAGINATIO return { centerIds: results.map(({ id }) => id), pagination }; }; + +/** + * @param {Object} params + * @param {Array} params.externalIds + * @returns {Promise} + */ +export const addToWhitelistByExternalIds = async ({ externalIds }) => { + const knexConn = DomainTransaction.getConnection(); + + await knexConn('certification-centers') + .update({ isScoBlockedAccessWhitelist: true }) + .whereIn('externalId', externalIds); +}; diff --git a/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js b/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js index 5ae868bc1f3..e9502d9dbaf 100644 --- a/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js +++ b/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js @@ -1,7 +1,8 @@ import * as centerRepository from '../../../../../../src/certification/configuration/infrastructure/repositories/center-repository.js'; +import { CenterTypes } from '../../../../../../src/certification/enrolment/domain/models/CenterTypes.js'; import { config } from '../../../../../../src/shared/config.js'; import { CERTIFICATION_CENTER_TYPES } from '../../../../../../src/shared/domain/constants.js'; -import { databaseBuilder, expect } from '../../../../../test-helper.js'; +import { databaseBuilder, expect, knex } from '../../../../../test-helper.js'; describe('Certification | Configuration | Integration | Repository | center-repository', function () { describe('findSCOV2Centers', function () { @@ -105,4 +106,26 @@ describe('Certification | Configuration | Integration | Repository | center-repo }); }); }); + + describe('#addToWhitelistByExternalIds', function () { + it('should set the centers as whitelisted', async function () { + // given + const whitelistedExternalId = '1234ABC'; + const centerId = databaseBuilder.factory.buildCertificationCenter({ + type: CenterTypes.SCO, + externalId: whitelistedExternalId2, + isScoBlockedAccessWhitelist: false, + }).id; + await databaseBuilder.commit(); + + // when + await centerRepository.addToWhitelistByExternalIds({ + externalIds: [whitelistedExternalId1, whitelistedExternalId2], + }); + + // then + const updatedCenter = await knex('certification-centers').where({ id: centerId }).first(); + expect(updatedCenter.isScoBlockedAccessWhitelist).to.be.true; + }); + }); }); From 39790aa879f93ce59dec0934e55f76766e94d56d Mon Sep 17 00:00:00 2001 From: Steph0 Date: Tue, 10 Sep 2024 16:54:29 +0200 Subject: [PATCH 3/6] :sparkles: api: reset whitelist before whitelisting centers Co-authored-by: P-Jeremy < jemyplu@gmail.com> --- .../domain/usecases/import-sco-whitelist.js | 19 +++++++---- .../repositories/center-repository.js | 11 ++++-- .../repositories/center-repository_test.js | 18 ++++++++++ .../usecases/import-sco-whitelist_test.js | 34 +++++++++++++++++++ 4 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 api/tests/certification/configuration/unit/domain/usecases/import-sco-whitelist_test.js diff --git a/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js b/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js index 40d0e5e4202..22a8ecf1ecb 100644 --- a/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js +++ b/api/src/certification/configuration/domain/usecases/import-sco-whitelist.js @@ -2,10 +2,15 @@ * @typedef {import ('../../domain/usecases/index.js').CenterRepository} CenterRepository */ -/** - * @param {Object} params - * @param {CenterRepository} params.centerRepository - */ -export const importScoWhitelist = async ({ externalIds = [], centerRepository }) => { - return centerRepository.addToWhitelistByExternalIds({externalIds}); -}; +import { withTransaction } from '../../../../shared/domain/DomainTransaction.js'; + +export const importScoWhitelist = withTransaction( + /** + * @param {Object} params + * @param {CenterRepository} params.centerRepository + */ + async ({ externalIds = [], centerRepository }) => { + await centerRepository.resetWhitelist(); + return centerRepository.addToWhitelistByExternalIds({ externalIds }); + }, +); diff --git a/api/src/certification/configuration/infrastructure/repositories/center-repository.js b/api/src/certification/configuration/infrastructure/repositories/center-repository.js index ccb61c79c81..bd4c7f9e94e 100644 --- a/api/src/certification/configuration/infrastructure/repositories/center-repository.js +++ b/api/src/certification/configuration/infrastructure/repositories/center-repository.js @@ -27,8 +27,15 @@ export const findSCOV2Centers = async function ({ pageNumber = DEFAULT_PAGINATIO */ export const addToWhitelistByExternalIds = async ({ externalIds }) => { const knexConn = DomainTransaction.getConnection(); - - await knexConn('certification-centers') + return knexConn('certification-centers') .update({ isScoBlockedAccessWhitelist: true }) .whereIn('externalId', externalIds); }; + +/** + * @returns {Promise} + */ +export const resetWhitelist = async () => { + const knexConn = DomainTransaction.getConnection(); + return knexConn('certification-centers').update({ isScoBlockedAccessWhitelist: false }); +}; diff --git a/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js b/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js index e9502d9dbaf..5294c1ba5a6 100644 --- a/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js +++ b/api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js @@ -128,4 +128,22 @@ describe('Certification | Configuration | Integration | Repository | center-repo expect(updatedCenter.isScoBlockedAccessWhitelist).to.be.true; }); }); + + describe('#resetWhitelist', function () { + it('should reset all isScoBlockedAccessWhitelist to false', async function () { + // given + const centerId = databaseBuilder.factory.buildCertificationCenter({ + type: CenterTypes.SCO, + isScoBlockedAccessWhitelist: true, + }).id; + await databaseBuilder.commit(); + + // when + await centerRepository.resetWhitelist(); + + // then + const updatedCenter = await knex('certification-centers').where({ id: centerId }).first(); + expect(updatedCenter.isScoBlockedAccessWhitelist).to.be.false; + }); + }); }); diff --git a/api/tests/certification/configuration/unit/domain/usecases/import-sco-whitelist_test.js b/api/tests/certification/configuration/unit/domain/usecases/import-sco-whitelist_test.js new file mode 100644 index 00000000000..15bb25989e6 --- /dev/null +++ b/api/tests/certification/configuration/unit/domain/usecases/import-sco-whitelist_test.js @@ -0,0 +1,34 @@ +import { DomainTransaction } from '../../../../../../lib/infrastructure/DomainTransaction.js'; +import { importScoWhitelist } from '../../../../../../src/certification/configuration/domain/usecases/import-sco-whitelist.js'; +import { expect, sinon } from '../../../../../test-helper.js'; + +describe('Certification | Configuration | Unit | UseCase | import-sco-whitelist', function () { + let centerRepository; + + beforeEach(function () { + sinon.stub(DomainTransaction, 'execute').callsFake((callback) => { + return callback(); + }); + + centerRepository = { + addToWhitelistByExternalIds: sinon.stub(), + resetWhitelist: sinon.stub(), + }; + }); + + it('should whitelist a center', async function () { + // given + centerRepository.resetWhitelist.resolves(); + centerRepository.addToWhitelistByExternalIds.resolves(); + + // when + await importScoWhitelist({ + externalIds: [12], + centerRepository, + }); + + // then + expect(centerRepository.resetWhitelist).to.have.been.calledOnce; + expect(centerRepository.addToWhitelistByExternalIds).to.have.been.calledOnceWithExactly({ externalIds: [12] }); + }); +}); From fc4112e85364d418322c1f31e7da40380f8ceb79 Mon Sep 17 00:00:00 2001 From: Steph0 Date: Tue, 24 Sep 2024 16:06:30 +0200 Subject: [PATCH 4/6] :sparkles: api: import whitelist route and controller --- api/server.js | 3 +- .../application/sco-whitelist-controller.js | 12 ++++ .../application/sco-whitelist-route.js | 47 ++++++++++++++ .../csv/sco-whitelist-csv-header.js | 19 ++++++ .../csv/sco-whitelist-csv-parser.js | 19 ++++++ api/src/certification/configuration/routes.js | 4 +- .../application/sco-whitelist-route_test.js | 62 +++++++++++++++++++ .../application/sco-whitelist-route_test.js | 27 ++++++++ .../csv/sco-whitelist-csv-parser_test.js | 34 ++++++++++ 9 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 api/src/certification/configuration/application/sco-whitelist-controller.js create mode 100644 api/src/certification/configuration/application/sco-whitelist-route.js create mode 100644 api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-header.js create mode 100644 api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-parser.js create mode 100644 api/tests/certification/configuration/acceptance/application/sco-whitelist-route_test.js create mode 100644 api/tests/certification/configuration/unit/application/sco-whitelist-route_test.js create mode 100644 api/tests/certification/configuration/unit/infrastructure/serializers/csv/sco-whitelist-csv-parser_test.js diff --git a/api/server.js b/api/server.js index a67b048dbbe..cef0f097ccb 100644 --- a/api/server.js +++ b/api/server.js @@ -10,7 +10,7 @@ import { attachTargetProfileRoutes, complementaryCertificationRoutes, } from './src/certification/complementary-certification/routes.js'; -import { certificationConfigurationRoutes } from './src/certification/configuration/routes.js'; +import { certificationConfigurationRoutes, scoWhitelistRoutes } from './src/certification/configuration/routes.js'; import { certificationEnrolmentRoutes } from './src/certification/enrolment/routes.js'; import { flashCertificationRoutes } from './src/certification/flash-certification/routes.js'; import { certificationResultRoutes } from './src/certification/results/routes.js'; @@ -46,6 +46,7 @@ const certificationRoutes = [ certificationSessionRoutes, complementaryCertificationRoutes, scoringRoutes, + scoWhitelistRoutes, ]; const prescriptionRoutes = [ diff --git a/api/src/certification/configuration/application/sco-whitelist-controller.js b/api/src/certification/configuration/application/sco-whitelist-controller.js new file mode 100644 index 00000000000..9d14cb0fd7b --- /dev/null +++ b/api/src/certification/configuration/application/sco-whitelist-controller.js @@ -0,0 +1,12 @@ +import { usecases } from '../domain/usecases/index.js'; +import { extractExternalIds } from '../infrastructure/serializers/csv/sco-whitelist-csv-parser.js'; + +const importScoWhitelist = async function (request, h, dependencies = { extractExternalIds }) { + const externalIds = await dependencies.extractExternalIds(request.payload.path); + await usecases.importScoWhitelist({ externalIds }); + return h.response().created(); +}; + +export const scoWhitelistController = { + importScoWhitelist, +}; diff --git a/api/src/certification/configuration/application/sco-whitelist-route.js b/api/src/certification/configuration/application/sco-whitelist-route.js new file mode 100644 index 00000000000..e1c8d881e96 --- /dev/null +++ b/api/src/certification/configuration/application/sco-whitelist-route.js @@ -0,0 +1,47 @@ +import { PayloadTooLargeError, sendJsonApiError } from '../../../shared/application/http-errors.js'; +import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js'; +import { scoWhitelistController } from './sco-whitelist-controller.js'; + +const TWENTY_MEGABYTES = 1048576 * 20; + +const register = async function (server) { + server.route([ + { + method: 'POST', + path: '/api/admin/sco-whitelist', + config: { + pre: [ + { + method: (request, h) => + securityPreHandlers.hasAtLeastOneAccessOf([securityPreHandlers.checkAdminMemberHasRoleSuperAdmin])( + request, + h, + ), + assign: 'hasAuthorizationToAccessAdminScope', + }, + ], + payload: { + maxBytes: TWENTY_MEGABYTES, + output: 'file', + failAction: (request, h) => { + return sendJsonApiError( + new PayloadTooLargeError('An error occurred, payload is too large', 'PAYLOAD_TOO_LARGE', { + maxSize: '20', + }), + h, + ); + }, + }, + handler: scoWhitelistController.importScoWhitelist, + tags: ['api', 'admin'], + notes: [ + 'Cette route est restreinte aux utilisateurs authentifiés avec le rôle Super Admin', + 'Elle permet de mettre a jour la liste blanche des centres SCO.', + ], + }, + }, + ]); +}; + +const name = 'sco-whitelist-api'; +export { name, register }; diff --git a/api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-header.js b/api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-header.js new file mode 100644 index 00000000000..6d6345bee23 --- /dev/null +++ b/api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-header.js @@ -0,0 +1,19 @@ +import { CsvColumn } from '../../../../../../lib/infrastructure/serializers/csv/csv-column.js'; + +class ScoWhitelistCsvHeader { + constructor() { + this.columns = this.setColumns(); + } + + setColumns() { + return [ + new CsvColumn({ + property: 'externalId', + name: 'externalId', + isRequired: true, + }), + ]; + } +} + +export { ScoWhitelistCsvHeader }; diff --git a/api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-parser.js b/api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-parser.js new file mode 100644 index 00000000000..8d3cacf082c --- /dev/null +++ b/api/src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-parser.js @@ -0,0 +1,19 @@ +import * as fs from 'node:fs/promises'; + +import { CsvParser } from '../../../../../shared/infrastructure/serializers/csv/csv-parser.js'; +import { ScoWhitelistCsvHeader } from './sco-whitelist-csv-header.js'; + +export const extractExternalIds = async (file) => { + const buffer = await fs.readFile(file); + try { + return _extractIds(buffer).map(({ externalId }) => externalId.trim()); + } finally { + fs.unlink(file); + } +}; + +const _extractIds = (buffer) => { + const columns = new ScoWhitelistCsvHeader(); + const campaignIdsCsv = new CsvParser(buffer, columns); + return campaignIdsCsv.parse(); +}; diff --git a/api/src/certification/configuration/routes.js b/api/src/certification/configuration/routes.js index 2656fd6b136..4841d31ba5b 100644 --- a/api/src/certification/configuration/routes.js +++ b/api/src/certification/configuration/routes.js @@ -1,5 +1,7 @@ import * as complementaryCertification from './application/complementary-certification-route.js'; +import * as scoWhitelist from './application/sco-whitelist-route.js'; const certificationConfigurationRoutes = [complementaryCertification]; +const scoWhitelistRoutes = [scoWhitelist]; -export { certificationConfigurationRoutes }; +export { certificationConfigurationRoutes, scoWhitelistRoutes }; diff --git a/api/tests/certification/configuration/acceptance/application/sco-whitelist-route_test.js b/api/tests/certification/configuration/acceptance/application/sco-whitelist-route_test.js new file mode 100644 index 00000000000..fc209fc7bef --- /dev/null +++ b/api/tests/certification/configuration/acceptance/application/sco-whitelist-route_test.js @@ -0,0 +1,62 @@ +import { CERTIFICATION_CENTER_TYPES } from '../../../../../src/shared/domain/constants.js'; +import { + createServer, + databaseBuilder, + expect, + generateValidRequestAuthorizationHeader, + insertUserWithRoleSuperAdmin, + knex, +} from '../../../../test-helper.js'; + +describe('Certification | Configuration | Acceptance | API | sco-whitelist-route', function () { + let server; + + beforeEach(async function () { + server = await createServer(); + }); + + describe('POST /api/admin/sco-whitelist', function () { + it('should return 200 HTTP status code', async function () { + // given + const superAdmin = await insertUserWithRoleSuperAdmin(); + const buffer = 'externalId\next1\next2'; + const options = { + method: 'POST', + url: '/api/admin/sco-whitelist', + headers: { + authorization: generateValidRequestAuthorizationHeader(superAdmin.id), + }, + payload: buffer, + }; + databaseBuilder.factory.buildCertificationCenter({ + isV3Pilot: true, + type: CERTIFICATION_CENTER_TYPES.SCO, + externalId: 'ext1', + isScoBlockedAccessWhitelist: false, + }); + databaseBuilder.factory.buildCertificationCenter({ + isV3Pilot: true, + type: CERTIFICATION_CENTER_TYPES.SCO, + externalId: 'ext2', + isScoBlockedAccessWhitelist: false, + }); + databaseBuilder.factory.buildCertificationCenter({ + isV3Pilot: true, + type: CERTIFICATION_CENTER_TYPES.SCO, + externalId: 'ext3', + isScoBlockedAccessWhitelist: true, + }); + await databaseBuilder.commit(); + + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(201); + const whitelist = await knex('certification-centers') + .where({ isScoBlockedAccessWhitelist: true }) + .pluck('externalId'); + expect(whitelist).to.deep.equal(['ext1', 'ext2']); + }); + }); +}); diff --git a/api/tests/certification/configuration/unit/application/sco-whitelist-route_test.js b/api/tests/certification/configuration/unit/application/sco-whitelist-route_test.js new file mode 100644 index 00000000000..d819b4e55a0 --- /dev/null +++ b/api/tests/certification/configuration/unit/application/sco-whitelist-route_test.js @@ -0,0 +1,27 @@ +import { scoWhitelistController } from '../../../../../src/certification/configuration/application/sco-whitelist-controller.js'; +import * as moduleUnderTest from '../../../../../src/certification/configuration/application/sco-whitelist-route.js'; +import { securityPreHandlers } from '../../../../../src/shared/application/security-pre-handlers.js'; +import { expect, HttpTestServer, sinon } from '../../../../test-helper.js'; + +describe('Certification | Configuration | Unit | Application | Router | sco-whitelist-route', function () { + describe('POST /api/admin/sco-whitelist', function () { + describe('when the user authenticated has no role', function () { + it('should return 403 HTTP status code', async function () { + // given + sinon + .stub(securityPreHandlers, 'hasAtLeastOneAccessOf') + .returns((request, h) => h.response().code(403).takeover()); + sinon.stub(scoWhitelistController, 'importScoWhitelist').returns('ok'); + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(moduleUnderTest); + + // when + const response = await httpTestServer.request('POST', '/api/admin/sco-whitelist'); + + // then + expect(response.statusCode).to.equal(403); + sinon.assert.notCalled(scoWhitelistController.importScoWhitelist); + }); + }); + }); +}); diff --git a/api/tests/certification/configuration/unit/infrastructure/serializers/csv/sco-whitelist-csv-parser_test.js b/api/tests/certification/configuration/unit/infrastructure/serializers/csv/sco-whitelist-csv-parser_test.js new file mode 100644 index 00000000000..735f9b8b9b1 --- /dev/null +++ b/api/tests/certification/configuration/unit/infrastructure/serializers/csv/sco-whitelist-csv-parser_test.js @@ -0,0 +1,34 @@ +import { extractExternalIds } from '../../../../../../../src/certification/configuration/infrastructure/serializers/csv/sco-whitelist-csv-parser.js'; +import { CsvImportError } from '../../../../../../../src/shared/domain/errors.js'; +import { catchErr, createTempFile, expect, removeTempFile } from '../../../../../../test-helper.js'; + +describe('Integration | Serializer | CSV | Certification | Configuration | sco-whitelist-csv-parser', function () { + describe('#extractExternalIds', function () { + let file; + + afterEach(function () { + removeTempFile(file); + }); + + context('when the file is correctly parsed', function () { + it('returns all external ids in the file', async function () { + file = 'valid.csv'; + const data = 'externalId\n ext1 \next2'; + const filePath = await createTempFile(file, data); + const ids = await extractExternalIds(filePath); + expect(ids).to.exactlyContain(['ext1', 'ext2']); + }); + }); + + context('when the file is incorrectly formed', function () { + it('throws an error', async function () { + file = 'invalid.csv'; + const data = 'RendLesDonnées\n1'; + const filePath = await createTempFile(file, data); + const error = await catchErr(extractExternalIds)(filePath); + expect(error).to.be.an.instanceOf(CsvImportError); + expect(error.code).to.equal('ENCODING_NOT_SUPPORTED'); + }); + }); + }); +}); From 615876b88bf89e78ce172c1e853b225404da24a4 Mon Sep 17 00:00:00 2001 From: Steph0 Date: Tue, 24 Sep 2024 16:50:37 +0200 Subject: [PATCH 5/6] :sparkles: admin: import new whitelist button --- .../administration/certification/index.gjs | 2 + .../sco-whitelist-configuration.gjs | 58 +++++++++++ .../sco-whitelist-configuration-test.gjs | 95 +++++++++++++++++++ admin/translations/en.json | 8 ++ admin/translations/fr.json | 8 ++ 5 files changed, 171 insertions(+) create mode 100644 admin/app/components/administration/certification/sco-whitelist-configuration.gjs create mode 100644 admin/tests/integration/components/administration/certification/sco-whitelist-configuration-test.gjs diff --git a/admin/app/components/administration/certification/index.gjs b/admin/app/components/administration/certification/index.gjs index a5817542dfc..271f1745426 100644 --- a/admin/app/components/administration/certification/index.gjs +++ b/admin/app/components/administration/certification/index.gjs @@ -1,9 +1,11 @@ import CertificationScoringConfiguration from './certification-scoring-configuration'; import CompetenceScoringConfiguration from './competence-scoring-configuration'; import FlashAlgorithmConfiguration from './flash-algorithm-configuration'; +import ScoWhitelistConfiguration from './sco-whitelist-configuration'; import ScoringSimulator from './scoring-simulator';