Skip to content

Commit

Permalink
[TECH] Permettre la désactivation des CRON liés au CPF (PIX-14388).
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Oct 8, 2024
2 parents 496c00c + e5f4730 commit 565c4e3
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 74 deletions.
12 changes: 12 additions & 0 deletions api/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,12 @@ TEST_REDIS_URL=redis://localhost:6379
# default: 3
# sample: CPF_PLANNER_JOB_MINIMUM_RELIABILITY_PERIOD=3

# Toggle of the cpf planner job
#
# presence: optional
# type: string
# sample: PGBOSS_PLANNER_JOB_ENABLED=true

# Cron of the cpf planner job
#
# presence: required for CPF xml file generation, optional otherwise
Expand All @@ -875,6 +881,12 @@ TEST_REDIS_URL=redis://localhost:6379
# type: string
# sample:CPF_SEND_EMAIL_JOB_RECIPIENT=

# Toggle of the cpf email job
#
# presence: optional
# type: string
# sample: PGBOSS_EXPORT_SENDER_JOB_ENABLED=true

# Cron of the cpf email job
#
# presence: required for CPF xml file generation, optional otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class CpfExportPlannerJobController extends JobScheduleController {
super('CpfExportPlannerJob', { jobCron: config.cpf.plannerJob.cron });
}

get isJobEnabled() {
return config.pgBoss.plannerJobEnabled;
}

