Skip to content

Commit 186ba5f

Browse files
[FEATURE] Convertir les sessions et les centres de certification à la version 3 (PIX-14429).
#10342
2 parents 4dc9e37 + ee0e06b commit 186ba5f

File tree

10 files changed

+397
-0
lines changed

10 files changed

+397
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'dotenv/config';
2+
3+
import * as url from 'node:url';
4+
5+
import { disconnect as disconnectFromDb } from '../../../db/knex-database-connection.js';
6+
import { usecases } from '../../../src/certification/configuration/domain/usecases/index.js';
7+
import { logger } from '../../../src/shared/infrastructure/utils/logger.js';
8+
import { parseCsvWithHeaderAndRequiredFields } from '../../helpers/csvHelpers.js';
9+
10+
/**
11+
* Usage: DRY_RUN=true node scripts/certification/next-gen/convert-all-centers-to-v3.js <file.csv>
12+
* file.csv is a csv file containing a single id column to specify certification center ids we want to preserve
13+
* ie. these centers will not be converted to v3
14+
**/
15+
16+
const modulePath = url.fileURLToPath(import.meta.url);
17+
const isLaunchedFromCommandLine = process.argv[1] === modulePath;
18+
19+
async function main({ isDryRun, preservedCenterIds, dependencies }) {
20+
await dependencies.convertCentersToV3({ isDryRun, preservedCenterIds });
21+
await dependencies.convertSessionsWithNoCoursesToV3({ isDryRun });
22+
}
23+
24+
async function _readPreservedCenterIdsFromFile({ filePath }) {
25+
const preservedCenters = filePath
26+
? await parseCsvWithHeaderAndRequiredFields({
27+
filePath,
28+
requiredFieldNames: ['id'],
29+
})
30+
: [];
31+
return preservedCenters.map(({ id }) => id);
32+
}
33+
34+
(async () => {
35+
if (isLaunchedFromCommandLine) {
36+
let exitCode = 0;
37+
try {
38+
const isDryRun = process.env.DRY_RUN === 'true';
39+
const filePath = process.argv[2];
40+
const preservedCenterIds = await _readPreservedCenterIdsFromFile({ filePath });
41+
logger.info(`Converting centers to v3...`);
42+
await main({ isDryRun, preservedCenterIds, dependencies: usecases });
43+
logger.info(`Converting centers to v3 successfully!`);
44+
} catch (error) {
45+
logger.error(error, `Error while converting centers to v3`);
46+
exitCode = 1;
47+
} finally {
48+
await disconnectFromDb();
49+
// eslint-disable-next-line n/no-process-exit
50+
process.exit(exitCode);
51+
}
52+
}
53+
})();
54+
55+
export { main };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { logger } from '../../../../shared/infrastructure/utils/logger.js';
2+
/**
3+
* @typedef {import ('./index.js').CentersRepository} CentersRepository
4+
*/
5+
6+
/**
7+
* @param {Object} params
8+
* @param {boolean} params.isDryRun
9+
* @param {preservedCenterIds} params.preservedCenterIds
10+
* @param {CentersRepository} params.centerRepository
11+
* @returns {Promise<void>}
12+
*/
13+
export async function convertCentersToV3({ isDryRun, preservedCenterIds, centerRepository }) {
14+
if (isDryRun) {
15+
logger.info('Dry run requested, no centers are actually converted');
16+
const centerIdsToConvert = await centerRepository.findV2CenterIds({ preservedCenterIds });
17+
logger.info(`${centerIdsToConvert.length} centers would have been converted`);
18+
logger.info(`Following centers would have been converted: ${centerIdsToConvert}`);
19+
return;
20+
}
21+
const convertedCentersCount = await centerRepository.updateCentersToV3({ preservedCenterIds });
22+
logger.info(`${convertedCentersCount} centers successfully converted to v3`);
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { logger } from '../../../../shared/infrastructure/utils/logger.js';
2+
3+
export async function convertSessionsWithNoCoursesToV3({ isDryRun, sessionsRepository }) {
4+
if (isDryRun) {
5+
logger.info('Dry run requested, no sessions are actually converted');
6+
const sessionIdsToConvert = await sessionsRepository.findV2SessionIdsWithNoCourses();
7+
logger.info(`${sessionIdsToConvert.length} sessions would have been converted`);
8+
logger.info(`Following sessions would have been converted: ${sessionIdsToConvert}`);
9+
return;
10+
}
11+
const convertedSessionsCount = await sessionsRepository.updateV2SessionsWithNoCourses();
12+
logger.info(`${convertedSessionsCount} sessions successfully converted to v3`);
13+
}

api/src/certification/configuration/infrastructure/repositories/center-repository.js

+30
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,33 @@ export const resetWhitelist = async () => {
5454
.update({ isScoBlockedAccessWhitelist: false, updatedAt: knexConn.fn.now() })
5555
.where({ type: CenterTypes.SCO });
5656
};
57+
58+
/**
59+
* @param {Object} params
60+
* @param {Array<number>} params.preservedCenterIds
61+
* @returns {Promise<number>} updated centers count
62+
*/
63+
export const updateCentersToV3 = async ({ preservedCenterIds }) => {
64+
const knexConn = DomainTransaction.getConnection();
65+
const results = await knexConn('certification-centers')
66+
.update({ isV3Pilot: true, updatedAt: knexConn.fn.now() }, ['id'])
67+
.where({ isV3Pilot: false })
68+
.whereNotIn('id', preservedCenterIds);
69+
70+
return results.length;
71+
};
72+
73+
/**
74+
* @param {Object} params
75+
* @param {Array<number>} params.preservedCenterIds
76+
* @returns {Promise<Array<number>>} v2 center ids excluding preservedCenterIds
77+
*/
78+
export const findV2CenterIds = async ({ preservedCenterIds }) => {
79+
const knexConn = DomainTransaction.getConnection();
80+
const centers = await knexConn('certification-centers')
81+
.select('id')
82+
.where({ isV3Pilot: false })
83+
.whereNotIn('id', preservedCenterIds);
84+
85+
return centers.map(({ id }) => id);
86+
};

api/src/certification/configuration/infrastructure/repositories/sessions-repository.js

+42
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,45 @@ export const deleteUnstartedSession = async function ({ sessionId, sessionsApi }
4545
throw error;
4646
}
4747
};
48+
49+
/**
50+
* @returns {Promise<number>} updated sessions count
51+
*/
52+
export const updateV2SessionsWithNoCourses = async function () {
53+
const knexConn = DomainTransaction.getConnection();
54+
55+
const updatedSessions = await knexConn('sessions')
56+
.update({ version: 3 }, ['id'])
57+
.whereIn('id', function () {
58+
this.select('sessions.id')
59+
.from('sessions')
60+
.leftJoin('certification-courses', 'sessions.id', 'certification-courses.sessionId')
61+
.where('sessions.version', 2)
62+
.whereNull('certification-courses.sessionId')
63+
.join('certification-centers', 'certification-centers.id', 'sessions.certificationCenterId')
64+
.where('certification-centers.isV3Pilot', true);
65+
});
66+
67+
return updatedSessions.length;
68+
};
69+
70+
/**
71+
* @returns {Promise<Array<number>>} ids of v2 sessions with no courses
72+
*/
73+
export const findV2SessionIdsWithNoCourses = async function () {
74+
const knexConn = DomainTransaction.getConnection();
75+
76+
const sessions = await knexConn('sessions')
77+
.select('id')
78+
.whereIn('id', function () {
79+
this.select('sessions.id')
80+
.from('sessions')
81+
.leftJoin('certification-courses', 'sessions.id', 'certification-courses.sessionId')
82+
.where('sessions.version', 2)
83+
.whereNull('certification-courses.sessionId')
84+
.join('certification-centers', 'certification-centers.id', 'sessions.certificationCenterId')
85+
.where('certification-centers.isV3Pilot', true);
86+
});
87+
88+
return sessions.map(({ id }) => id);
89+
};

