Skip to content

Commit

Permalink
[FEATURE] Script d'ajout d'un document legal (PIX-15582)
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Dec 13, 2024
2 parents 9c35049 + 8222a3a commit c2b2b41
Show file tree
Hide file tree
Showing 19 changed files with 387 additions and 28 deletions.
51 changes: 51 additions & 0 deletions api/scripts/legal-documents/add-new-legal-document-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'dotenv/config';

import { usecases } from '../../src/legal-documents/domain/usecases/index.js';
import { isoDateParser } from '../../src/shared/application/scripts/parsers.js';
import { Script } from '../../src/shared/application/scripts/script.js';
import { ScriptRunner } from '../../src/shared/application/scripts/script-runner.js';

export class AddNewLegalDocumentVersion extends Script {
constructor() {
super({
description: 'Adds a new legal document version.',
permanent: true,
options: {
type: {
type: 'string',
describe: 'Type of document (ex: "TOS", "PDP")',
demandOption: true,
requiresArg: true,
},
service: {
type: 'string',
describe: 'Associated service (ex: "pix-app", "pix-orga",...)',
demandOption: true,
requiresArg: true,
},
versionAt: {
type: 'string',
describe: 'Version date of the legal document, format "YYYY-MM-DD", (ex: "2020-02-27")',
demandOption: true,
requiresArg: true,
coerce: isoDateParser(),
},
},
});
}

async handle({ options, logger }) {
let { type, service } = options;
const { versionAt } = options;

type = type.trim();
service = service.trim();

logger.info(`Adding new legal document for type:${type}, service:${service}, versionAt:${versionAt}`);

await usecases.createLegalDocument({ type, service, versionAt });
logger.info(`New legal document for type:${type}, service:${service}, versionAt:${versionAt} added successfully.`);
}
}

await ScriptRunner.execute(import.meta.url, AddNewLegalDocumentVersion);
13 changes: 13 additions & 0 deletions api/src/legal-documents/domain/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DomainError } from '../../shared/domain/errors.js';

class LegalDocumentInvalidDateError extends DomainError {
constructor({
code = 'LEGAL_DOCUMENT_INVALID_DATE',
message = 'Document version must not be before or equal to same document type and service',
} = {}) {
super(message);
this.code = code;
}
}