async handle({ jobId, dependencies = { cpfCertificationResultRepository, cpfExportBuilderJobRepository, logger } }) {
const startDate = dayjs()
.utc()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class CpfExportSenderJobController extends JobScheduleController {
super('CpfExportSenderJob', { jobCron: config.cpf.sendEmailJob.cron });
}

get isJobEnabled() {
return config.pgBoss.exportSenderJobEnabled;
}

async handle({ dependencies = { mailService } }) {
const generatedFiles = await usecases.getPreSignedUrls();

Expand Down
7 changes: 7 additions & 0 deletions api/src/shared/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ const configuration = (function () {
importFileJobEnabled: process.env.PGBOSS_IMPORT_FILE_JOB_ENABLED
? toBoolean(process.env.PGBOSS_IMPORT_FILE_JOB_ENABLED)
: true,
plannerJobEnabled: process.env.PGBOSS_PLANNER_JOB_ENABLED
? toBoolean(process.env.PGBOSS_PLANNER_JOB_ENABLED)
: true,
exportSenderJobEnabled: process.env.PGBOSS_EXPORT_SENDER_JOB_ENABLED
? toBoolean(process.env.PGBOSS_EXPORT_SENDER_JOB_ENABLED)
: true,
},
poleEmploi: {
clientId: process.env.POLE_EMPLOI_CLIENT_ID,
Expand Down Expand Up @@ -484,6 +490,7 @@ const configuration = (function () {

config.cpf.sendEmailJob = {
recipient: '[email protected]',
cron: '0 3 * * *',
};

config.jwtConfig.livretScolaire = { secret: 'secretosmose', tokenLifespan: '1h' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,56 +27,82 @@ describe('Unit | Application | Certification | Sessions Management | jobs | cpf-
};
});

it('should send to CpfExportBuilderJob chunks of certification course ids', async function () {
// given
const jobId = '237584-7648';
const logger = { info: noop };
sinon.stub(cpf.plannerJob, 'chunkSize').value(2);
sinon.stub(cpf.plannerJob, 'monthsToProcess').value(2);
sinon.stub(cpf.plannerJob, 'minimumReliabilityPeriod').value(2);

const startDate = dayjs().utc().subtract(3, 'months').startOf('month').toDate();
const endDate = dayjs().utc().subtract(2, 'months').endOf('month').toDate();

cpfCertificationResultRepository.countExportableCertificationCoursesByTimeRange.resolves(5);

// when
const jobController = new CpfExportPlannerJobController();
await jobController.handle({
jobId,
dependencies: { cpfCertificationResultRepository, cpfExportBuilderJobRepository, logger },
});
describe('#isJobEnabled', function () {
it('return true when job is enabled', function () {
//given
sinon.stub(config.pgBoss, 'plannerJobEnabled').value(true);

// when
const handler = new CpfExportPlannerJobController();

// then
expect(cpfCertificationResultRepository.markCertificationToExport).to.have.been.callCount(3);
expect(cpfCertificationResultRepository.markCertificationToExport.getCall(0)).to.have.been.calledWithExactly({
startDate,
endDate,
limit: 2,
offset: 0,
batchId: '237584-7648#0',
// then
expect(handler.isJobEnabled).to.be.true;
});
expect(cpfCertificationResultRepository.markCertificationToExport.getCall(1)).to.have.been.calledWithExactly({
startDate,
endDate,
limit: 2,
offset: 2,
batchId: '237584-7648#1',

it('return false when job is disabled', function () {
//given
sinon.stub(config.pgBoss, 'plannerJobEnabled').value(false);

//when
const handler = new CpfExportPlannerJobController();

//then
expect(handler.isJobEnabled).to.be.false;
});
expect(cpfCertificationResultRepository.markCertificationToExport.getCall(2)).to.have.been.calledWithExactly({
startDate,
endDate,
limit: 2,
offset: 4,
batchId: '237584-7648#2',
});

describe('#handle', function () {
it('should send to CpfExportBuilderJob chunks of certification course ids', async function () {
// given
const jobId = '237584-7648';
const logger = { info: noop };
sinon.stub(cpf.plannerJob, 'chunkSize').value(2);
sinon.stub(cpf.plannerJob, 'monthsToProcess').value(2);
sinon.stub(cpf.plannerJob, 'minimumReliabilityPeriod').value(2);

const startDate = dayjs().utc().subtract(3, 'months').startOf('month').toDate();
const endDate = dayjs().utc().subtract(2, 'months').endOf('month').toDate();

cpfCertificationResultRepository.countExportableCertificationCoursesByTimeRange.resolves(5);

// when
const jobController = new CpfExportPlannerJobController();
await jobController.handle({
jobId,
dependencies: { cpfCertificationResultRepository, cpfExportBuilderJobRepository, logger },
});

// then
expect(cpfCertificationResultRepository.markCertificationToExport).to.have.been.callCount(3);
expect(cpfCertificationResultRepository.markCertificationToExport.getCall(0)).to.have.been.calledWithExactly({
startDate,
endDate,
limit: 2,
offset: 0,
batchId: '237584-7648#0',
});
expect(cpfCertificationResultRepository.markCertificationToExport.getCall(1)).to.have.been.calledWithExactly({
startDate,
endDate,
limit: 2,
offset: 2,
batchId: '237584-7648#1',
});
expect(cpfCertificationResultRepository.markCertificationToExport.getCall(2)).to.have.been.calledWithExactly({
startDate,
endDate,
limit: 2,
offset: 4,
batchId: '237584-7648#2',
});
expect(
cpfCertificationResultRepository.countExportableCertificationCoursesByTimeRange,
).to.have.been.calledWithExactly({ startDate, endDate });
expect(cpfExportBuilderJobRepository.performAsync).to.have.been.calledOnceWith(
new CpfExportBuilderJob({ batchId: '237584-7648#0' }),
new CpfExportBuilderJob({ batchId: '237584-7648#1' }),
new CpfExportBuilderJob({ batchId: '237584-7648#2' }),
);
});
expect(
cpfCertificationResultRepository.countExportableCertificationCoursesByTimeRange,
).to.have.been.calledWithExactly({ startDate, endDate });
expect(cpfExportBuilderJobRepository.performAsync).to.have.been.calledOnceWith(
new CpfExportBuilderJob({ batchId: '237584-7648#0' }),
new CpfExportBuilderJob({ batchId: '237584-7648#1' }),
new CpfExportBuilderJob({ batchId: '237584-7648#2' }),
);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CpfExportSenderJobController } from '../../../../../../src/certification/session-management/application/jobs/cpf-export-sender-job-controller.js';
import { usecases } from '../../../../../../src/certification/session-management/domain/usecases/index.js';
import { config } from '../../../../../../src/shared/config.js';
import { logger } from '../../../../../../src/shared/infrastructure/utils/logger.js';
import { expect, sinon } from '../../../../../test-helper.js';

Expand All @@ -11,45 +12,71 @@ describe('Unit | Application | Certification | Sessions Management | jobs | cpf-
mailService = { sendCpfEmail: sinon.stub() };
});

describe('when generated files are found', function () {
it('should send an email with a list of generated files url', async function () {
// given
usecases.getPreSignedUrls = sinon.stub();
usecases.getPreSignedUrls.resolves([
'https://bucket.url.com/file1.xml',
'https://bucket.url.com/file2.xml',
'https://bucket.url.com/file3.xml',
]);
describe('#isJobEnabled', function () {
it('return true when job is enabled', function () {
//given
sinon.stub(config.pgBoss, 'exportSenderJobEnabled').value(true);

// when
const jobController = new CpfExportSenderJobController();
await jobController.handle({ dependencies: { mailService } });
const handler = new CpfExportSenderJobController();

// then
expect(mailService.sendCpfEmail).to.have.been.calledWithExactly({
email: '[email protected]',
generatedFiles: [
expect(handler.isJobEnabled).to.be.true;
});

it('return false when job is disabled', function () {
//given
sinon.stub(config.pgBoss, 'exportSenderJobEnabled').value(false);

//when
const handler = new CpfExportSenderJobController();

//then
expect(handler.isJobEnabled).to.be.false;
});
});

describe('#handle', function () {
describe('when generated files are found', function () {
it('should send an email with a list of generated files url', async function () {
// given
usecases.getPreSignedUrls = sinon.stub();
usecases.getPreSignedUrls.resolves([
'https://bucket.url.com/file1.xml',
'https://bucket.url.com/file2.xml',
'https://bucket.url.com/file3.xml',
],
]);

// when
const jobController = new CpfExportSenderJobController();
await jobController.handle({ dependencies: { mailService } });

// then
expect(mailService.sendCpfEmail).to.have.been.calledWithExactly({
email: '[email protected]',
generatedFiles: [
'https://bucket.url.com/file1.xml',
'https://bucket.url.com/file2.xml',
'https://bucket.url.com/file3.xml',
],
});
});
});
});

describe('when no generated file is found', function () {
it('should not send an email', async function () {
// given
usecases.getPreSignedUrls = sinon.stub();
usecases.getPreSignedUrls.resolves([]);
describe('when no generated file is found', function () {
it('should not send an email', async function () {
// given
usecases.getPreSignedUrls = sinon.stub();
usecases.getPreSignedUrls.resolves([]);

// when
const jobController = new CpfExportSenderJobController();
await jobController.handle({ dependencies: { mailService } });
// when
const jobController = new CpfExportSenderJobController();
await jobController.handle({ dependencies: { mailService } });

// then
expect(mailService.sendCpfEmail).to.not.have.been.called;
expect(logger.info).to.have.been.calledWithExactly(`No CPF exports files ready to send`);
// then
expect(mailService.sendCpfEmail).to.not.have.been.called;
expect(logger.info).to.have.been.calledWithExactly(`No CPF exports files ready to send`);
});
});
});
});
19 changes: 19 additions & 0 deletions api/tests/unit/worker_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,24 @@ describe('#registerJobs', function () {
'legyNameForScheduleComputeOrganizationLearnersCertificabilityJobController',
);
});

context('when a cron job is disabled', function () {
it('unschedule the job', async function () {
//given
sinon.stub(config.cpf.sendEmailJob, 'cron').value('0 21 * * *');
sinon.stub(config.pgBoss, 'exportSenderJobEnabled').value(false);

await registerJobs({
jobGroup: JobGroup.DEFAULT,
dependencies: {
startPgBoss: startPgBossStub,
createJobQueues: createJobQueuesStub,
},
});

// then
expect(jobQueueStub.unscheduleCronJob).to.have.been.calledWithExactly('CpfExportSenderJob');
});
});
});
});
6 changes: 6 additions & 0 deletions api/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ export async function registerJobs({ jobGroup, dependencies = { startPgBoss, cre
}
} else {
logger.warn(`Job "${job.jobName}" is disabled.`);

// For cronJob we need to unschedule older cron
if (job.jobCron) {
await jobQueues.unscheduleCronJob(job.jobName);
logger.info(`Job CRON "${job.jobName}" is unscheduled.`);
}
}
}

Expand Down

0 comments on commit 565c4e3

Please sign in to comment.