api/tests/certification/configuration/integration/infrastructure/repositories/center-repository_test.js

+49
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,53 @@ describe('Certification | Configuration | Integration | Repository | center-repo
196196
expect(updatedCenter.isScoBlockedAccessWhitelist).to.be.true;
197197
});
198198
});
199+
200+
describe('#updateCentersToV3', function () {
201+
it('should set isV3Pilot to true for v2 centers', async function () {
202+
const v2Center = databaseBuilder.factory.buildCertificationCenter({
203+
isV3Pilot: false,
204+
});
205+
await databaseBuilder.commit();
206+
207+
const count = await centerRepository.updateCentersToV3({ preservedCenterIds: [] });
208+
209+
const updatedCenter = await knex('certification-centers').where({ id: v2Center.id }).first();
210+
expect(updatedCenter.isV3Pilot).to.be.true;
211+
expect(count).to.equal(1);
212+
});
213+
214+
it('should avoid setting isV3Pilot to true for v2 centers of preservedCenterIds', async function () {
215+
const v2Center = databaseBuilder.factory.buildCertificationCenter({
216+
isV3Pilot: false,
217+
});
218+
const v2CenterToPreserve = databaseBuilder.factory.buildCertificationCenter({
219+
isV3Pilot: false,
220+
});
221+
await databaseBuilder.commit();
222+
223+
await centerRepository.updateCentersToV3({ preservedCenterIds: [v2CenterToPreserve.id] });
224+
225+
const updatedCenter = await knex('certification-centers').where({ id: v2Center.id }).first();
226+
expect(updatedCenter.isV3Pilot).to.be.true;
227+
228+
const preservedCenter = await knex('certification-centers').where({ id: v2CenterToPreserve.id }).first();
229+
expect(preservedCenter.isV3Pilot).to.be.false;
230+
});
231+
});
232+
233+
describe('#findV2Centers', function () {
234+
it('should avoid setting isV3Pilot to true for v2 centers of preservedCenterIds', async function () {
235+
const v2Center = databaseBuilder.factory.buildCertificationCenter({
236+
isV3Pilot: false,
237+
});
238+
const v2CenterToPreserve = databaseBuilder.factory.buildCertificationCenter({
239+
isV3Pilot: false,
240+
});
241+
await databaseBuilder.commit();
242+
243+
const centerIds = await centerRepository.findV2CenterIds({ preservedCenterIds: [v2CenterToPreserve.id] });
244+
245+
expect(centerIds).to.deep.equal([v2Center.id]);
246+
});
247+
});
199248
});

