diff --git a/api/src/helper/lib/base-nlp-helper.ts b/api/src/helper/lib/base-nlp-helper.ts index 7c52e11b..02c30863 100644 --- a/api/src/helper/lib/base-nlp-helper.ts +++ b/api/src/helper/lib/base-nlp-helper.ts @@ -151,7 +151,7 @@ export default abstract class BaseNlpHelper< }) .concat({ entity: 'language', - value: s.language.code, + value: s.language!.code, }); return { diff --git a/api/src/i18n/services/language.service.ts b/api/src/i18n/services/language.service.ts index de312cf6..0a7425d5 100644 --- a/api/src/i18n/services/language.service.ts +++ b/api/src/i18n/services/language.service.ts @@ -11,9 +11,11 @@ import { Inject, Injectable, InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; import { Cache } from 'cache-manager'; +import { LoggerService } from '@/logger/logger.service'; import { DEFAULT_LANGUAGE_CACHE_KEY, LANGUAGES_CACHE_KEY, @@ -35,6 +37,7 @@ export class LanguageService extends BaseService< constructor( readonly repository: LanguageRepository, @Inject(CACHE_MANAGER) private readonly cacheManager: Cache, + private readonly logger: LoggerService, ) { super(repository); } @@ -78,6 +81,15 @@ export class LanguageService extends BaseService< * @returns A promise that resolves to the `Language` object. */ async getLanguageByCode(code: string) { - return await this.findOne({ code }); + const language = await this.findOne({ code }); + + if (!language) { + this.logger.warn(`Unable to Language by languageCode ${code}`); + throw new NotFoundException( + `Language with languageCode ${code} not found`, + ); + } + + return language; } } diff --git a/api/src/nlp/controllers/nlp-entity.controller.spec.ts b/api/src/nlp/controllers/nlp-entity.controller.spec.ts index 22d9dfd8..c33f8955 100644 --- a/api/src/nlp/controllers/nlp-entity.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-entity.controller.spec.ts @@ -27,15 +27,16 @@ import { closeInMongodConnection, rootMongooseTestModule, } from '@/utils/test/test'; +import { TFixtures } from '@/utils/test/types'; import { NlpEntityCreateDto } from '../dto/nlp-entity.dto'; import { NlpEntityRepository } from '../repositories/nlp-entity.repository'; import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.repository'; import { NlpValueRepository } from '../repositories/nlp-value.repository'; import { - NlpEntityModel, NlpEntity, NlpEntityFull, + NlpEntityModel, } from '../schemas/nlp-entity.schema'; import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema'; import { NlpValueModel } from '../schemas/nlp-value.schema'; @@ -48,8 +49,8 @@ describe('NlpEntityController', () => { let nlpEntityController: NlpEntityController; let nlpValueService: NlpValueService; let nlpEntityService: NlpEntityService; - let intentEntityId: string; - let buitInEntityId: string; + let intentEntityId: string | null; + let buitInEntityId: string | null; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -76,16 +77,18 @@ describe('NlpEntityController', () => { nlpValueService = module.get(NlpValueService); nlpEntityService = module.get(NlpEntityService); - intentEntityId = ( - await nlpEntityService.findOne({ - name: 'intent', - }) - ).id; - buitInEntityId = ( - await nlpEntityService.findOne({ - name: 'built_in', - }) - ).id; + intentEntityId = + ( + await nlpEntityService.findOne({ + name: 'intent', + }) + )?.id || null; + buitInEntityId = + ( + await nlpEntityService.findOne({ + name: 'built_in', + }) + )?.id || null; }); afterAll(async () => { await closeInMongodConnection(); @@ -107,11 +110,13 @@ describe('NlpEntityController', () => { ...curr, values: nlpValueFixtures.filter( ({ entity }) => parseInt(entity) === index, - ), + ) as NlpEntityFull['values'], + lookups: curr.lookups!, + builtin: curr.builtin!, }); return acc; }, - [], + [] as TFixtures[], ); expect(result).toEqualPayload( entitiesWithValues.sort((a, b) => { @@ -170,19 +175,19 @@ describe('NlpEntityController', () => { describe('deleteOne', () => { it('should delete a nlp entity', async () => { - const result = await nlpEntityController.deleteOne(intentEntityId); + const result = await nlpEntityController.deleteOne(intentEntityId!); expect(result.deletedCount).toEqual(1); }); it('should throw exception when nlp entity id not found', async () => { await expect( - nlpEntityController.deleteOne(intentEntityId), + nlpEntityController.deleteOne(intentEntityId!), ).rejects.toThrow(NotFoundException); }); it('should throw exception when nlp entity is builtin', async () => { await expect( - nlpEntityController.deleteOne(buitInEntityId), + nlpEntityController.deleteOne(buitInEntityId!), ).rejects.toThrow(MethodNotAllowedException); }); }); @@ -192,10 +197,10 @@ describe('NlpEntityController', () => { const firstNameEntity = await nlpEntityService.findOne({ name: 'first_name', }); - const result = await nlpEntityController.findOne(firstNameEntity.id, []); + const result = await nlpEntityController.findOne(firstNameEntity!.id, []); expect(result).toEqualPayload( - nlpEntityFixtures.find(({ name }) => name === 'first_name'), + nlpEntityFixtures.find(({ name }) => name === 'first_name')!, ); }); @@ -206,9 +211,15 @@ describe('NlpEntityController', () => { const firstNameValues = await nlpValueService.findOne({ value: 'jhon' }); const firstNameWithValues: NlpEntityFull = { ...firstNameEntity, - values: [firstNameValues], + values: firstNameValues ? [firstNameValues] : [], + name: firstNameEntity!.name, + id: firstNameEntity!.id, + createdAt: firstNameEntity!.createdAt, + updatedAt: firstNameEntity!.updatedAt, + lookups: firstNameEntity!.lookups, + builtin: firstNameEntity!.builtin, }; - const result = await nlpEntityController.findOne(firstNameEntity.id, [ + const result = await nlpEntityController.findOne(firstNameEntity!.id, [ 'values', ]); expect(result).toEqualPayload(firstNameWithValues); @@ -216,7 +227,7 @@ describe('NlpEntityController', () => { it('should throw NotFoundException when Id does not exist', async () => { await expect( - nlpEntityController.findOne(intentEntityId, ['values']), + nlpEntityController.findOne(intentEntityId!, ['values']), ).rejects.toThrow(NotFoundException); }); }); @@ -233,7 +244,7 @@ describe('NlpEntityController', () => { builtin: false, }; const result = await nlpEntityController.updateOne( - firstNameEntity.id, + firstNameEntity!.id, updatedNlpEntity, ); expect(result).toEqualPayload(updatedNlpEntity); @@ -247,7 +258,7 @@ describe('NlpEntityController', () => { builtin: false, }; await expect( - nlpEntityController.updateOne(intentEntityId, updateNlpEntity), + nlpEntityController.updateOne(intentEntityId!, updateNlpEntity), ).rejects.toThrow(NotFoundException); }); @@ -259,7 +270,7 @@ describe('NlpEntityController', () => { builtin: false, }; await expect( - nlpEntityController.updateOne(buitInEntityId, updateNlpEntity), + nlpEntityController.updateOne(buitInEntityId!, updateNlpEntity), ).rejects.toThrow(MethodNotAllowedException); }); }); @@ -276,7 +287,7 @@ describe('NlpEntityController', () => { name: 'updated', }) )?.id, - ]; + ] as string[]; const result = await nlpEntityController.deleteMany(entitiesToDelete); diff --git a/api/src/nlp/controllers/nlp-sample.controller.spec.ts b/api/src/nlp/controllers/nlp-sample.controller.spec.ts index b8d51f9f..e3285931 100644 --- a/api/src/nlp/controllers/nlp-sample.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-sample.controller.spec.ts @@ -30,6 +30,7 @@ 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'; @@ -38,7 +39,11 @@ 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, NlpSampleModel } from '../schemas/nlp-sample.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'; @@ -55,7 +60,7 @@ describe('NlpSampleController', () => { let nlpEntityService: NlpEntityService; let nlpValueService: NlpValueService; let languageService: LanguageService; - let byeJhonSampleId: string; + let byeJhonSampleId: string | null; let languages: Language[]; beforeAll(async () => { @@ -115,11 +120,12 @@ describe('NlpSampleController', () => { nlpSampleService = module.get(NlpSampleService); nlpEntityService = module.get(NlpEntityService); nlpValueService = module.get(NlpValueService); - byeJhonSampleId = ( - await nlpSampleService.findOne({ - text: 'Bye Jhon', - }) - ).id; + byeJhonSampleId = + ( + await nlpSampleService.findOne({ + text: 'Bye Jhon', + }) + )?.id || null; languageService = module.get(LanguageService); languages = await languageService.findAll(); }); @@ -143,15 +149,16 @@ describe('NlpSampleController', () => { (acc, currSample) => { const sampleWithEntities = { ...currSample, - entities: nlpSampleEntities.filter((currSampleEntity) => { - return currSampleEntity.sample === currSample.id; - }), - language: languages.find((lang) => lang.id === currSample.language), + entities: nlpSampleEntities.filter( + (currSampleEntity) => currSampleEntity.sample === currSample.id, + ), + language: + languages.find((lang) => lang.id === currSample.language) || null, }; acc.push(sampleWithEntities); return acc; }, - [], + [] as TFixtures[], ); expect(result).toEqualPayload(nlpSampleFixturesWithEntities); @@ -167,7 +174,7 @@ describe('NlpSampleController', () => { expect(result).toEqualPayload( nlpSampleFixtures.map((sample) => ({ ...sample, - language: languages[sample.language].id, + language: sample.language ? languages[sample.language].id : null, })), ); }); @@ -201,13 +208,13 @@ describe('NlpSampleController', () => { describe('deleteOne', () => { it('should delete a nlp sample', async () => { - const result = await nlpSampleController.deleteOne(byeJhonSampleId); + 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), + nlpSampleController.deleteOne(byeJhonSampleId!), ).rejects.toThrow(NotFoundException); }); }); @@ -217,12 +224,14 @@ describe('NlpSampleController', () => { const yessSample = await nlpSampleService.findOne({ text: 'yess', }); - const result = await nlpSampleController.findOne(yessSample.id, [ + const result = await nlpSampleController.findOne(yessSample!.id, [ 'invalidCreteria', ]); expect(result).toEqualPayload({ ...nlpSampleFixtures[0], - language: languages[nlpSampleFixtures[0].language].id, + language: nlpSampleFixtures[0].language + ? languages[nlpSampleFixtures?.[0]?.language]?.id + : null, }); }); @@ -231,22 +240,24 @@ describe('NlpSampleController', () => { text: 'yess', }); const yessSampleEntity = await nlpSampleEntityService.findOne({ - sample: yessSample.id, + sample: yessSample!.id, }); - const result = await nlpSampleController.findOne(yessSample.id, [ + const result = await nlpSampleController.findOne(yessSample!.id, [ 'entities', ]); const samplesWithEntities = { ...nlpSampleFixtures[0], entities: [yessSampleEntity], - language: languages[nlpSampleFixtures[0].language], + 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']), + nlpSampleController.findOne(byeJhonSampleId!, ['entities']), ).rejects.toThrow(NotFoundException); }); }); @@ -259,7 +270,7 @@ describe('NlpSampleController', () => { const frLang = await languageService.findOne({ code: 'fr', }); - const result = await nlpSampleController.updateOne(yessSample.id, { + const result = await nlpSampleController.updateOne(yessSample!.id, { text: 'updated', trained: true, type: NlpSampleState.test, @@ -288,12 +299,12 @@ describe('NlpSampleController', () => { expect(result.type).toEqual(updatedSample.type); expect(result.trained).toEqual(updatedSample.trained); expect(result.entities).toMatchObject(updatedSample.entities); - expect(result.language).toEqualPayload(updatedSample.language); + expect(result.language).toEqualPayload(updatedSample.language!); }); it('should throw exception when nlp sample id not found', async () => { await expect( - nlpSampleController.updateOne(byeJhonSampleId, { + nlpSampleController.updateOne(byeJhonSampleId!, { text: 'updated', trained: true, type: NlpSampleState.test, @@ -366,13 +377,13 @@ describe('NlpSampleController', () => { value: 'price', expressions: [], builtin: false, - entity: priceValueEntity.id, + entity: priceValueEntity!.id, }; const textSample = { text: 'How much does a BMW cost?', trained: false, type: 'train', - language: language.id, + language: language!.id, }; expect(intentEntityResult).toEqualPayload(intentEntity); @@ -389,13 +400,13 @@ describe('NlpSampleController', () => { await nlpSampleService.findOne({ text: 'How much does a BMW cost?', }) - ).id, + )?.id, ( await nlpSampleService.findOne({ text: 'text1', }) - ).id, - ]; + )?.id, + ] as string[]; const result = await nlpSampleController.deleteMany(samplesToDelete); diff --git a/api/src/nlp/controllers/nlp-sample.controller.ts b/api/src/nlp/controllers/nlp-sample.controller.ts index fbeed58b..cc49d41a 100644 --- a/api/src/nlp/controllers/nlp-sample.controller.ts +++ b/api/src/nlp/controllers/nlp-sample.controller.ts @@ -42,7 +42,7 @@ import { PopulatePipe } from '@/utils/pipes/populate.pipe'; import { SearchFilterPipe } from '@/utils/pipes/search-filter.pipe'; import { TFilterQuery } from '@/utils/types/filter.types'; -import { NlpSampleDto } from '../dto/nlp-sample.dto'; +import { NlpSampleDto, TNlpSampleDto } from '../dto/nlp-sample.dto'; import { NlpSample, NlpSampleFull, @@ -60,7 +60,8 @@ export class NlpSampleController extends BaseController< NlpSample, NlpSampleStub, NlpSamplePopulate, - NlpSampleFull + NlpSampleFull, + TNlpSampleDto > { constructor( private readonly nlpSampleService: NlpSampleService, @@ -128,15 +129,18 @@ export class NlpSampleController extends BaseController< }: NlpSampleDto, ): Promise { const language = await this.languageService.getLanguageByCode(languageCode); + const nlpSample = await this.nlpSampleService.create({ ...createNlpSampleDto, language: language.id, }); - const entities = await this.nlpSampleEntityService.storeSampleEntities( - nlpSample, - nlpEntities, - ); + const entities = nlpEntities + ? await this.nlpSampleEntityService.storeSampleEntities( + nlpSample, + nlpEntities, + ) + : []; return { ...nlpSample, @@ -202,7 +206,7 @@ export class NlpSampleController extends BaseController< try { const helper = await this.helperService.getDefaultNluHelper(); - const response = await helper.train(samples, entities); + const response = await helper.train?.(samples, entities); // Mark samples as trained await this.nlpSampleService.updateMany( { type: 'train' }, @@ -228,7 +232,7 @@ export class NlpSampleController extends BaseController< await this.getSamplesAndEntitiesByType('test'); const helper = await this.helperService.getDefaultNluHelper(); - return await helper.evaluate(samples, entities); + return await helper.evaluate?.(samples, entities); } /** @@ -294,6 +298,7 @@ export class NlpSampleController extends BaseController< @Body() { entities, language: languageCode, ...sampleAttrs }: NlpSampleDto, ): Promise { const language = await this.languageService.getLanguageByCode(languageCode); + const sample = await this.nlpSampleService.updateOne(id, { ...sampleAttrs, language: language.id, @@ -308,7 +313,10 @@ export class NlpSampleController extends BaseController< await this.nlpSampleEntityService.deleteMany({ sample: id }); const updatedSampleEntities = - await this.nlpSampleEntityService.storeSampleEntities(sample, entities); + await this.nlpSampleEntityService.storeSampleEntities( + sample, + entities || [], + ); return { ...sample, diff --git a/api/src/nlp/controllers/nlp-value.controller.spec.ts b/api/src/nlp/controllers/nlp-value.controller.spec.ts index bb4a56f4..58be7aa8 100644 --- a/api/src/nlp/controllers/nlp-value.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-value.controller.spec.ts @@ -22,6 +22,7 @@ import { closeInMongodConnection, rootMongooseTestModule, } from '@/utils/test/test'; +import { TFixtures } from '@/utils/test/types'; import { NlpValueCreateDto } from '../dto/nlp-value.dto'; import { NlpEntityRepository } from '../repositories/nlp-entity.repository'; @@ -29,7 +30,11 @@ import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.rep import { NlpValueRepository } from '../repositories/nlp-value.repository'; import { NlpEntityModel } from '../schemas/nlp-entity.schema'; import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema'; -import { NlpValueModel, NlpValue } from '../schemas/nlp-value.schema'; +import { + NlpValue, + NlpValueFull, + NlpValueModel, +} from '../schemas/nlp-value.schema'; import { NlpEntityService } from '../services/nlp-entity.service'; import { NlpValueService } from '../services/nlp-value.service'; @@ -39,9 +44,9 @@ describe('NlpValueController', () => { let nlpValueController: NlpValueController; let nlpValueService: NlpValueService; let nlpEntityService: NlpEntityService; - let jhonNlpValue: NlpValue; - let positiveValue: NlpValue; - let negativeValue: NlpValue; + let jhonNlpValue: NlpValue | null; + let positiveValue: NlpValue | null; + let negativeValue: NlpValue | null; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -92,11 +97,16 @@ describe('NlpValueController', () => { (acc, curr) => { acc.push({ ...curr, - entity: nlpEntityFixtures[parseInt(curr.entity)], + entity: nlpEntityFixtures[ + parseInt(curr.entity) + ] as NlpValueFull['entity'], + builtin: curr.builtin!, + expressions: curr.expressions!, + metadata: curr.metadata!, }); return acc; }, - [], + [] as TFixtures[], ); expect(result).toEqualPayload(nlpValueFixturesWithEntities); }); @@ -116,11 +126,14 @@ describe('NlpValueController', () => { const ValueWithEntities = { ...curr, entity: nlpEntities[parseInt(curr.entity)].id, + expressions: curr.expressions!, + metadata: curr.metadata!, + builtin: curr.builtin!, }; acc.push(ValueWithEntities); return acc; }, - [], + [] as TFixtures[], ); expect(result).toEqualPayload(nlpValueFixturesWithEntities); }); @@ -151,20 +164,20 @@ describe('NlpValueController', () => { describe('deleteOne', () => { it('should delete a nlp Value', async () => { - const result = await nlpValueController.deleteOne(jhonNlpValue.id); + const result = await nlpValueController.deleteOne(jhonNlpValue!.id); expect(result.deletedCount).toEqual(1); }); it('should throw exception when nlp Value id not found', async () => { await expect( - nlpValueController.deleteOne(jhonNlpValue.id), + nlpValueController.deleteOne(jhonNlpValue!.id), ).rejects.toThrow(NotFoundException); }); }); describe('findOne', () => { it('should get a nlp Value', async () => { - const result = await nlpValueController.findOne(positiveValue.id, [ + const result = await nlpValueController.findOne(positiveValue!.id, [ 'invalidCreteria', ]); const intentNlpEntity = await nlpEntityService.findOne({ @@ -172,7 +185,7 @@ describe('NlpValueController', () => { }); const valueWithEntity = { ...nlpValueFixtures[0], - entity: intentNlpEntity.id, + entity: intentNlpEntity!.id, }; expect(result).toEqualPayload(valueWithEntity); @@ -182,7 +195,7 @@ describe('NlpValueController', () => { const intentNlpEntity = await nlpEntityService.findOne({ name: 'intent', }); - const result = await nlpValueController.findOne(positiveValue.id, [ + const result = await nlpValueController.findOne(positiveValue!.id, [ 'entity', ]); const valueWithEntity = { @@ -194,7 +207,7 @@ describe('NlpValueController', () => { it('should throw NotFoundException when Id does not exist', async () => { await expect( - nlpValueController.findOne(jhonNlpValue.id, ['entity']), + nlpValueController.findOne(jhonNlpValue!.id, ['entity']), ).rejects.toThrow(NotFoundException); }); }); @@ -205,13 +218,13 @@ describe('NlpValueController', () => { name: 'intent', }); const updatedValue = { - entity: intentNlpEntity.id, + entity: intentNlpEntity!.id, value: 'updated', expressions: [], builtin: true, }; const result = await nlpValueController.updateOne( - positiveValue.id, + positiveValue!.id, updatedValue, ); expect(result).toEqualPayload(updatedValue); @@ -222,8 +235,8 @@ describe('NlpValueController', () => { name: 'intent', }); await expect( - nlpValueController.updateOne(jhonNlpValue.id, { - entity: intentNlpEntity.id, + nlpValueController.updateOne(jhonNlpValue!.id, { + entity: intentNlpEntity!.id, value: 'updated', expressions: [], builtin: true, @@ -233,7 +246,7 @@ describe('NlpValueController', () => { }); describe('deleteMany', () => { it('should delete multiple nlp values', async () => { - const valuesToDelete = [positiveValue.id, negativeValue.id]; + const valuesToDelete = [positiveValue!.id, negativeValue!.id]; const result = await nlpValueController.deleteMany(valuesToDelete); diff --git a/api/src/nlp/repositories/nlp-entity.repository.spec.ts b/api/src/nlp/repositories/nlp-entity.repository.spec.ts index 4ed70ca9..a4bd2a17 100644 --- a/api/src/nlp/repositories/nlp-entity.repository.spec.ts +++ b/api/src/nlp/repositories/nlp-entity.repository.spec.ts @@ -18,7 +18,7 @@ import { rootMongooseTestModule, } from '@/utils/test/test'; -import { NlpEntityModel, NlpEntity } from '../schemas/nlp-entity.schema'; +import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema'; import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema'; import { NlpValueModel } from '../schemas/nlp-value.schema'; @@ -29,7 +29,7 @@ import { NlpValueRepository } from './nlp-value.repository'; describe('NlpEntityRepository', () => { let nlpEntityRepository: NlpEntityRepository; let nlpValueRepository: NlpValueRepository; - let firstNameNlpEntity: NlpEntity; + let firstNameNlpEntity: NlpEntity | null; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -66,12 +66,12 @@ describe('NlpEntityRepository', () => { const intentNlpEntity = await nlpEntityRepository.findOne({ name: 'intent', }); - const result = await nlpEntityRepository.deleteOne(intentNlpEntity.id); + const result = await nlpEntityRepository.deleteOne(intentNlpEntity!.id); expect(result.deletedCount).toEqual(1); const intentNlpValues = await nlpValueRepository.find({ - entity: intentNlpEntity.id, + entity: intentNlpEntity!.id, }); expect(intentNlpValues.length).toEqual(0); @@ -81,10 +81,10 @@ describe('NlpEntityRepository', () => { describe('findOneAndPopulate', () => { it('should return a nlp entity with populate', async () => { const firstNameValues = await nlpValueRepository.find({ - entity: firstNameNlpEntity.id, + entity: firstNameNlpEntity!.id, }); const result = await nlpEntityRepository.findOneAndPopulate( - firstNameNlpEntity.id, + firstNameNlpEntity!.id, ); expect(result).toEqualPayload({ ...nlpEntityFixtures[1], @@ -99,15 +99,15 @@ describe('NlpEntityRepository', () => { sort: ['name', 'desc'], }); const firstNameValues = await nlpValueRepository.find({ - entity: firstNameNlpEntity.id, + entity: firstNameNlpEntity!.id, }); const result = await nlpEntityRepository.findPageAndPopulate( - { _id: firstNameNlpEntity.id }, + { _id: firstNameNlpEntity!.id }, pageQuery, ); expect(result).toEqualPayload([ { - id: firstNameNlpEntity.id, + id: firstNameNlpEntity!.id, ...nlpEntityFixtures[1], values: firstNameValues, }, diff --git a/api/src/nlp/repositories/nlp-sample-entity.repository.spec.ts b/api/src/nlp/repositories/nlp-sample-entity.repository.spec.ts index 801f5e5b..50494a7b 100644 --- a/api/src/nlp/repositories/nlp-sample-entity.repository.spec.ts +++ b/api/src/nlp/repositories/nlp-sample-entity.repository.spec.ts @@ -23,14 +23,16 @@ import { closeInMongodConnection, rootMongooseTestModule, } from '@/utils/test/test'; +import { TFixtures } from '@/utils/test/types'; -import { NlpEntityModel, NlpEntity } from '../schemas/nlp-entity.schema'; +import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema'; import { - NlpSampleEntityModel, NlpSampleEntity, + NlpSampleEntityFull, + NlpSampleEntityModel, } from '../schemas/nlp-sample-entity.schema'; import { NlpSampleModel } from '../schemas/nlp-sample.schema'; -import { NlpValueModel } from '../schemas/nlp-value.schema'; +import { NlpValueModel, NlpValueStub } from '../schemas/nlp-value.schema'; import { NlpEntityRepository } from './nlp-entity.repository'; import { NlpSampleEntityRepository } from './nlp-sample-entity.repository'; @@ -91,7 +93,7 @@ describe('NlpSampleEntityRepository', () => { value: { ...nlpValueFixtures[0], entity: nlpEntities[0].id }, sample: { ...nlpSampleFixtures[0], - language: languages[nlpSampleFixtures[0].language].id, + language: languages[nlpSampleFixtures[0].language!].id, }, }); }); @@ -111,11 +113,14 @@ describe('NlpSampleEntityRepository', () => { const ValueWithEntities = { ...curr, entity: nlpEntities[0].id, + expressions: curr.expressions!, + builtin: curr.builtin!, + metadata: curr.metadata!, }; acc.push(ValueWithEntities); return acc; }, - [], + [] as TFixtures[], ); nlpValueFixturesWithEntities[2] = { ...nlpValueFixturesWithEntities[2], @@ -135,7 +140,7 @@ describe('NlpSampleEntityRepository', () => { }; acc.push(sampleEntityWithPopulate); return acc; - }, []); + }, [] as TFixtures[]); expect(result).toEqualPayload(nlpSampleEntityFixturesWithPopulate); }); }); diff --git a/api/src/nlp/repositories/nlp-sample.repository.spec.ts b/api/src/nlp/repositories/nlp-sample.repository.spec.ts index b5dd9e0b..4c2d00c8 100644 --- a/api/src/nlp/repositories/nlp-sample.repository.spec.ts +++ b/api/src/nlp/repositories/nlp-sample.repository.spec.ts @@ -19,12 +19,17 @@ import { closeInMongodConnection, rootMongooseTestModule, } from '@/utils/test/test'; +import { TFixtures } from '@/utils/test/types'; import { - NlpSampleEntityModel, NlpSampleEntity, + NlpSampleEntityModel, } from '../schemas/nlp-sample-entity.schema'; -import { NlpSampleModel, NlpSample } from '../schemas/nlp-sample.schema'; +import { + NlpSample, + NlpSampleFull, + NlpSampleModel, +} from '../schemas/nlp-sample.schema'; import { NlpSampleEntityRepository } from './nlp-sample-entity.repository'; import { NlpSampleRepository } from './nlp-sample.repository'; @@ -33,8 +38,8 @@ describe('NlpSampleRepository', () => { let nlpSampleRepository: NlpSampleRepository; let nlpSampleEntityRepository: NlpSampleEntityRepository; let languageRepository: LanguageRepository; - let nlpSampleEntity: NlpSampleEntity; - let noNlpSample: NlpSample; + let nlpSampleEntity: NlpSampleEntity | null; + let noNlpSample: NlpSample | null; let languages: Language[]; beforeAll(async () => { @@ -61,7 +66,7 @@ describe('NlpSampleRepository', () => { languageRepository = module.get(LanguageRepository); noNlpSample = await nlpSampleRepository.findOne({ text: 'No' }); nlpSampleEntity = await nlpSampleEntityRepository.findOne({ - sample: noNlpSample.id, + sample: noNlpSample!.id, }); languages = await languageRepository.findAll(); }); @@ -75,12 +80,12 @@ describe('NlpSampleRepository', () => { describe('findOneAndPopulate', () => { it('should return a nlp Sample with populate', async () => { const result = await nlpSampleRepository.findOneAndPopulate( - noNlpSample.id, + noNlpSample!.id, ); expect(result).toEqualPayload({ ...nlpSampleFixtures[1], entities: [nlpSampleEntity], - language: languages[nlpSampleFixtures[1].language], + language: languages[nlpSampleFixtures[1].language!], }); }); }); @@ -104,12 +109,13 @@ describe('NlpSampleRepository', () => { entities: nlpSampleEntities.filter((currSampleEntity) => { return currSampleEntity.sample === currSample.id; }), - language: languages.find((lang) => currSample.language === lang.id), + language: + languages.find((lang) => currSample.language === lang.id) || null, }; acc.push(sampleWithEntities); return acc; }, - [], + [] as TFixtures[], ); expect(result).toEqualPayload(nlpSampleFixturesWithEntities); }); @@ -130,10 +136,10 @@ describe('NlpSampleRepository', () => { describe('The deleteCascadeOne function', () => { it('should delete a nlp Sample', async () => { - const result = await nlpSampleRepository.deleteOne(noNlpSample.id); + const result = await nlpSampleRepository.deleteOne(noNlpSample!.id); expect(result.deletedCount).toEqual(1); const sampleEntities = await nlpSampleEntityRepository.find({ - sample: noNlpSample.id, + sample: noNlpSample!.id, }); expect(sampleEntities.length).toEqual(0); }); diff --git a/api/src/nlp/repositories/nlp-value.repository.spec.ts b/api/src/nlp/repositories/nlp-value.repository.spec.ts index ffef47f9..3186cc26 100644 --- a/api/src/nlp/repositories/nlp-value.repository.spec.ts +++ b/api/src/nlp/repositories/nlp-value.repository.spec.ts @@ -18,10 +18,15 @@ import { closeInMongodConnection, rootMongooseTestModule, } from '@/utils/test/test'; +import { TFixtures } from '@/utils/test/types'; import { NlpEntityModel } from '../schemas/nlp-entity.schema'; import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema'; -import { NlpValue, NlpValueModel } from '../schemas/nlp-value.schema'; +import { + NlpValue, + NlpValueFull, + NlpValueModel, +} from '../schemas/nlp-value.schema'; import { NlpSampleEntityRepository } from './nlp-sample-entity.repository'; import { NlpValueRepository } from './nlp-value.repository'; @@ -81,12 +86,17 @@ describe('NlpValueRepository', () => { (acc, curr) => { const ValueWithEntities = { ...curr, - entity: nlpEntityFixtures[parseInt(curr.entity)], + entity: nlpEntityFixtures[ + parseInt(curr.entity) + ] as NlpValueFull['entity'], + builtin: curr.builtin!, + expressions: curr.expressions!, + metadata: curr.metadata!, }; acc.push(ValueWithEntities); return acc; }, - [], + [] as TFixtures[], ); expect(result).toEqualPayload(nlpValueFixturesWithEntities); }); diff --git a/api/src/nlp/services/nlp-entity.service.spec.ts b/api/src/nlp/services/nlp-entity.service.spec.ts index ffb53216..76b78233 100644 --- a/api/src/nlp/services/nlp-entity.service.spec.ts +++ b/api/src/nlp/services/nlp-entity.service.spec.ts @@ -21,7 +21,7 @@ import { import { NlpEntityRepository } from '../repositories/nlp-entity.repository'; import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.repository'; import { NlpValueRepository } from '../repositories/nlp-value.repository'; -import { NlpEntityModel, NlpEntity } from '../schemas/nlp-entity.schema'; +import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema'; import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema'; import { NlpValueModel } from '../schemas/nlp-value.schema'; @@ -69,7 +69,7 @@ describe('nlpEntityService', () => { name: 'intent', }); const result = await nlpEntityService.deleteCascadeOne( - intentNlpEntity.id, + intentNlpEntity!.id, ); expect(result.deletedCount).toEqual(1); }); @@ -81,13 +81,13 @@ describe('nlpEntityService', () => { name: 'first_name', }); const result = await nlpEntityService.findOneAndPopulate( - firstNameNlpEntity.id, + firstNameNlpEntity!.id, ); const firstNameValues = await nlpValueRepository.findOne({ - entity: firstNameNlpEntity.id, + entity: firstNameNlpEntity!.id, }); const entityWithValues = { - id: firstNameNlpEntity.id, + id: firstNameNlpEntity!.id, ...nlpEntityFixtures[1], values: [firstNameValues], }; @@ -102,15 +102,15 @@ describe('nlpEntityService', () => { name: 'first_name', }); const result = await nlpEntityService.findPageAndPopulate( - { _id: firstNameNlpEntity.id }, + { _id: firstNameNlpEntity!.id }, pageQuery, ); const firstNameValues = await nlpValueRepository.findOne({ - entity: firstNameNlpEntity.id, + entity: firstNameNlpEntity!.id, }); const entitiesWithValues = [ { - id: firstNameNlpEntity.id, + id: firstNameNlpEntity!.id, ...nlpEntityFixtures[1], values: [firstNameValues], }, @@ -139,12 +139,12 @@ describe('nlpEntityService', () => { const deValue = await nlpValueRepository.findOne({ value: 'de' }); const storedEntites = [ { - entity: intentEntity.id, - value: nameValue.id, + entity: intentEntity!.id, + value: nameValue!.id, }, { - entity: languageEntity.id, - value: deValue.id, + entity: languageEntity!.id, + value: deValue!.id, }, ]; diff --git a/api/src/nlp/services/nlp-sample-entity.service.spec.ts b/api/src/nlp/services/nlp-sample-entity.service.spec.ts index 72a42011..94707a0a 100644 --- a/api/src/nlp/services/nlp-sample-entity.service.spec.ts +++ b/api/src/nlp/services/nlp-sample-entity.service.spec.ts @@ -23,17 +23,23 @@ import { closeInMongodConnection, rootMongooseTestModule, } from '@/utils/test/test'; +import { TFixtures } from '@/utils/test/types'; import { NlpEntityRepository } from '../repositories/nlp-entity.repository'; import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.repository'; import { NlpValueRepository } from '../repositories/nlp-value.repository'; -import { NlpEntityModel, NlpEntity } from '../schemas/nlp-entity.schema'; +import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema'; import { - NlpSampleEntityModel, NlpSampleEntity, + NlpSampleEntityFull, + NlpSampleEntityModel, } from '../schemas/nlp-sample-entity.schema'; import { NlpSample, NlpSampleModel } from '../schemas/nlp-sample.schema'; -import { NlpValue, NlpValueModel } from '../schemas/nlp-value.schema'; +import { + NlpValue, + NlpValueModel, + NlpValueStub, +} from '../schemas/nlp-value.schema'; import { NlpEntityService } from './nlp-entity.service'; import { NlpSampleEntityService } from './nlp-sample-entity.service'; @@ -91,9 +97,7 @@ describe('NlpSampleEntityService', () => { languages = await languageRepository.findAll(); }); - afterAll(async () => { - await closeInMongodConnection(); - }); + afterAll(closeInMongodConnection); afterEach(jest.clearAllMocks); @@ -108,7 +112,7 @@ describe('NlpSampleEntityService', () => { value: { ...nlpValueFixtures[0], entity: nlpEntities[0].id }, sample: { ...nlpSampleFixtures[0], - language: languages[nlpSampleFixtures[0].language].id, + language: languages[nlpSampleFixtures[0].language!].id, }, }; expect(result).toEqualPayload(sampleEntityWithPopulate); @@ -129,11 +133,14 @@ describe('NlpSampleEntityService', () => { const ValueWithEntities = { ...curr, entity: nlpEntities[0].id, + expressions: curr.expressions!, + builtin: curr.builtin!, + metadata: curr.metadata!, }; acc.push(ValueWithEntities); return acc; }, - [], + [] as TFixtures[], ); nlpValueFixturesWithEntities[2] = { ...nlpValueFixturesWithEntities[2], @@ -153,7 +160,7 @@ describe('NlpSampleEntityService', () => { }; acc.push(sampleEntityWithPopulate); return acc; - }, []); + }, [] as TFixtures[]); expect(result).toEqualPayload(nlpSampleEntityFixturesWithPopulate); }); }); diff --git a/api/src/nlp/services/nlp-sample.service.spec.ts b/api/src/nlp/services/nlp-sample.service.spec.ts index 3b62973b..38deff34 100644 --- a/api/src/nlp/services/nlp-sample.service.spec.ts +++ b/api/src/nlp/services/nlp-sample.service.spec.ts @@ -33,7 +33,11 @@ import { NlpSampleEntity, NlpSampleEntityModel, } from '../schemas/nlp-sample-entity.schema'; -import { NlpSample, NlpSampleModel } from '../schemas/nlp-sample.schema'; +import { + NlpSample, + NlpSampleFull, + NlpSampleModel, +} from '../schemas/nlp-sample.schema'; import { NlpValueModel } from '../schemas/nlp-value.schema'; import { NlpEntityService } from './nlp-entity.service'; @@ -49,8 +53,8 @@ describe('NlpSampleService', () => { let nlpSampleEntityRepository: NlpSampleEntityRepository; let nlpSampleRepository: NlpSampleRepository; let languageRepository: LanguageRepository; - let noNlpSample: NlpSample; - let nlpSampleEntity: NlpSampleEntity; + let noNlpSample: NlpSample | null; + let nlpSampleEntity: NlpSampleEntity | null; let languages: Language[]; beforeAll(async () => { @@ -104,7 +108,7 @@ describe('NlpSampleService', () => { languageRepository = module.get(LanguageRepository); noNlpSample = await nlpSampleService.findOne({ text: 'No' }); nlpSampleEntity = await nlpSampleEntityRepository.findOne({ - sample: noNlpSample.id, + sample: noNlpSample!.id, }); languages = await languageRepository.findAll(); }); @@ -117,11 +121,11 @@ describe('NlpSampleService', () => { describe('findOneAndPopulate', () => { it('should return a nlp Sample with populate', async () => { - const result = await nlpSampleService.findOneAndPopulate(noNlpSample.id); + const result = await nlpSampleService.findOneAndPopulate(noNlpSample!.id); const sampleWithEntities = { ...nlpSampleFixtures[1], entities: [nlpSampleEntity], - language: languages[nlpSampleFixtures[1].language], + language: languages[nlpSampleFixtures[1].language!], }; expect(result).toEqualPayload(sampleWithEntities); }); @@ -141,12 +145,13 @@ describe('NlpSampleService', () => { entities: nlpSampleEntities.filter((currSampleEntity) => { return currSampleEntity.sample === currSample.id; }), - language: languages.find((lang) => lang.id === currSample.language), + language: + languages.find((lang) => lang.id === currSample.language) || null, }; acc.push(sampleWithEntities); return acc; }, - [], + [] as NlpSampleFull[], ); expect(result).toEqualPayload(nlpSampleFixturesWithEntities); }); @@ -167,7 +172,7 @@ describe('NlpSampleService', () => { describe('The deleteCascadeOne function', () => { it('should delete a nlp Sample', async () => { - const result = await nlpSampleService.deleteOne(noNlpSample.id); + const result = await nlpSampleService.deleteOne(noNlpSample!.id); expect(result.deletedCount).toEqual(1); }); }); diff --git a/api/src/nlp/services/nlp-value.service.spec.ts b/api/src/nlp/services/nlp-value.service.spec.ts index c5aeb79a..35202015 100644 --- a/api/src/nlp/services/nlp-value.service.spec.ts +++ b/api/src/nlp/services/nlp-value.service.spec.ts @@ -10,6 +10,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { MongooseModule } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; +import { BaseSchema } from '@/utils/generics/base-schema'; import { nlpEntityFixtures } from '@/utils/test/fixtures/nlpentity'; import { installNlpValueFixtures, @@ -26,7 +27,11 @@ import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.rep import { NlpValueRepository } from '../repositories/nlp-value.repository'; import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema'; import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema'; -import { NlpValue, NlpValueModel } from '../schemas/nlp-value.schema'; +import { + NlpValue, + NlpValueFull, + NlpValueModel, +} from '../schemas/nlp-value.schema'; import { NlpEntityService } from './nlp-entity.service'; import { NlpValueService } from './nlp-value.service'; @@ -89,12 +94,17 @@ describe('NlpValueService', () => { (acc, curr) => { const ValueWithEntities = { ...curr, - entity: nlpEntityFixtures[parseInt(curr.entity)], + entity: nlpEntityFixtures[ + parseInt(curr.entity) + ] as NlpValueFull['entity'], + expressions: curr.expressions!, + metadata: curr.metadata!, + builtin: curr.builtin!, }; acc.push(ValueWithEntities); return acc; }, - [], + [] as Omit[], ); expect(result).toEqualPayload(nlpValueFixturesWithEntities); }); @@ -130,12 +140,12 @@ describe('NlpValueService', () => { const jhonValue = await nlpValueRepository.findOne({ value: 'jhon' }); const storedValues = [ { - entity: intentEntity.id, - value: greetingValue.id, + entity: intentEntity!.id, + value: greetingValue!.id, }, { - entity: firstNameEntity.id, - value: jhonValue.id, + entity: firstNameEntity!.id, + value: jhonValue!.id, }, ]; diff --git a/api/src/nlp/services/nlp-value.service.ts b/api/src/nlp/services/nlp-value.service.ts index d6a51b6d..2d20bde5 100644 --- a/api/src/nlp/services/nlp-value.service.ts +++ b/api/src/nlp/services/nlp-value.service.ts @@ -128,7 +128,7 @@ export class NlpValueService extends BaseService< if ('start' in e && 'end' in e) { const word = sampleText.slice(e.start, e.end); return ( - word !== e.value && vMap[e.value].expressions.indexOf(word) === -1 + word !== e.value && vMap[e.value].expressions?.indexOf(word) === -1 ); } return false; @@ -136,7 +136,7 @@ export class NlpValueService extends BaseService< .map((e) => { return this.updateOne(vMap[e.value].id, { ...vMap[e.value], - expressions: vMap[e.value].expressions.concat([ + expressions: vMap[e.value].expressions?.concat([ sampleText.slice(e.start, e.end), ]), } as NlpValueUpdateDto); @@ -207,9 +207,11 @@ export class NlpValueService extends BaseService< const promises = valuesToAdd.map(async (v) => { const createdOrFound = await this.findOneOrCreate({ value: v.value }, v); // If value is found in database, then update it's synonyms - const expressions = createdOrFound.expressions - .concat(v.expressions) // Add new synonyms - .filter((v, i, a) => a.indexOf(v) === i); // Filter unique values + const expressions = v.expressions + ? createdOrFound.expressions + ?.concat(v.expressions) // Add new synonyms + .filter((v, i, a) => a.indexOf(v) === i) + : createdOrFound.expressions?.filter((v, i, a) => a.indexOf(v) === i); // Filter unique values // Update expressions const result = await this.updateOne({ value: v.value }, { expressions }); diff --git a/api/src/utils/pipes/search-filter.pipe.ts b/api/src/utils/pipes/search-filter.pipe.ts index 64221585..3e2c22a8 100644 --- a/api/src/utils/pipes/search-filter.pipe.ts +++ b/api/src/utils/pipes/search-filter.pipe.ts @@ -31,7 +31,7 @@ export class SearchFilterPipe private readonly props: { allowedFields: TFilterNestedKeysOfType< T, - undefined | string | string[] + null | undefined | string | string[] >[]; }, ) {} @@ -48,7 +48,10 @@ export class SearchFilterPipe private isAllowedField(field: string) { if ( this.props.allowedFields.includes( - field as TFilterNestedKeysOfType, + field as TFilterNestedKeysOfType< + T, + null | undefined | string | string[] + >, ) ) return true; diff --git a/api/src/utils/test/fixtures/nlpsample.ts b/api/src/utils/test/fixtures/nlpsample.ts index b62ce7e9..e17bc74c 100644 --- a/api/src/utils/test/fixtures/nlpsample.ts +++ b/api/src/utils/test/fixtures/nlpsample.ts @@ -60,7 +60,7 @@ export const installNlpSampleFixtures = async () => { nlpSampleFixtures.map((v) => { return { ...v, - language: languages[parseInt(v.language)].id, + language: v.language ? languages[parseInt(v.language)].id : null, }; }), );