hexabot/api/src/nlp/controllers/nlp-sample.controller.spec.ts
2025-01-09 12:31:25 +01:00

438 lines
14 KiB
TypeScript

/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { MongooseModule } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { HelperService } from '@/helper/helper.service';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { Language, LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { installAttachmentFixtures } from '@/utils/test/fixtures/attachment';
import { nlpSampleFixtures } from '@/utils/test/fixtures/nlpsample';
import { installNlpSampleEntityFixtures } from '@/utils/test/fixtures/nlpsampleentity';
import { getPageQuery } from '@/utils/test/pagination';
import {
closeInMongodConnection,
rootMongooseTestModule,
} from '@/utils/test/test';
import { TFixtures } from '@/utils/test/types';
import { NlpSampleDto } from '../dto/nlp-sample.dto';
import { NlpEntityRepository } from '../repositories/nlp-entity.repository';
import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.repository';
import { NlpSampleRepository } from '../repositories/nlp-sample.repository';
import { NlpValueRepository } from '../repositories/nlp-value.repository';
import { NlpEntityModel } from '../schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema';
import {
NlpSample,
NlpSampleFull,
NlpSampleModel,
} from '../schemas/nlp-sample.schema';
import { NlpValueModel } from '../schemas/nlp-value.schema';
import { NlpSampleState } from '../schemas/types';
import { NlpEntityService } from '../services/nlp-entity.service';
import { NlpSampleEntityService } from '../services/nlp-sample-entity.service';
import { NlpSampleService } from '../services/nlp-sample.service';
import { NlpValueService } from '../services/nlp-value.service';
import { NlpSampleController } from './nlp-sample.controller';
describe('NlpSampleController', () => {
let nlpSampleController: NlpSampleController;
let nlpSampleEntityService: NlpSampleEntityService;
let nlpSampleService: NlpSampleService;
let nlpEntityService: NlpEntityService;
let nlpValueService: NlpValueService;
let languageService: LanguageService;
let byeJhonSampleId: string | null;
let languages: Language[];
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [NlpSampleController],
imports: [
rootMongooseTestModule(async () => {
await installNlpSampleEntityFixtures();
await installAttachmentFixtures();
}),
MongooseModule.forFeature([
NlpSampleModel,
NlpSampleEntityModel,
NlpEntityModel,
NlpValueModel,
SettingModel,
LanguageModel,
]),
],
providers: [
LoggerService,
NlpSampleRepository,
NlpSampleEntityRepository,
NlpEntityService,
NlpEntityRepository,
NlpValueService,
NlpValueRepository,
NlpSampleService,
NlpSampleEntityService,
LanguageRepository,
LanguageService,
EventEmitter2,
HelperService,
SettingRepository,
SettingService,
SettingSeeder,
{
provide: I18nService,
useValue: {
t: jest.fn().mockImplementation((t) => t),
},
},
{
provide: CACHE_MANAGER,
useValue: {
del: jest.fn(),
get: jest.fn(),
set: jest.fn(),
},
},
],
}).compile();
nlpSampleController = module.get<NlpSampleController>(NlpSampleController);
nlpSampleEntityService = module.get<NlpSampleEntityService>(
NlpSampleEntityService,
);
nlpSampleService = module.get<NlpSampleService>(NlpSampleService);
nlpEntityService = module.get<NlpEntityService>(NlpEntityService);
nlpValueService = module.get<NlpValueService>(NlpValueService);
byeJhonSampleId =
(
await nlpSampleService.findOne({
text: 'Bye Jhon',
})
)?.id || null;
languageService = module.get<LanguageService>(LanguageService);
languages = await languageService.findAll();
});
afterAll(async () => {
await closeInMongodConnection();
});
afterEach(jest.clearAllMocks);
describe('findPage', () => {
it('should find nlp samples, and foreach sample populate the corresponding entities', async () => {
const pageQuery = getPageQuery<NlpSample>({ sort: ['text', 'desc'] });
const result = await nlpSampleController.findPage(
pageQuery,
['language', 'entities'],
{},
);
const nlpSamples = await nlpSampleService.findAll();
const nlpSampleEntities = await nlpSampleEntityService.findAll();
const nlpSampleFixturesWithEntities = nlpSamples.reduce(
(acc, currSample) => {
const sampleWithEntities = {
...currSample,
entities: nlpSampleEntities.filter(
(currSampleEntity) => currSampleEntity.sample === currSample.id,
),
language:
languages.find((lang) => lang.id === currSample.language) || null,
};
acc.push(sampleWithEntities);
return acc;
},
[] as TFixtures<NlpSampleFull>[],
);
expect(result).toEqualPayload(nlpSampleFixturesWithEntities);
});
it('should find nlp samples', async () => {
const pageQuery = getPageQuery<NlpSample>({ sort: ['text', 'desc'] });
const result = await nlpSampleController.findPage(
pageQuery,
['invalidCriteria'],
{},
);
expect(result).toEqualPayload(
nlpSampleFixtures.map((sample) => ({
...sample,
language: sample.language ? languages[sample.language].id : null,
})),
);
});
});
describe('count', () => {
it('should count the nlp samples', async () => {
const result = await nlpSampleController.count({});
const count = nlpSampleFixtures.length;
expect(result).toEqual({ count });
});
});
describe('create', () => {
it('should create nlp sample', async () => {
const enLang = await languageService.findOne({ code: 'en' });
const nlSample: NlpSampleDto = {
text: 'text1',
trained: true,
type: NlpSampleState.test,
entities: [],
language: 'en',
};
const result = await nlpSampleController.create(nlSample);
expect(result).toEqualPayload({
...nlSample,
language: enLang,
});
});
});
describe('deleteOne', () => {
it('should delete a nlp sample', async () => {
const result = await nlpSampleController.deleteOne(byeJhonSampleId!);
expect(result.deletedCount).toEqual(1);
});
it('should throw exception when nlp sample id not found', async () => {
await expect(
nlpSampleController.deleteOne(byeJhonSampleId!),
).rejects.toThrow(NotFoundException);
});
});
describe('findOne', () => {
it('should find a nlp sample', async () => {
const yessSample = await nlpSampleService.findOne({
text: 'yess',
});
const result = await nlpSampleController.findOne(yessSample!.id, [
'invalidCreteria',
]);
expect(result).toEqualPayload({
...nlpSampleFixtures[0],
language: nlpSampleFixtures[0].language
? languages[nlpSampleFixtures?.[0]?.language]?.id
: null,
});
});
it('should find a nlp sample and populate its entities', async () => {
const yessSample = await nlpSampleService.findOne({
text: 'yess',
});
const yessSampleEntity = await nlpSampleEntityService.findOne({
sample: yessSample!.id,
});
const result = await nlpSampleController.findOne(yessSample!.id, [
'entities',
]);
const samplesWithEntities = {
...nlpSampleFixtures[0],
entities: [yessSampleEntity],
language: nlpSampleFixtures[0].language
? languages[nlpSampleFixtures[0].language]
: null,
};
expect(result).toEqualPayload(samplesWithEntities);
});
it('should throw NotFoundException when Id does not exist', async () => {
await expect(
nlpSampleController.findOne(byeJhonSampleId!, ['entities']),
).rejects.toThrow(NotFoundException);
});
});
describe('updateOne', () => {
it('should update a nlp sample', async () => {
const yessSample = await nlpSampleService.findOne({
text: 'yess',
});
const frLang = await languageService.findOne({
code: 'fr',
});
const result = await nlpSampleController.updateOne(yessSample!.id, {
text: 'updated',
trained: true,
type: NlpSampleState.test,
entities: [
{
entity: 'intent',
value: 'update',
},
],
language: 'fr',
});
const updatedSample = {
text: 'updated',
trained: false,
type: NlpSampleState.test,
entities: [
{
entity: expect.stringMatching(/^[a-z0-9]+$/),
sample: expect.stringMatching(/^[a-z0-9]+$/),
value: expect.stringMatching(/^[a-z0-9]+$/),
},
],
language: frLang,
};
expect(result.text).toEqual(updatedSample.text);
expect(result.type).toEqual(updatedSample.type);
expect(result.trained).toEqual(updatedSample.trained);
expect(result.entities).toMatchObject(updatedSample.entities);
expect(result.language).toEqualPayload(updatedSample.language!);
});
it('should throw exception when nlp sample id not found', async () => {
await expect(
nlpSampleController.updateOne(byeJhonSampleId!, {
text: 'updated',
trained: true,
type: NlpSampleState.test,
language: 'fr',
}),
).rejects.toThrow(NotFoundException);
});
});
describe('importFile', () => {
it('should throw exception when something is wrong with the upload', async () => {
const file = {
buffer: Buffer.from('', 'utf-8'),
size: 0,
mimetype: 'text/csv',
} as Express.Multer.File;
await expect(nlpSampleController.importFile(file)).rejects.toThrow(
'Bad Request Exception',
);
});
it('should return a failure if an error occurs when parsing csv file ', async () => {
const mockCsvDataWithErrors: string = `intent,entities,lang,question
greeting,person,en`;
const buffer = Buffer.from(mockCsvDataWithErrors, 'utf-8');
const file = {
buffer,
size: buffer.length,
mimetype: 'text/csv',
} as Express.Multer.File;
await expect(nlpSampleController.importFile(file)).rejects.toThrow();
});
it('should import data from a CSV file', async () => {
const mockCsvData: string = [
`text,intent,language`,
`How much does a BMW cost?,price,en`,
].join('\n');
const buffer = Buffer.from(mockCsvData, 'utf-8');
const file = {
buffer,
size: buffer.length,
mimetype: 'text/csv',
} as Express.Multer.File;
const result = await nlpSampleController.importFile(file);
const intentEntityResult = await nlpEntityService.findOne({
name: 'intent',
});
const priceValueResult = await nlpValueService.findOne({
value: 'price',
});
const textSampleResult = await nlpSampleService.findOne({
text: 'How much does a BMW cost?',
});
const language = await languageService.findOne({
code: 'en',
});
const intentEntity = {
name: 'intent',
lookups: ['trait'],
doc: '',
builtin: false,
};
const priceValueEntity = await nlpEntityService.findOne({
name: 'intent',
});
const priceValue = {
value: 'price',
expressions: [],
builtin: false,
entity: priceValueEntity!.id,
};
const textSample = {
text: 'How much does a BMW cost?',
trained: false,
type: 'train',
language: language!.id,
};
expect(intentEntityResult).toEqualPayload(intentEntity);
expect(priceValueResult).toEqualPayload(priceValue);
expect(textSampleResult).toEqualPayload(textSample);
expect(result).toEqualPayload([textSample]);
});
});
describe('deleteMany', () => {
it('should delete multiple nlp samples', async () => {
const samplesToDelete = [
(
await nlpSampleService.findOne({
text: 'How much does a BMW cost?',
})
)?.id,
(
await nlpSampleService.findOne({
text: 'text1',
})
)?.id,
] as string[];
const result = await nlpSampleController.deleteMany(samplesToDelete);
expect(result.deletedCount).toEqual(samplesToDelete.length);
const remainingSamples = await nlpSampleService.find({
_id: { $in: samplesToDelete },
});
expect(remainingSamples.length).toBe(0);
});
it('should throw BadRequestException when no IDs are provided', async () => {
await expect(nlpSampleController.deleteMany([])).rejects.toThrow(
BadRequestException,
);
});
it('should throw NotFoundException when provided IDs do not exist', async () => {
const nonExistentIds = [
'614c1b2f58f4f04c876d6b8d',
'614c1b2f58f4f04c876d6b8e',
];
await expect(
nlpSampleController.deleteMany(nonExistentIds),
).rejects.toThrow(NotFoundException);
});
});
});