api/tests/certification/configuration/integration/infrastructure/repositories/sessions-repository_test.js

+89
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,93 @@ describe('Certification | Configuration | Integration | Repository | sessions-re
129129
});
130130
});
131131
});
132+
133+
describe('updateV2SessionsWithNoCourses', function () {
134+
it('should update v2 sessions with no courses of v3 centers', async function () {
135+
// given
136+
const certificationCenterId = databaseBuilder.factory.buildCertificationCenter({
137+
isV3Pilot: true,
138+
}).id;
139+
const activeSessionId = databaseBuilder.factory.buildSession({
140+
version: 2,
141+
certificationCenterId,
142+
}).id;
143+
databaseBuilder.factory.buildCertificationCourse({ sessionId: activeSessionId });
144+
const inactiveSessionId = databaseBuilder.factory.buildSession({
145+
version: 2,
146+
certificationCenterId,
147+
}).id;
148+
await databaseBuilder.commit();
149+
150+
// when
151+
const count = await configurationRepositories.sessionsRepository.updateV2SessionsWithNoCourses();
152+
153+
// then
154+
expect(count).to.equal(1);
155+
const sessions = await knex('sessions').select('id', 'version').orderBy('version');
156+
expect(sessions).to.deep.equal([
157+
{ id: activeSessionId, version: 2 },
158+
{ id: inactiveSessionId, version: 3 },
159+
]);
160+
});
161+
162+
it('should not update v2 sessions with no courses of v2 centers', async function () {
163+
// given
164+
const certificationCenterId = databaseBuilder.factory.buildCertificationCenter({
165+
isV3Pilot: false,
166+
}).id;
167+
const activeSessionId = databaseBuilder.factory.buildSession({
168+
version: 2,
169+
certificationCenterId,
170+
}).id;
171+
databaseBuilder.factory.buildCertificationCourse({ sessionId: activeSessionId });
172+
const inactiveSessionId = databaseBuilder.factory.buildSession({
173+
version: 2,
174+
certificationCenterId,
175+
}).id;
176+
await databaseBuilder.commit();
177+
178+
// when
179+
const count = await configurationRepositories.sessionsRepository.updateV2SessionsWithNoCourses();
180+
181+
// then
182+
expect(count).to.equal(0);
183+
const sessions = await knex('sessions').select('id', 'version').orderBy('version');
184+
expect(sessions).to.deep.equal([
185+
{ id: activeSessionId, version: 2 },
186+
{ id: inactiveSessionId, version: 2 },
187+
]);
188+
});
189+
});
190+
191+
describe('findV2SessionIdsWithNoCourses', function () {
192+
it('should return v2 session ids with no courses of v3 centers', async function () {
193+
// given
194+
const certificationCenterId = databaseBuilder.factory.buildCertificationCenter({
195+
isV3Pilot: true,
196+
}).id;
197+
const activeSessionId = databaseBuilder.factory.buildSession({
198+
version: 2,
199+
certificationCenterId,
200+
}).id;
201+
databaseBuilder.factory.buildCertificationCourse({ sessionId: activeSessionId });
202+
const inactiveSessionId = databaseBuilder.factory.buildSession({
203+
version: 2,
204+
certificationCenterId,
205+
}).id;
206+
const v2CertificationCenterId = databaseBuilder.factory.buildCertificationCenter({
207+
isV2Pilot: true,
208+
}).id;
209+
databaseBuilder.factory.buildSession({
210+
version: 2,
211+
certificationCenterId: v2CertificationCenterId,
212+
}).id;
213+
await databaseBuilder.commit();
214+
215+
// when
216+
const sessionIds = await configurationRepositories.sessionsRepository.findV2SessionIdsWithNoCourses();
217+
218+
expect(sessionIds).to.deep.equal([inactiveSessionId]);
219+
});
220+
});
132221
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { convertCentersToV3 } from '../../../../../../src/certification/configuration/domain/usecases/convert-centers-to-v3.js';
2+
import { expect, sinon } from '../../../../../test-helper.js';
3+
4+
describe('Certification | Configuration | Unit | UseCase | convert-centers-to-v3', function () {
5+
it('should update v2 certification courses to v3', async function () {
6+
const centerRepository = {
7+
updateCentersToV3: sinon.stub().resolves(),
8+
};
9+
const preservedCenterIds = ['123'];
10+
await convertCentersToV3({
11+
isDryRun: false,
12+
preservedCenterIds,
13+
centerRepository,
14+
});
15+
16+
expect(centerRepository.updateCentersToV3).to.have.been.calledWithExactly({ preservedCenterIds });
17+
});
18+
19+
context('when isDryRun is true', function () {
20+
it('should skip update', async function () {
21+
const centerRepository = {
22+
updateCentersToV3: sinon.stub().resolves(),
23+
findV2CenterIds: sinon.stub().resolves(['v2CenterId']),
24+
};
25+
const preservedCenterIds = ['123'];
26+
await convertCentersToV3({
27+
isDryRun: true,
28+
preservedCenterIds,
29+
centerRepository,
30+
});
31+
32+
expect(centerRepository.updateCentersToV3).not.to.have.been.called;
33+
expect(centerRepository.findV2CenterIds).to.have.been.calledWithExactly({ preservedCenterIds });
34+
});
35+
});
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { convertSessionsWithNoCoursesToV3 } from '../../../../../../src/certification/configuration/domain/usecases/convert-sessions-with-no-courses-to-v3.js';
2+
import { expect, sinon } from '../../../../../test-helper.js';
3+
4+
describe('Certification | Configuration | Unit | UseCase | convert-sessions-with-no-courses-to-v3', function () {
5+
it('should update v2 sessions with no certification courses to v3', async function () {
6+
const sessionsRepository = {
7+
updateV2SessionsWithNoCourses: sinon.stub().resolves(),
8+
};
9+
10+
await convertSessionsWithNoCoursesToV3({ isDryRun: false, sessionsRepository });
11+
12+
expect(sessionsRepository.updateV2SessionsWithNoCourses).to.have.been.calledOnceWith();
13+
});
14+
15+
context('when isDryRun is true', function () {
16+
it('should skip update', async function () {
17+
const sessionsRepository = {
18+
updateV2SessionsWithNoCourses: sinon.stub().resolves(),
19+
findV2SessionIdsWithNoCourses: sinon.stub().resolves(['123']),
20+
};
21+
22+
await convertSessionsWithNoCoursesToV3({ isDryRun: true, sessionsRepository });
23+
24+
expect(sessionsRepository.updateV2SessionsWithNoCourses).not.to.have.been.called;
25+
expect(sessionsRepository.findV2SessionIdsWithNoCourses).to.have.been.calledOnceWith();
26+
});
27+
});
28+
});

0 commit comments

Comments
 (0)