From b5ecb9f1a5163e25b34eaeca059d85347861deba Mon Sep 17 00:00:00 2001 From: Benjamin Petetot Date: Wed, 18 Sep 2024 11:42:52 +0200 Subject: [PATCH 1/3] feat(api): create a job to log in audit logger --- .../jobs/event-logging.job-controller.js | 23 ++++++ .../domain/models/jobs/EventLoggingJob.js | 42 ++++++++++ .../jobs/event-logging-job.repository.js | 13 +++ .../jobs/event-logging.job-controller.test.js | 40 ++++++++++ .../models/jobs/EventLoggingJob.test.js | 79 +++++++++++++++++++ .../jobs/event-logging-job.repository.test.js | 11 +++ 6 files changed, 208 insertions(+) create mode 100644 api/src/identity-access-management/application/jobs/event-logging.job-controller.js create mode 100644 api/src/identity-access-management/domain/models/jobs/EventLoggingJob.js create mode 100644 api/src/identity-access-management/infrastructure/repositories/jobs/event-logging-job.repository.js create mode 100644 api/tests/identity-access-management/unit/application/jobs/event-logging.job-controller.test.js create mode 100644 api/tests/identity-access-management/unit/domain/models/jobs/EventLoggingJob.test.js create mode 100644 api/tests/identity-access-management/unit/infrastructure/repositories/jobs/event-logging-job.repository.test.js diff --git a/api/src/identity-access-management/application/jobs/event-logging.job-controller.js b/api/src/identity-access-management/application/jobs/event-logging.job-controller.js new file mode 100644 index 00000000000..f30dfeca547 --- /dev/null +++ b/api/src/identity-access-management/application/jobs/event-logging.job-controller.js @@ -0,0 +1,23 @@ +import { auditLoggerRepository } from '../../../../lib/infrastructure/repositories/audit-logger-repository.js'; +import { JobController } from '../../../shared/application/jobs/job-controller.js'; +import { EventLoggingJob } from '../../domain/models/jobs/EventLoggingJob.js'; + +export class EventLoggingJobController extends JobController { + constructor() { + super(EventLoggingJob.name); + } + + async handle({ data: jobData, dependencies = { auditLoggerRepository } }) { + const { client, action, role, userId, targetUserId, data, occurredAt } = jobData; + + return dependencies.auditLoggerRepository.logEvent({ + client, + action, + role, + userId: userId.toString(), + targetUserId: targetUserId.toString(), + data, + occurredAt, + }); + } +} diff --git a/api/src/identity-access-management/domain/models/jobs/EventLoggingJob.js b/api/src/identity-access-management/domain/models/jobs/EventLoggingJob.js new file mode 100644 index 00000000000..79764ff8417 --- /dev/null +++ b/api/src/identity-access-management/domain/models/jobs/EventLoggingJob.js @@ -0,0 +1,42 @@ +import Joi from 'joi'; + +import { EntityValidationError } from '../../../../shared/domain/errors.js'; + +const CLIENTS = ['PIX_ADMIN', 'PIX_APP']; +const ACTIONS = ['ANONYMIZATION', 'ANONYMIZATION_GAR', 'EMAIL_CHANGED']; +const ROLES = ['SUPER_ADMIN', 'SUPPORT', 'USER']; + +const EventLogSchema = Joi.object({ + client: Joi.string() + .valid(...CLIENTS) + .required(), + action: Joi.string() + .valid(...ACTIONS) + .required(), + role: Joi.string() + .valid(...ROLES) + .required(), + userId: Joi.number().required(), + targetUserId: Joi.number().required(), + data: Joi.object().optional(), + occurredAt: Joi.date().optional(), +}); + +export class EventLoggingJob { + constructor({ client, action, role, userId, targetUserId, data, occurredAt }) { + this.client = client; + this.action = action; + this.role = role; + this.userId = userId; + this.targetUserId = targetUserId; + this.data = data; + this.occurredAt = occurredAt || new Date(); + + this.#validate(); + } + + #validate() { + const { error } = EventLogSchema.validate(this, { abortEarly: false }); + if (error) throw EntityValidationError.fromJoiErrors(error.details); + } +} diff --git a/api/src/identity-access-management/infrastructure/repositories/jobs/event-logging-job.repository.js b/api/src/identity-access-management/infrastructure/repositories/jobs/event-logging-job.repository.js new file mode 100644 index 00000000000..fba2628f6fe --- /dev/null +++ b/api/src/identity-access-management/infrastructure/repositories/jobs/event-logging-job.repository.js @@ -0,0 +1,13 @@ +import { JobRepository, JobRetry } from '../../../../shared/infrastructure/repositories/jobs/job-repository.js'; +import { EventLoggingJob } from '../../../domain/models/jobs/EventLoggingJob.js'; + +class EventLoggingJobRepository extends JobRepository { + constructor() { + super({ + name: EventLoggingJob.name, + retry: JobRetry.STANDARD_RETRY, + }); + } +} + +export const eventLoggingJobRepository = new EventLoggingJobRepository(); diff --git a/api/tests/identity-access-management/unit/application/jobs/event-logging.job-controller.test.js b/api/tests/identity-access-management/unit/application/jobs/event-logging.job-controller.test.js new file mode 100644 index 00000000000..5ef3a83d9e1 --- /dev/null +++ b/api/tests/identity-access-management/unit/application/jobs/event-logging.job-controller.test.js @@ -0,0 +1,40 @@ +import { EventLoggingJobController } from '../../../../../src/identity-access-management/application/jobs/event-logging.job-controller.js'; +import { EventLoggingJob } from '../../../../../src/identity-access-management/domain/models/jobs/EventLoggingJob.js'; +import { expect, sinon } from '../../../../test-helper.js'; + +describe('Unit | Identity Access Management | Application | Jobs | EventLoggingJobController', function () { + it('sets up the job controller configuration', async function () { + const jobController = new EventLoggingJobController(); + expect(jobController.jobName).to.equal(EventLoggingJob.name); + }); + + it('logs the event', async function () { + // given + const auditLoggerRepository = { logEvent: sinon.stub() }; + const options = { dependencies: { auditLoggerRepository } }; + const data = { + client: 'PIX_APP', + action: 'EMAIL_CHANGED', + role: 'USER', + userId: 123, + targetUserId: 456, + data: { foo: 'bar' }, + occurredAt: new Date(), + }; + + // when + const jobController = new EventLoggingJobController(); + await jobController.handle({ data, ...options }); + + // then + expect(auditLoggerRepository.logEvent).to.have.been.calledWith({ + client: data.client, + action: data.action, + role: data.role, + userId: data.userId.toString(), + targetUserId: data.targetUserId.toString(), + data: data.data, + occurredAt: data.occurredAt, + }); + }); +}); diff --git a/api/tests/identity-access-management/unit/domain/models/jobs/EventLoggingJob.test.js b/api/tests/identity-access-management/unit/domain/models/jobs/EventLoggingJob.test.js new file mode 100644 index 00000000000..356d803d121 --- /dev/null +++ b/api/tests/identity-access-management/unit/domain/models/jobs/EventLoggingJob.test.js @@ -0,0 +1,79 @@ +import { assert } from 'chai'; + +import { EventLoggingJob } from '../../../../../../src/identity-access-management/domain/models/jobs/EventLoggingJob.js'; +import { EntityValidationError } from '../../../../../../src/shared/domain/errors.js'; +import { expect, sinon } from '../../../../../test-helper.js'; + +describe('Unit | Identity Access Management | Domain | Model | Jobs | EventLoggingJob', function () { + const now = new Date(2024, 1, 1); + let clock; + + beforeEach(function () { + clock = sinon.useFakeTimers({ now, toFake: ['Date'] }); + }); + + afterEach(function () { + clock.restore(); + }); + + describe('#constructor', function () { + it('creates an EventLoggingJob', async function () { + // when + const eventLoggingJob = new EventLoggingJob({ + client: 'PIX_APP', + action: 'EMAIL_CHANGED', + role: 'USER', + userId: 123, + targetUserId: 456, + data: { foo: 'bar' }, + occurredAt: new Date(), + }); + + // then + expect(eventLoggingJob.client).to.equal('PIX_APP'); + expect(eventLoggingJob.action).to.equal('EMAIL_CHANGED'); + expect(eventLoggingJob.role).to.equal('USER'); + expect(eventLoggingJob.userId).to.equal(123); + expect(eventLoggingJob.targetUserId).to.equal(456); + expect(eventLoggingJob.data).to.deep.equal({ foo: 'bar' }); + expect(eventLoggingJob.occurredAt).to.deep.equal(now); + }); + + context('when occurredAt is not defined', function () { + it('set a default date for occurredAt', function () { + // when + const eventLoggingJob = new EventLoggingJob({ + client: 'PIX_APP', + action: 'EMAIL_CHANGED', + role: 'USER', + userId: 123, + targetUserId: 456, + }); + + // then + expect(eventLoggingJob.targetUserId).to.equal(456); + expect(eventLoggingJob.occurredAt).to.deep.equal(now); + }); + }); + + context('when required fields are missing', function () { + it('throws an entity validation error', function () { + try { + // when + new EventLoggingJob({}); + assert.fail(); + } catch (error) { + // then + expect(error).to.be.instanceOf(EntityValidationError); + expect(error.invalidAttributes).to.deep.equal([ + { attribute: 'client', message: '"client" is required' }, + { attribute: 'action', message: '"action" is required' }, + { attribute: 'role', message: '"role" is required' }, + { attribute: 'userId', message: '"userId" is required' }, + { attribute: 'targetUserId', message: '"targetUserId" is required' }, + ]); + } + }); + }); + }); +}); diff --git a/api/tests/identity-access-management/unit/infrastructure/repositories/jobs/event-logging-job.repository.test.js b/api/tests/identity-access-management/unit/infrastructure/repositories/jobs/event-logging-job.repository.test.js new file mode 100644 index 00000000000..07680f5e8f2 --- /dev/null +++ b/api/tests/identity-access-management/unit/infrastructure/repositories/jobs/event-logging-job.repository.test.js @@ -0,0 +1,11 @@ +import { EventLoggingJob } from '../../../../../../src/identity-access-management/domain/models/jobs/EventLoggingJob.js'; +import { eventLoggingJobRepository } from '../../../../../../src/identity-access-management/infrastructure/repositories/jobs/event-logging-job.repository.js'; +import { JobRetry } from '../../../../../../src/shared/infrastructure/repositories/jobs/job-repository.js'; +import { expect } from '../../../../../test-helper.js'; + +describe('Unit | Identity Access Management | Infrastructure | Jobs | EventLoggingJobRepository', function () { + it('sets up the job repository configuration', async function () { + expect(eventLoggingJobRepository.name).to.equal(EventLoggingJob.name); + expect(eventLoggingJobRepository.retry).to.equal(JobRetry.STANDARD_RETRY); + }); +}); From 082bb5665e237f7c75f27c85ab23da5e86c783f5 Mon Sep 17 00:00:00 2001 From: Benjamin Petetot Date: Wed, 18 Sep 2024 15:12:44 +0200 Subject: [PATCH 2/3] refactor(api): log email change in audit logger --- .../domain/usecases/index.js | 4 +- ...date-user-email-with-validation.usecase.js | 21 +- ...user-email-with-validation.usecase.test.js | 143 ++++++++++++++ ...user-email-with-validation.usecase.test.js | 180 ------------------ 4 files changed, 166 insertions(+), 182 deletions(-) create mode 100644 api/tests/identity-access-management/integration/domain/usecases/update-user-email-with-validation.usecase.test.js delete mode 100644 api/tests/identity-access-management/unit/domain/usecases/update-user-email-with-validation.usecase.test.js diff --git a/api/src/identity-access-management/domain/usecases/index.js b/api/src/identity-access-management/domain/usecases/index.js index 2d05633b486..38cd89a6812 100644 --- a/api/src/identity-access-management/domain/usecases/index.js +++ b/api/src/identity-access-management/domain/usecases/index.js @@ -24,6 +24,7 @@ import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/ import { accountRecoveryDemandRepository } from '../../infrastructure/repositories/account-recovery-demand.repository.js'; import * as authenticationMethodRepository from '../../infrastructure/repositories/authentication-method.repository.js'; import { emailValidationDemandRepository } from '../../infrastructure/repositories/email-validation-demand.repository.js'; +import { eventLoggingJobRepository } from '../../infrastructure/repositories/jobs/event-logging-job.repository.js'; import { garAnonymizedBatchEventsLoggingJobRepository } from '../../infrastructure/repositories/jobs/gar-anonymized-batch-events-logging-job-repository.js'; import { oidcProviderRepository } from '../../infrastructure/repositories/oidc-provider-repository.js'; import { refreshTokenRepository } from '../../infrastructure/repositories/refresh-token.repository.js'; @@ -45,8 +46,9 @@ const repositories = { authenticationMethodRepository, campaignParticipationRepository, campaignRepository, - emailValidationDemandRepository, campaignToJoinRepository: campaignRepositories.campaignToJoinRepository, + emailValidationDemandRepository, + eventLoggingJobRepository, oidcProviderRepository, organizationLearnerRepository, refreshTokenRepository, diff --git a/api/src/identity-access-management/domain/usecases/update-user-email-with-validation.usecase.js b/api/src/identity-access-management/domain/usecases/update-user-email-with-validation.usecase.js index 275fc14cba7..a6ad3b63d6a 100644 --- a/api/src/identity-access-management/domain/usecases/update-user-email-with-validation.usecase.js +++ b/api/src/identity-access-management/domain/usecases/update-user-email-with-validation.usecase.js @@ -3,8 +3,15 @@ import { InvalidVerificationCodeError, UserNotAuthorizedToUpdateEmailError, } from '../../../shared/domain/errors.js'; +import { EventLoggingJob } from '../models/jobs/EventLoggingJob.js'; -const updateUserEmailWithValidation = async function ({ code, userId, userEmailRepository, userRepository }) { +const updateUserEmailWithValidation = async function ({ + code, + userId, + userEmailRepository, + userRepository, + eventLoggingJobRepository, +}) { const user = await userRepository.get(userId); if (!user.email) { throw new UserNotAuthorizedToUpdateEmailError(); @@ -29,6 +36,18 @@ const updateUserEmailWithValidation = async function ({ code, userId, userEmailR }, }); + // Currently only used in Pix App, which is why app name is hard-coded for the audit log. + await eventLoggingJobRepository.performAsync( + new EventLoggingJob({ + client: 'PIX_APP', + action: 'EMAIL_CHANGED', + role: 'USER', + userId: user.id, + targetUserId: user.id, + data: { oldEmail: user.email, newEmail: emailModificationDemand.newEmail }, + }), + ); + return { email: emailModificationDemand.newEmail }; }; diff --git a/api/tests/identity-access-management/integration/domain/usecases/update-user-email-with-validation.usecase.test.js b/api/tests/identity-access-management/integration/domain/usecases/update-user-email-with-validation.usecase.test.js new file mode 100644 index 00000000000..8788b024e71 --- /dev/null +++ b/api/tests/identity-access-management/integration/domain/usecases/update-user-email-with-validation.usecase.test.js @@ -0,0 +1,143 @@ +import { expect } from 'chai'; + +import { EventLoggingJob } from '../../../../../src/identity-access-management/domain/models/jobs/EventLoggingJob.js'; +import { usecases } from '../../../../../src/identity-access-management/domain/usecases/index.js'; +import { userEmailRepository } from '../../../../../src/identity-access-management/infrastructure/repositories/user-email.repository.js'; +import { + AlreadyRegisteredEmailError, + EmailModificationDemandNotFoundOrExpiredError, + InvalidVerificationCodeError, + UserNotAuthorizedToUpdateEmailError, +} from '../../../../../src/shared/domain/errors.js'; +import { temporaryStorage } from '../../../../../src/shared/infrastructure/temporary-storage/index.js'; +import { catchErr, databaseBuilder, knex, sinon } from '../../../../test-helper.js'; + +const verifyEmailTemporaryStorage = temporaryStorage.withPrefix('verify-email:'); + +describe('Integration | Identity Access Management | Domain | UseCase | updateUserEmailWithValidation', function () { + let clock; + const now = new Date('2024-12-25'); + + beforeEach(function () { + verifyEmailTemporaryStorage.flushAll(); + clock = sinon.useFakeTimers({ now, toFake: ['Date'] }); + }); + + afterEach(async function () { + clock.restore(); + }); + + it('updates the user email checking the verification code', async function () { + // given + const code = 123; + const newEmail = 'new.email@example.net'; + const user = databaseBuilder.factory.buildUser({ email: 'email@example.net' }); + await databaseBuilder.commit(); + await userEmailRepository.saveEmailModificationDemand({ userId: user.id, code, newEmail }); + + // when + const result = await usecases.updateUserEmailWithValidation({ userId: user.id, code, newEmail }); + + // then + expect(result.email).to.equal(newEmail); + + const updatedUser = await knex('users').where({ id: user.id }).first(); + expect(updatedUser.email).to.equal(newEmail); + expect(updatedUser.emailConfirmedAt).to.not.be.null; + + await expect(EventLoggingJob.name).to.have.been.performed.withJobPayload({ + client: 'PIX_APP', + action: 'EMAIL_CHANGED', + role: 'USER', + userId: user.id, + targetUserId: user.id, + data: { oldEmail: 'email@example.net', newEmail: 'new.email@example.net' }, + occurredAt: '2024-12-25T00:00:00.000Z', + }); + }); + + context('when the verification code is invalid', function () { + it('throws an error', async function () { + // given + const code = 123; + const invalidCode = 456; + const newEmail = 'new.email@example.net'; + const user = databaseBuilder.factory.buildUser({ email: 'email@example.net' }); + await databaseBuilder.commit(); + await userEmailRepository.saveEmailModificationDemand({ userId: user.id, code, newEmail }); + + // when + const error = await catchErr(usecases.updateUserEmailWithValidation)({ + userId: user.id, + code: invalidCode, + newEmail, + }); + + // then + expect(error).to.be.instanceOf(InvalidVerificationCodeError); + }); + }); + + context('when the email modification demand is not found or expired', function () { + it('throws an error', async function () { + // given + const code = 123; + const newEmail = 'new.email@example.net'; + const user = databaseBuilder.factory.buildUser({ email: 'email@example.net' }); + await databaseBuilder.commit(); + + // when + const error = await catchErr(usecases.updateUserEmailWithValidation)({ + userId: user.id, + code, + newEmail, + }); + + // then + expect(error).to.be.instanceOf(EmailModificationDemandNotFoundOrExpiredError); + }); + }); + + context('when the user has no email', function () { + it('throws an error', async function () { + // given + const code = 123; + const newEmail = 'new.email@example.net'; + const user = databaseBuilder.factory.buildUser({ email: null }); + await databaseBuilder.commit(); + await userEmailRepository.saveEmailModificationDemand({ userId: user.id, code, newEmail }); + + // when + const error = await catchErr(usecases.updateUserEmailWithValidation)({ + userId: user.id, + code, + newEmail, + }); + + // then + expect(error).to.be.instanceOf(UserNotAuthorizedToUpdateEmailError); + }); + }); + + context('when the new email is already registered for an other user', function () { + it('throws an error', async function () { + // given + const code = 123; + const alreadyExistEmail = 'already.exist.email@example.net'; + const user = databaseBuilder.factory.buildUser({ email: 'email@example.net' }); + databaseBuilder.factory.buildUser({ email: alreadyExistEmail }); + await databaseBuilder.commit(); + await userEmailRepository.saveEmailModificationDemand({ userId: user.id, code, newEmail: alreadyExistEmail }); + + // when + const error = await catchErr(usecases.updateUserEmailWithValidation)({ + userId: user.id, + code, + newEmail: alreadyExistEmail, + }); + + // then + expect(error).to.be.instanceOf(AlreadyRegisteredEmailError); + }); + }); +}); diff --git a/api/tests/identity-access-management/unit/domain/usecases/update-user-email-with-validation.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/update-user-email-with-validation.usecase.test.js deleted file mode 100644 index 487c810cd19..00000000000 --- a/api/tests/identity-access-management/unit/domain/usecases/update-user-email-with-validation.usecase.test.js +++ /dev/null @@ -1,180 +0,0 @@ -import { EmailModificationDemand } from '../../../../../src/identity-access-management/domain/models/EmailModificationDemand.js'; -import { updateUserEmailWithValidation } from '../../../../../src/identity-access-management/domain/usecases/update-user-email-with-validation.usecase.js'; -import { - AlreadyRegisteredEmailError, - EmailModificationDemandNotFoundOrExpiredError, - InvalidVerificationCodeError, -} from '../../../../../src/shared/domain/errors.js'; -import { UserNotAuthorizedToUpdateEmailError } from '../../../../../src/shared/domain/errors.js'; -import { catchErr, domainBuilder, expect, sinon } from '../../../../test-helper.js'; - -describe('Unit | Identity Access Management | Domain | UseCase | update-user-email-with-validation', function () { - let userEmailRepository; - let userRepository; - let clock; - - beforeEach(function () { - userEmailRepository = { - getEmailModificationDemandByUserId: sinon.stub(), - }; - userRepository = { - checkIfEmailIsAvailable: sinon.stub(), - get: sinon.stub(), - updateWithEmailConfirmed: sinon.stub(), - }; - }); - - it('should update email and set date for confirmed email', async function () { - // given - const userId = domainBuilder.buildUser().id; - const email = 'oldEmail@example.net'; - const newEmail = 'new_email@example.net'; - const code = '999999'; - const emailModificationDemand = new EmailModificationDemand({ - code, - newEmail, - }); - - const now = new Date(); - clock = sinon.useFakeTimers({ now, toFake: ['Date'] }); - - userRepository.get.withArgs(userId).resolves({ email }); - userEmailRepository.getEmailModificationDemandByUserId.withArgs(userId).resolves(emailModificationDemand); - userRepository.checkIfEmailIsAvailable.withArgs(newEmail).resolves(); - - // when - await updateUserEmailWithValidation({ - userId, - code, - userEmailRepository, - userRepository, - }); - - // then - expect(userRepository.updateWithEmailConfirmed).to.have.been.calledWithExactly({ - id: userId, - userAttributes: { email: newEmail, emailConfirmedAt: now }, - }); - clock.restore(); - }); - - it('should get email modification demand in temporary storage', async function () { - // given - const userId = domainBuilder.buildUser().id; - const email = 'oldEmail@example.net'; - const newEmail = 'new_email@example.net'; - const code = '999999'; - const emailModificationDemand = new EmailModificationDemand({ - code, - newEmail, - }); - - userRepository.get.withArgs(userId).resolves({ email }); - userEmailRepository.getEmailModificationDemandByUserId.withArgs(userId).resolves(emailModificationDemand); - - // when - await updateUserEmailWithValidation({ - userId, - code, - userEmailRepository, - userRepository, - }); - - // then - expect(userEmailRepository.getEmailModificationDemandByUserId).to.have.been.calledWithExactly(userId); - }); - - it('should throw UserNotAuthorizedToUpdateEmailError if user does not have an email', async function () { - // given - userRepository.get.resolves({}); - const userId = 1; - const code = '999999'; - - // when - const error = await catchErr(updateUserEmailWithValidation)({ - userId, - code, - userRepository, - userEmailRepository, - }); - - // then - expect(error).to.be.an.instanceOf(UserNotAuthorizedToUpdateEmailError); - }); - - it('should throw AlreadyRegisteredEmailError if email already exists', async function () { - // given - const userId = domainBuilder.buildUser().id; - const email = 'oldEmail@example.net'; - const newEmail = 'new_email@example.net'; - const code = '999999'; - const emailModificationDemand = new EmailModificationDemand({ - code, - newEmail, - }); - - userRepository.get.withArgs(userId).resolves({ email }); - userEmailRepository.getEmailModificationDemandByUserId.withArgs(userId).resolves(emailModificationDemand); - userRepository.checkIfEmailIsAvailable.withArgs(newEmail).rejects(new AlreadyRegisteredEmailError()); - - // when - const error = await catchErr(updateUserEmailWithValidation)({ - userId, - code, - userEmailRepository, - userRepository, - }); - - // then - expect(error).to.be.an.instanceOf(AlreadyRegisteredEmailError); - }); - - it('should throw InvalidVerificationCodeError if the code send does not match with then code saved in temporary storage', async function () { - // given - const userId = domainBuilder.buildUser().id; - const email = 'oldEmail@example.net'; - const newEmail = 'new_email@example.net'; - const code = '999999'; - const anotherCode = '444444'; - const emailModificationDemand = new EmailModificationDemand({ - code: anotherCode, - newEmail, - }); - - userRepository.get.withArgs(userId).resolves({ email }); - userEmailRepository.getEmailModificationDemandByUserId.withArgs(userId).resolves(emailModificationDemand); - - // when - const error = await catchErr(updateUserEmailWithValidation)({ - userId, - code, - userEmailRepository, - userRepository, - }); - - // then - expect(error).to.be.an.instanceOf(InvalidVerificationCodeError); - }); - - it('should throw EmailModificationDemandNotFoundOrExpiredError if no email modification demand match or is expired', async function () { - // given - const userId = domainBuilder.buildUser().id; - const anotherUserId = domainBuilder.buildUser().id; - const email = 'oldEmail@example.net'; - const code = '999999'; - - userRepository.get.withArgs(userId).resolves({ email }); - userEmailRepository.getEmailModificationDemandByUserId.withArgs(anotherUserId).resolves(null); - - // when - const error = await catchErr(updateUserEmailWithValidation)({ - userId, - code, - userEmailRepository, - userRepository, - }); - - // then - expect(error).to.be.an.instanceOf(EmailModificationDemandNotFoundOrExpiredError); - }); -}); From 31f825fe7a612a8545f304a07abf12434e8f37b2 Mon Sep 17 00:00:00 2001 From: Benjamin Petetot Date: Fri, 20 Sep 2024 14:26:08 +0200 Subject: [PATCH 3/3] chore(api): update js doc in JobRepository constructor --- .../infrastructure/repositories/jobs/job-repository.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/shared/infrastructure/repositories/jobs/job-repository.js b/api/src/shared/infrastructure/repositories/jobs/job-repository.js index 4630983062e..abf3c51d1ce 100644 --- a/api/src/shared/infrastructure/repositories/jobs/job-repository.js +++ b/api/src/shared/infrastructure/repositories/jobs/job-repository.js @@ -27,9 +27,10 @@ export class JobRepository { /** * @param {Object} config - * @param {valueOf} config.priority - * @param {valueOf} config.retry - * @param {valueOf} config.expireIn + * @param {string} config.name Job name + * @param {valueOf} config.priority Job prority + * @param {valueOf} config.retry Job retry strategy + * @param {valueOf} config.expireIn Job retention duration */ constructor(config) { this.name = config.name;