export { LegalDocumentInvalidDateError };
10 changes: 0 additions & 10 deletions api/src/legal-documents/domain/models/LegalDocument.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
export class LegalDocument {
static TYPES = {
TOS: 'TOS',
};

static SERVICES = {
PIX_APP: 'pix-app',
PIX_ORGA: 'pix-orga',
PIX_CERTIF: 'pix-certif',
};

constructor({ id, type, service, versionAt }) {
this.id = id;
this.type = type;
Expand Down
13 changes: 13 additions & 0 deletions api/src/legal-documents/domain/models/LegalDocumentService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Joi from 'joi';

const VALUES = {
PIX_APP: 'pix-app',
PIX_ORGA: 'pix-orga',
PIX_CERTIF: 'pix-certif',
};

const assert = (value) => {
Joi.assert(value, Joi.string().valid(...Object.values(VALUES)));
};

export const LegalDocumentService = { VALUES, assert };
11 changes: 11 additions & 0 deletions api/src/legal-documents/domain/models/LegalDocumentType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Joi from 'joi';

const VALUES = {
TOS: 'TOS',
};

const assert = (value) => {
Joi.assert(value, Joi.string().valid(...Object.values(VALUES)));
};

export const LegalDocumentType = { VALUES, assert };
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { LegalDocument } from '../models/LegalDocument.js';
import { LegalDocumentService } from '../models/LegalDocumentService.js';
import { LegalDocumentType } from '../models/LegalDocumentType.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;
const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA } = LegalDocumentService.VALUES;

/**
* Accepts a legal document by user ID.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { LegalDocumentInvalidDateError } from '../errors.js';
import { LegalDocumentService } from '../models/LegalDocumentService.js';
import { LegalDocumentType } from '../models/LegalDocumentType.js';

/**
* Creates a new legal document.
*
* @param {Object} params - The parameters.
* @param {string} params.type - The type of the legal document.
* @param {string} params.service - The service of the legal document.
* @param {string} params.versionAt - Version date of the new legal document.
* @returns {Promise<LegalDocument>} A promise that resolves the new legal document.
*/
const createLegalDocument = async ({ type, service, versionAt, legalDocumentRepository }) => {
LegalDocumentType.assert(type);
LegalDocumentService.assert(service);

const lastDocument = await legalDocumentRepository.findLastVersionByTypeAndService({ type, service });

if (lastDocument && lastDocument.versionAt >= versionAt) {
throw new LegalDocumentInvalidDateError();
}

return legalDocumentRepository.create({ type, service, versionAt });
};

export { createLegalDocument };
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { LegalDocument } from '../../domain/models/LegalDocument.js';

const TABLE_NAME = 'legal-document-versions';

/**
* Retrieves the latest version of a legal document by type and service.
*
Expand All @@ -11,7 +13,7 @@ import { LegalDocument } from '../../domain/models/LegalDocument.js';
*/
const findLastVersionByTypeAndService = async ({ type, service }) => {
const knexConnection = DomainTransaction.getConnection();
const documentVersionDto = await knexConnection('legal-document-versions')
const documentVersionDto = await knexConnection(TABLE_NAME)
.where({ type, service })
.orderBy('versionAt', 'desc')
.first();
Expand All @@ -21,4 +23,21 @@ const findLastVersionByTypeAndService = async ({ type, service }) => {
return new LegalDocument(documentVersionDto);
};

export { findLastVersionByTypeAndService };
/**
* Creates a new legal document in the database.
*
* @param {Object} params - The parameters.
* @param {string} params.type - The type of the legal document.
* @param {string} params.service - The service associated with the legal document.
* @param {Date} params.versionAt - The date of the legal document version.
* @returns {Promise<LegalDocument>} The newly created legal document.
*/
const create = async ({ type, service, versionAt }) => {
const knexConnection = DomainTransaction.getConnection();

const [documentVersionDto] = await knexConnection(TABLE_NAME).insert({ type, service, versionAt }).returning('*');

return new LegalDocument(documentVersionDto);
};

export { create, findLastVersionByTypeAndService };
18 changes: 18 additions & 0 deletions api/src/shared/application/scripts/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,21 @@ export function commaSeparatedNumberParser(separator = ',') {
return Joi.attempt(data, Joi.array().items(Joi.number()));
};
}

/**
* Create a parser for date strings in the format "YYYY-MM-DD"
* @returns {Date}
*/
export function isoDateParser() {
return (date) => {
const schema = Joi.string()
.pattern(/^\d{4}-\d{2}-\d{2}$/)
.message('Invalid date format. Expected "YYYY-MM-DD".');

const { error, value: validatedDate } = schema.validate(date);
if (error) {
throw new Error(error.message);
}
return new Date(validatedDate);
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as legalDocumentsApi from '../../../../../src/legal-documents/application/api/legal-documents-api.js';
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import { LegalDocumentService } from '../../../../../src/legal-documents/domain/models/LegalDocumentService.js';
import { LegalDocumentType } from '../../../../../src/legal-documents/domain/models/LegalDocumentType.js';
import { databaseBuilder, expect, knex } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;
const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA } = LegalDocumentService.VALUES;

describe('Integration | Privacy | Application | Api | legal documents', function () {
describe('#acceptLegalDocumentByUserId', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import { LegalDocumentService } from '../../../../../src/legal-documents/domain/models/LegalDocumentService.js';
import { LegalDocumentType } from '../../../../../src/legal-documents/domain/models/LegalDocumentType.js';
import { usecases } from '../../../../../src/legal-documents/domain/usecases/index.js';
import { databaseBuilder, expect, knex, sinon } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;
const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA } = LegalDocumentService.VALUES;

describe('Integration | Legal documents | Domain | Use case | accept-legal-document-by-user-id', function () {
it('accepts the lastest legal document version for a user', async function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { LegalDocumentInvalidDateError } from '../../../../../src/legal-documents/domain/errors.js';
import { LegalDocumentService } from '../../../../../src/legal-documents/domain/models/LegalDocumentService.js';
import { LegalDocumentType } from '../../../../../src/legal-documents/domain/models/LegalDocumentType.js';
import { usecases } from '../../../../../src/legal-documents/domain/usecases/index.js';
import { catchErr, databaseBuilder, domainBuilder, expect } from '../../../../test-helper.js';

const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA } = LegalDocumentService.VALUES;

describe('Integration | Legal documents | Domain | Use case | create-legal-document', function () {
it('creates a new legal document when there is no previous version', async function () {
// given
const type = TOS;
const service = PIX_ORGA;
const versionAt = new Date('2024-12-01');
const expectedDocument = domainBuilder.buildLegalDocument({ type, service, versionAt });

// when
const document = await usecases.createLegalDocument({ type, service, versionAt });

// then
expect(document).to.deepEqualInstanceOmitting(expectedDocument, 'id');
});

context('when a previous version exists', function () {
it('throws an error if the new version date is before or equal to the existing version date', async function () {
// given
const type = TOS;
const service = PIX_ORGA;
const existingVersionAt = new Date('2024-12-01');
const newVersionAt = new Date('2024-11-30');

databaseBuilder.factory.buildLegalDocumentVersion({ type, service, versionAt: existingVersionAt });
await databaseBuilder.commit();

// when
const error = await catchErr(usecases.createLegalDocument)({ type, service, versionAt: newVersionAt });

//then
expect(error).to.be.instanceOf(LegalDocumentInvalidDateError);
expect(error.message).to.be.equal(
'Document version must not be before or equal to same document type and service',
);
});

it('creates a new document if the new version date is after the existing version date', async function () {
// given
const type = TOS;
const service = PIX_ORGA;
const existingVersionAt = new Date('2024-12-01');
const newVersionAt = new Date('2024-12-02');
const expectedDocument = domainBuilder.buildLegalDocument({ type, service, versionAt: newVersionAt });

databaseBuilder.factory.buildLegalDocumentVersion({ type, service, versionAt: existingVersionAt });
await databaseBuilder.commit();

// when
const document = await usecases.createLegalDocument({ type, service, versionAt: newVersionAt });

// then
expect(document).to.deepEqualInstanceOmitting(expectedDocument, 'id');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import { LegalDocumentService } from '../../../../../src/legal-documents/domain/models/LegalDocumentService.js';
import { LegalDocumentType } from '../../../../../src/legal-documents/domain/models/LegalDocumentType.js';
import * as legalDocumentRepository from '../../../../../src/legal-documents/infrastructure/repositories/legal-document.repository.js';
import { databaseBuilder, domainBuilder, expect } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA, PIX_APP } = LegalDocument.SERVICES;
const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA, PIX_APP } = LegalDocumentService.VALUES;

describe('Integration | Legal document | Infrastructure | Repository | legal-document', function () {
describe('#findLastVersionByTypeAndService', function () {
Expand Down Expand Up @@ -47,4 +49,20 @@ describe('Integration | Legal document | Infrastructure | Repository | legal-doc
expect(lastDocument).to.be.null;
});
});

describe('#create', function () {
it('creates a new legal document in the database', async function () {
// given
const type = TOS;
const service = PIX_ORGA;
const versionAt = new Date('2024-12-01');

// when
const createdDocument = await legalDocumentRepository.create({ type, service, versionAt });

// then
expect(createdDocument).to.be.instanceOf(LegalDocument);
expect(createdDocument).to.deep.include({ type, service, versionAt });
});
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import { LegalDocumentService } from '../../../../../src/legal-documents/domain/models/LegalDocumentService.js';
import { LegalDocumentType } from '../../../../../src/legal-documents/domain/models/LegalDocumentType.js';
import * as userAcceptanceRepository from '../../../../../src/legal-documents/infrastructure/repositories/user-acceptance.repository.js';
import { databaseBuilder, expect, knex } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;
const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA } = LegalDocumentService.VALUES;

describe('Integration | Legal document | Infrastructure | Repository | user-acceptance', function () {
describe('#create', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Joi from 'joi';

import { LegalDocumentService } from '../../../../../src/legal-documents/domain/models/LegalDocumentService.js';
import { expect } from '../../../../test-helper.js';

describe('Unit | Legal documents | Domain | Model | LegalDocumentService', function () {
describe('VALUES', function () {
it('has correct values', function () {
expect(LegalDocumentService.VALUES.PIX_APP).to.equal('pix-app');
expect(LegalDocumentService.VALUES.PIX_ORGA).to.equal('pix-orga');
expect(LegalDocumentService.VALUES.PIX_CERTIF).to.equal('pix-certif');
});
});

describe('#assert', function () {
it('does not throw an error for valid values', function () {
expect(() => LegalDocumentService.assert('pix-app')).to.not.throw();
expect(() => LegalDocumentService.assert('pix-orga')).to.not.throw();
expect(() => LegalDocumentService.assert('pix-certif')).to.not.throw();
});

it('throws an error for invalid values', function () {
expect(() => LegalDocumentService.assert('invalid-value')).to.throw(Joi.ValidationError);
});
});
});
Loading

0 comments on commit c2b2b41

Please sign in to comment.