Skip to content

Commit

Permalink
feat(api): create a job to log in audit logger
Browse files Browse the repository at this point in the history
  • Loading branch information
bpetetot committed Sep 19, 2024
1 parent ab9c8ff commit 6c53569
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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,
});
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
Original file line number Diff line number Diff line change
@@ -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('setup 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,
});
});
});
Original file line number Diff line number Diff line change
@@ -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' },
]);
}
});
});
});
});
Original file line number Diff line number Diff line change
@@ -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('setup the job repository configuration', async function () {
expect(eventLoggingJobRepository.name).to.equal(EventLoggingJob.name);
expect(eventLoggingJobRepository.retry).to.equal(JobRetry.STANDARD_RETRY);
});
});

0 comments on commit 6c53569

Please sign in to comment.