Skip to content

Commit e936de7

Browse files
Add additional fields to DTO and schema, and update validation service
1 parent 523ea5f commit e936de7

File tree

5 files changed

+161
-71
lines changed

5 files changed

+161
-71
lines changed

src/management/identities/_schemas/_parts/additionalFields.part.schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class AdditionalFieldsPart {
1010
attributes: Record<string, MixedValue>;
1111

1212
@Prop({ type: Object, required: false })
13-
validations: Record<string, string>;
13+
validations?: Record<string, string>;
1414
}
1515

1616
export const AdditionalFieldsPartSchema = SchemaFactory.createForClass(AdditionalFieldsPart);

src/management/identities/_stubs/_parts/addtionalFields.dto.stub.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ export const additionalFieldsPartDtoStub = (): additionalFieldsPartDto => {
55
objectClasses: ['supann'],
66
attributes: {
77
supann: {
8-
supannEmpId: 'supannEmpId',
8+
supannEmpId: '12345',
9+
supannCivilite: 'Mr',
10+
supannBirthName: 'Doe',
911
},
1012
},
1113
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
export const validSchemaStub = {
2+
$schema: 'http://json-schema.org/draft-07/schema#',
3+
type: 'object',
4+
properties: {
5+
supannEmpId: {
6+
type: 'string',
7+
description: 'Employee ID',
8+
},
9+
supannCivilite: {
10+
type: 'string',
11+
description: 'Title (Mr, Ms, etc.)',
12+
},
13+
supannBirthName: {
14+
type: 'string',
15+
description: 'Birth name',
16+
},
17+
},
18+
required: ['supannEmpId', 'supannCivilite'],
19+
};
20+
21+
export const invalidSchemaStub = {
22+
type: 'invalid',
23+
};
24+
25+
export const validAdditionalFieldsStub = () => {
26+
return {
27+
objectClasses: ['supann'],
28+
attributes: {
29+
supann: {
30+
supannEmpId: '12345',
31+
supannCivilite: 'Mr',
32+
supannBirthName: 'Doe',
33+
},
34+
},
35+
};
36+
};
37+
38+
//
39+
export const invalidRequiredAdditionalFieldsStub = () => {
40+
return {
41+
objectClasses: ['supann'],
42+
attributes: {
43+
supann: {
44+
supannEmpId: '12345',
45+
supannBirthName: 'Doe',
46+
},
47+
},
48+
};
49+
};
50+
51+
export const invalidTypeAdditionalFieldsStub = () => {
52+
return {
53+
objectClasses: ['supann'],
54+
attributes: {
55+
supann: {
56+
supannEmpId: '12345',
57+
supannCivilite: 'Mr',
58+
supannBirthName: 123,
59+
},
60+
},
61+
};
62+
};
63+
64+
export const missingAttributeAdditionalFieldsStub = () => {
65+
return {
66+
objectClasses: ['class1'],
67+
attributes: {},
68+
};
69+
};
70+
71+
export const invalidObjectClassAdditionalFieldsStub = () => {
72+
return {
73+
objectClasses: ['testClass'],
74+
attributes: { supann: { test: 'test' } },
75+
};
76+
};

src/management/identities/validations/identities.validation.service.spec.ts

Lines changed: 77 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,96 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
12
import { IdentitiesValidationService } from './identities.validation.service';
2-
import * as yup from 'yup';
3-
import fs from 'fs';
4-
import yaml from 'yaml';
3+
import * as fs from 'fs';
4+
import { ValidationConfigException, ValidationSchemaException } from '~/_common/errors/ValidationException';
5+
import {
6+
invalidObjectClassAdditionalFieldsStub,
7+
invalidRequiredAdditionalFieldsStub,
8+
invalidTypeAdditionalFieldsStub,
9+
missingAttributeAdditionalFieldsStub,
10+
validSchemaStub,
11+
validAdditionalFieldsStub,
12+
} from './_stubs/identities.validation.stub';
13+
import Ajv from 'ajv';
14+
import ajvErrors from 'ajv-errors';
515

6-
jest.mock('yup');
16+
jest.mock('fs');
717

818
describe('IdentitiesValidationService', () => {
919
let service: IdentitiesValidationService;
20+
let mockAjv: jest.Mocked<Ajv>;
21+
let mockFs: jest.Mocked<typeof fs>;
1022

11-
beforeEach(() => {
12-
jest.spyOn(fs, 'readFileSync').mockReturnValue('valid yml content');
13-
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
14-
jest.spyOn(yaml, 'parse').mockReturnValue({ attributes: [] });
15-
service = new IdentitiesValidationService();
23+
beforeAll(() => {});
24+
25+
beforeEach(async () => {
26+
const module: TestingModule = await Test.createTestingModule({
27+
providers: [IdentitiesValidationService],
28+
}).compile();
29+
30+
service = module.get<IdentitiesValidationService>(IdentitiesValidationService);
31+
32+
mockAjv = new Ajv({ allErrors: true }) as jest.Mocked<Ajv>;
33+
ajvErrors(mockAjv);
34+
35+
mockFs = fs as jest.Mocked<typeof fs>;
36+
mockFs.existsSync.mockReturnValue(true);
37+
mockFs.readFileSync.mockReturnValue(JSON.stringify(validSchemaStub));
38+
});
39+
40+
afterEach(() => {
1641
jest.clearAllMocks();
1742
});
1843

19-
it('should reject when config file is missing', async () => {
20-
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
21-
const data = { objectClasses: ['testClass'], attributes: { testClass: {} } };
22-
await expect(service.validate(data)).rejects.toMatchObject({
23-
message: 'Validation failed',
44+
describe('test Exceptions thrown', () => {
45+
describe('test ValidConfigException', () => {
46+
it('should throw ValidationConfigException for missing attributes', async () => {
47+
const data = missingAttributeAdditionalFieldsStub();
48+
await expect(service.validate(data)).rejects.toThrow(ValidationConfigException);
49+
});
50+
51+
it('should throw ValidationConfigException when object class is not found in attributes', async () => {
52+
const data = invalidObjectClassAdditionalFieldsStub();
53+
await expect(service.validate(data)).rejects.toThrow(ValidationConfigException);
54+
});
55+
56+
it('should throw ValidationConfigException for not found schema', async () => {
57+
mockFs.existsSync.mockReturnValue(false);
58+
const data = validAdditionalFieldsStub();
59+
await expect(service.validate(data)).rejects.toThrow(ValidationConfigException);
60+
});
61+
62+
it('should throw ValidationConfigException for invalid schema', async () => {
63+
mockFs.readFileSync.mockReturnValue('invalid content');
64+
const data = validAdditionalFieldsStub();
65+
await expect(service.validate(data)).rejects.toThrow(ValidationConfigException);
66+
});
2467
});
25-
});
2668

27-
it('should reject when object class is not found in attributes', async () => {
28-
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
29-
jest.spyOn(yaml, 'parse').mockReturnValue({ attributes: [] }); // Mocked schema
30-
const data = { objectClasses: ['testClass'], attributes: { supann: { test: 'test' } } };
31-
await expect(service.validate(data)).rejects.toMatchObject({
32-
message: 'Validation failed',
69+
describe('test ValidationSchemaException', () => {
70+
it('should throw ValidationSchemaException for invalid required attribute', async () => {
71+
const data = invalidRequiredAdditionalFieldsStub();
72+
await expect(service.validate(data)).rejects.toThrow(ValidationSchemaException);
73+
});
74+
75+
it('should throw ValidationSchemaException for invalid attribute type', async () => {
76+
const data = invalidTypeAdditionalFieldsStub();
77+
await expect(service.validate(data)).rejects.toThrow(ValidationSchemaException);
78+
});
3379
});
3480
});
3581

36-
it('should reject on yup schema validation error', async () => {
37-
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
38-
jest.spyOn(yaml, 'parse').mockReturnValue({ attributes: [] }); // Mocked schema
39-
const mockYupObject = { validate: jest.fn() };
40-
const mockValidationError = new yup.ValidationError('error', {}, 'test');
41-
mockValidationError.inner = [
42-
{
43-
path: 'test',
44-
errors: ['error'],
45-
value: 'test',
46-
inner: [],
47-
name: 'test',
48-
message: 'test',
49-
[Symbol.toStringTag]: 'test',
50-
},
51-
];
52-
mockValidationError.errors = ['error'];
53-
mockYupObject.validate.mockRejectedValue(mockValidationError);
54-
service.createSchema = jest.fn().mockResolvedValue(mockYupObject);
55-
56-
const data = { objectClasses: ['testClass'], attributes: { testClass: {} } };
57-
await expect(service.validate(data)).rejects.toMatchObject({
58-
message: 'Validation failed',
82+
describe('validation success', () => {
83+
it('should validate additional fields successfully', async () => {
84+
const data = validAdditionalFieldsStub();
85+
await expect(service.validate(data)).resolves.toEqual({ message: 'Validation succeeded' });
5986
});
6087
});
6188

62-
it('should resolve on successful validation', async () => {
63-
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
64-
jest.spyOn(yaml, 'parse').mockReturnValue({ attributes: [] }); // Mocked schema
65-
const mockYupObject = { validate: jest.fn() };
66-
mockYupObject.validate.mockResolvedValue('validated');
67-
service.createSchema = jest.fn().mockResolvedValue(mockYupObject);
68-
69-
const data = { objectClasses: ['testClass'], attributes: { testClass: {} } };
70-
await expect(service.validate(data)).resolves.toBeDefined();
89+
describe('test validateAttribute', () => {
90+
it('should return null for a valid attribute', async () => {
91+
const key = validAdditionalFieldsStub().objectClasses[0];
92+
const attribute = validAdditionalFieldsStub().attributes[key];
93+
await expect(service.validateAttribute(key, attribute)).resolves.toBeNull();
94+
});
7195
});
7296
});
73-
74-
// describe('createSchema', () => {
75-
// let service: IdentitiesValidationService;
76-
77-
// beforeEach(() => {
78-
// service = new IdentitiesValidationService();
79-
// });
80-
81-
// it('should create a schema based on input attributes', async () => {
82-
// const attributes = [{ name: 'test', type: 'string', required: true }];
83-
// const schema = await service.createSchema(attributes);
84-
// expect(yup.object).toHaveBeenCalled();
85-
// expect(schema).toBeDefined();
86-
// });
87-
// });

src/management/identities/validations/identities.validation.service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class IdentitiesValidationService {
4444
}
4545

4646
for (const key of attributesKeys) {
47+
// Check for invalid object classes
4748
if (!objectClasses.includes(key)) {
4849
validations[key] =
4950
`${key} n'est pas une classe d'objet valide dans ce contexte, les classes d'objets valides sont: ${objectClasses.join(
@@ -53,13 +54,15 @@ export class IdentitiesValidationService {
5354
continue;
5455
}
5556

57+
// Check for missing schema files
5658
const path = `./src/management/identities/validations/_config/${key}.yml`;
5759
if (!existsSync(path)) {
5860
validations[key] = `Fichier de config '${key}.yml' introuvable`;
5961
reject = true;
6062
continue;
6163
}
6264

65+
// Check for invalid schema
6366
const schema: ConfigObjectSchemaDTO = parse(readFileSync(path, 'utf8'));
6467
if (!this.validateSchema(schema)) {
6568
validations[key] = `Schema ${key}.yml invalide: ${this.ajv.errorsText(this.validateSchema.errors)}`;
@@ -93,7 +96,7 @@ export class IdentitiesValidationService {
9396
* @param attribute - The attribute value to validate.
9497
* @returns A promise that resolves with an error message if validation fails, otherwise null.
9598
*/
96-
private async validateAttribute(key: string, attribute: any): Promise<string | null> {
99+
public async validateAttribute(key: string, attribute: any): Promise<string | null> {
97100
const path = `./src/management/identities/validations/_config/${key}.yml`;
98101
const schema: ConfigObjectSchemaDTO = parse(readFileSync(path, 'utf8'));
99102

0 commit comments

Comments
 (0)