mirror of
https://github.com/hexastack/hexabot
synced 2025-01-22 10:35:37 +00:00
feat: implement strict null check
This commit is contained in:
parent
11ef58d048
commit
d2223d6bfd
@ -27,7 +27,7 @@ import {
|
||||
|
||||
import { attachment, attachmentFile } from '../mocks/attachment.mock';
|
||||
import { AttachmentRepository } from '../repositories/attachment.repository';
|
||||
import { AttachmentModel, Attachment } from '../schemas/attachment.schema';
|
||||
import { Attachment, AttachmentModel } from '../schemas/attachment.schema';
|
||||
import { AttachmentService } from '../services/attachment.service';
|
||||
|
||||
import { AttachmentController } from './attachment.controller';
|
||||
@ -35,7 +35,7 @@ import { AttachmentController } from './attachment.controller';
|
||||
describe('AttachmentController', () => {
|
||||
let attachmentController: AttachmentController;
|
||||
let attachmentService: AttachmentService;
|
||||
let attachmentToDelete: Attachment;
|
||||
let attachmentToDelete: Attachment | null;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -79,7 +79,7 @@ describe('AttachmentController', () => {
|
||||
describe('Upload', () => {
|
||||
it('should throw BadRequestException if no file is selected to be uploaded', async () => {
|
||||
const promiseResult = attachmentController.uploadFile({
|
||||
file: undefined,
|
||||
file: [],
|
||||
});
|
||||
await expect(promiseResult).rejects.toThrow(
|
||||
new BadRequestException('No file was selected'),
|
||||
@ -121,17 +121,17 @@ describe('AttachmentController', () => {
|
||||
name: 'store1.jpg',
|
||||
});
|
||||
const result = await attachmentController.download({
|
||||
id: storedAttachment.id,
|
||||
id: storedAttachment!.id,
|
||||
});
|
||||
|
||||
expect(attachmentService.findOne).toHaveBeenCalledWith(
|
||||
storedAttachment.id,
|
||||
storedAttachment!.id,
|
||||
);
|
||||
expect(result.options).toEqual({
|
||||
type: storedAttachment.type,
|
||||
length: storedAttachment.size,
|
||||
expect(result?.options).toEqual({
|
||||
type: storedAttachment!.type,
|
||||
length: storedAttachment!.size,
|
||||
disposition: `attachment; filename="${encodeURIComponent(
|
||||
storedAttachment.name,
|
||||
storedAttachment!.name,
|
||||
)}"`,
|
||||
});
|
||||
});
|
||||
@ -141,11 +141,11 @@ describe('AttachmentController', () => {
|
||||
it('should delete an attachment by id', async () => {
|
||||
jest.spyOn(attachmentService, 'deleteOne');
|
||||
const result = await attachmentController.deleteOne(
|
||||
attachmentToDelete.id,
|
||||
attachmentToDelete!.id,
|
||||
);
|
||||
|
||||
expect(attachmentService.deleteOne).toHaveBeenCalledWith(
|
||||
attachmentToDelete.id,
|
||||
attachmentToDelete!.id,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
acknowledged: true,
|
||||
@ -155,10 +155,10 @@ describe('AttachmentController', () => {
|
||||
|
||||
it('should throw a NotFoundException when attempting to delete an attachment by id', async () => {
|
||||
await expect(
|
||||
attachmentController.deleteOne(attachmentToDelete.id),
|
||||
attachmentController.deleteOne(attachmentToDelete!.id),
|
||||
).rejects.toThrow(
|
||||
new NotFoundException(
|
||||
`Attachment with ID ${attachmentToDelete.id} not found`,
|
||||
`Attachment with ID ${attachmentToDelete!.id} not found`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
@ -146,7 +146,7 @@ export class AttachmentController extends BaseController<Attachment> {
|
||||
@Get('download/:id/:filename?')
|
||||
async download(
|
||||
@Param() params: AttachmentDownloadDto,
|
||||
): Promise<StreamableFile> {
|
||||
): Promise<StreamableFile | undefined> {
|
||||
const attachment = await this.attachmentService.findOne(params.id);
|
||||
|
||||
if (!attachment) {
|
||||
|
@ -100,32 +100,31 @@ export default class LlmNluHelper
|
||||
*
|
||||
* @returns An array of objects representing the found entities, with their `value`, `start`, and `end` positions.
|
||||
*/
|
||||
private findKeywordEntities(
|
||||
text: string,
|
||||
entity: NlpEntityFull,
|
||||
): NLU.ParseEntity[] {
|
||||
return entity.values
|
||||
.flatMap(({ value, expressions }) => {
|
||||
const allValues = [value, ...expressions];
|
||||
private findKeywordEntities(text: string, entity: NlpEntityFull) {
|
||||
return (
|
||||
entity.values
|
||||
.flatMap(({ value, expressions }) => {
|
||||
const allValues = [value, ...expressions];
|
||||
|
||||
// Filter the terms that are found in the text
|
||||
return allValues
|
||||
.flatMap((term) => {
|
||||
const regex = new RegExp(`\\b${term}\\b`, 'g');
|
||||
const matches = [...text.matchAll(regex)];
|
||||
// Filter the terms that are found in the text
|
||||
return allValues
|
||||
.flatMap((term) => {
|
||||
const regex = new RegExp(`\\b${term}\\b`, 'g');
|
||||
const matches = [...text.matchAll(regex)];
|
||||
|
||||
// Map matches to FoundEntity format
|
||||
return matches.map((match) => ({
|
||||
entity: entity.name,
|
||||
value: term,
|
||||
start: match.index!,
|
||||
end: match.index! + term.length,
|
||||
confidence: 1,
|
||||
}));
|
||||
})
|
||||
.shift();
|
||||
})
|
||||
.filter((v) => !!v);
|
||||
// Map matches to FoundEntity format
|
||||
return matches.map((match) => ({
|
||||
entity: entity.name,
|
||||
value: term,
|
||||
start: match.index!,
|
||||
end: match.index! + term.length,
|
||||
confidence: 1,
|
||||
}));
|
||||
})
|
||||
.shift();
|
||||
})
|
||||
.filter((v) => !!v) || []
|
||||
);
|
||||
}
|
||||
|
||||
async predict(text: string): Promise<NLU.ParseEntities> {
|
||||
@ -133,7 +132,7 @@ export default class LlmNluHelper
|
||||
const helper = await this.helperService.getDefaultLlmHelper();
|
||||
const defaultLanguage = await this.languageService.getDefaultLanguage();
|
||||
// Detect language
|
||||
const language = await helper.generateStructuredResponse<string>(
|
||||
const language = await helper.generateStructuredResponse<string>?.(
|
||||
`input text: ${text}`,
|
||||
settings.model,
|
||||
this.languageClassifierPrompt,
|
||||
@ -147,13 +146,13 @@ export default class LlmNluHelper
|
||||
{
|
||||
entity: 'language',
|
||||
value: language || defaultLanguage.code,
|
||||
confidence: undefined,
|
||||
confidence: 0.0,
|
||||
},
|
||||
];
|
||||
for await (const { name, doc, prompt, values } of this
|
||||
.traitClassifierPrompts) {
|
||||
const allowedValues = values.map(({ value }) => value);
|
||||
const result = await helper.generateStructuredResponse<string>(
|
||||
const result = await helper.generateStructuredResponse<string>?.(
|
||||
`input text: ${text}`,
|
||||
settings.model,
|
||||
prompt,
|
||||
@ -163,12 +162,13 @@ export default class LlmNluHelper
|
||||
enum: allowedValues.concat('unknown'),
|
||||
},
|
||||
);
|
||||
const safeValue = result.toLowerCase().trim();
|
||||
const value = allowedValues.includes(safeValue) ? safeValue : '';
|
||||
const safeValue = result?.toLowerCase().trim();
|
||||
const value =
|
||||
safeValue && allowedValues.includes(safeValue) ? safeValue : '';
|
||||
traits.push({
|
||||
entity: name,
|
||||
value,
|
||||
confidence: undefined,
|
||||
confidence: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ export default class LlmNluHelper
|
||||
});
|
||||
const entities = keywordEntities.flatMap((keywordEntity) =>
|
||||
this.findKeywordEntities(text, keywordEntity),
|
||||
);
|
||||
) as NLU.ParseEntity[];
|
||||
|
||||
return { entities: traits.concat(entities) };
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ describe('MigrationService', () => {
|
||||
|
||||
await service.run({
|
||||
action: MigrationAction.UP,
|
||||
version: null,
|
||||
version: undefined,
|
||||
isAutoMigrate: false,
|
||||
});
|
||||
|
||||
|
@ -28,6 +28,7 @@ import { Migration, MigrationDocument } from './migration.schema';
|
||||
import {
|
||||
MigrationAction,
|
||||
MigrationName,
|
||||
MigrationRunOneParams,
|
||||
MigrationRunParams,
|
||||
MigrationSuccessCallback,
|
||||
MigrationVersion,
|
||||
@ -237,7 +238,7 @@ module.exports = {
|
||||
*
|
||||
* @returns Resolves when the migration action is successfully executed or stops if the migration already exists.
|
||||
*/
|
||||
private async runOne({ version, action }: MigrationRunParams) {
|
||||
private async runOne({ version, action }: MigrationRunOneParams) {
|
||||
// Verify DB status
|
||||
const { exist, migrationDocument } = await this.verifyStatus({
|
||||
version,
|
||||
@ -255,7 +256,7 @@ module.exports = {
|
||||
http: this.httpService,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
if (result && migrationDocument) {
|
||||
await this.successCallback({
|
||||
version,
|
||||
action,
|
||||
|
@ -27,6 +27,10 @@ export interface MigrationRunParams {
|
||||
isAutoMigrate?: boolean;
|
||||
}
|
||||
|
||||
export interface MigrationRunOneParams extends MigrationRunParams {
|
||||
version: MigrationVersion;
|
||||
}
|
||||
|
||||
export interface MigrationSuccessCallback extends MigrationRunParams {
|
||||
migrationDocument: MigrationDocument;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ describe('NlpEntityController', () => {
|
||||
acc.push({
|
||||
...curr,
|
||||
values: nlpValueFixtures.filter(
|
||||
({ entity }) => parseInt(entity) === index,
|
||||
({ entity }) => parseInt(entity!) === index,
|
||||
) as NlpEntityFull['values'],
|
||||
lookups: curr.lookups!,
|
||||
builtin: curr.builtin!,
|
||||
|
@ -98,7 +98,7 @@ describe('NlpValueController', () => {
|
||||
acc.push({
|
||||
...curr,
|
||||
entity: nlpEntityFixtures[
|
||||
parseInt(curr.entity)
|
||||
parseInt(curr.entity!)
|
||||
] as NlpValueFull['entity'],
|
||||
builtin: curr.builtin!,
|
||||
expressions: curr.expressions!,
|
||||
@ -125,7 +125,7 @@ describe('NlpValueController', () => {
|
||||
(acc, curr) => {
|
||||
const ValueWithEntities = {
|
||||
...curr,
|
||||
entity: nlpEntities[parseInt(curr.entity)].id,
|
||||
entity: nlpEntities[parseInt(curr.entity!)].id,
|
||||
expressions: curr.expressions!,
|
||||
metadata: curr.metadata!,
|
||||
builtin: curr.builtin!,
|
||||
|
@ -75,8 +75,9 @@ export class NlpValueController extends BaseController<
|
||||
this.validate({
|
||||
dto: createNlpValueDto,
|
||||
allowedIds: {
|
||||
entity: (await this.nlpEntityService.findOne(createNlpValueDto.entity))
|
||||
?.id,
|
||||
entity: createNlpValueDto.entity
|
||||
? (await this.nlpEntityService.findOne(createNlpValueDto.entity))?.id
|
||||
: null,
|
||||
},
|
||||
});
|
||||
return await this.nlpValueService.create(createNlpValueDto);
|
||||
@ -167,7 +168,10 @@ export class NlpValueController extends BaseController<
|
||||
@Param('id') id: string,
|
||||
@Body() updateNlpValueDto: NlpValueUpdateDto,
|
||||
): Promise<NlpValue> {
|
||||
const result = await this.nlpValueService.updateOne(id, updateNlpValueDto);
|
||||
const result = await this.nlpValueService.updateOne(id, {
|
||||
...updateNlpValueDto,
|
||||
entity: updateNlpValueDto.entity || undefined,
|
||||
});
|
||||
if (!result) {
|
||||
this.logger.warn(`Unable to update NLP Value by id ${id}`);
|
||||
throw new NotFoundException(`NLP Value with ID ${id} not found`);
|
||||
|
@ -49,7 +49,7 @@ export class NlpValueCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsObjectId({ message: 'Entity must be a valid ObjectId' })
|
||||
entity: string;
|
||||
entity: string | null;
|
||||
}
|
||||
|
||||
export class NlpValueUpdateDto extends PartialType(NlpValueCreateDto) {}
|
||||
|
@ -87,7 +87,7 @@ describe('NlpValueRepository', () => {
|
||||
const ValueWithEntities = {
|
||||
...curr,
|
||||
entity: nlpEntityFixtures[
|
||||
parseInt(curr.entity)
|
||||
parseInt(curr.entity!)
|
||||
] as NlpValueFull['entity'],
|
||||
builtin: curr.builtin!,
|
||||
expressions: curr.expressions!,
|
||||
|
@ -38,7 +38,7 @@ export class NlpValueSeeder extends BaseSeeder<
|
||||
const entities = await this.nlpEntityRepository.findAll();
|
||||
const modelDtos = models.map((v) => ({
|
||||
...v,
|
||||
entity: entities.find(({ name }) => name === v.entity)?.id,
|
||||
entity: entities.find(({ name }) => name === v.entity)?.id || null,
|
||||
}));
|
||||
await this.repository.createMany(modelDtos);
|
||||
return true;
|
||||
|
@ -69,9 +69,7 @@ describe('NlpValueService', () => {
|
||||
nlpValues = await nlpValueRepository.findAll();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closeInMongodConnection();
|
||||
});
|
||||
afterAll(closeInMongodConnection);
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
@ -94,9 +92,7 @@ describe('NlpValueService', () => {
|
||||
(acc, curr) => {
|
||||
const ValueWithEntities = {
|
||||
...curr,
|
||||
entity: nlpEntityFixtures[
|
||||
parseInt(curr.entity)
|
||||
] as NlpValueFull['entity'],
|
||||
entity: nlpEntityFixtures[parseInt(curr.entity!)] as NlpEntity,
|
||||
expressions: curr.expressions!,
|
||||
metadata: curr.metadata!,
|
||||
builtin: curr.builtin!,
|
||||
|
@ -11,11 +11,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { BaseService } from '@/utils/generics/base-service';
|
||||
|
||||
import {
|
||||
NlpValueCreateDto,
|
||||
NlpValueDto,
|
||||
NlpValueUpdateDto,
|
||||
} from '../dto/nlp-value.dto';
|
||||
import { NlpValueCreateDto, NlpValueDto } from '../dto/nlp-value.dto';
|
||||
import { NlpValueRepository } from '../repositories/nlp-value.repository';
|
||||
import { NlpEntity } from '../schemas/nlp-entity.schema';
|
||||
import {
|
||||
@ -139,7 +135,7 @@ export class NlpValueService extends BaseService<
|
||||
expressions: vMap[e.value].expressions?.concat([
|
||||
sampleText.slice(e.start, e.end),
|
||||
]),
|
||||
} as NlpValueUpdateDto);
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(synonymsToAdd);
|
||||
|
@ -48,7 +48,7 @@ export async function moveFiles(
|
||||
const files = await fs.promises.readdir(sourceFolder);
|
||||
|
||||
// Filter only files (skip directories)
|
||||
const filePaths = [];
|
||||
const filePaths: string[] = [];
|
||||
for (const file of files) {
|
||||
const filePath = join(sourceFolder, file);
|
||||
const stat = await fs.promises.stat(filePath);
|
||||
|
@ -7,10 +7,10 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
PipeTransform,
|
||||
ArgumentMetadata,
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
PipeTransform,
|
||||
} from '@nestjs/common';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { validate } from 'class-validator';
|
||||
@ -33,7 +33,7 @@ export class ObjectIdPipe implements PipeTransform<string, Promise<string>> {
|
||||
async transform(value: string, { type, data }: ArgumentMetadata) {
|
||||
if (typeof value === 'string' && data === 'id' && type === 'param') {
|
||||
const errors = await this.getErrors(value);
|
||||
if (errors)
|
||||
if (errors.constraints)
|
||||
throw new BadRequestException(Object.values(errors.constraints)[0]);
|
||||
} else if (
|
||||
typeof value === 'object' &&
|
||||
@ -45,7 +45,7 @@ export class ObjectIdPipe implements PipeTransform<string, Promise<string>> {
|
||||
if (param.startsWith('id')) {
|
||||
const errors = await this.getErrors(String(paramValue));
|
||||
|
||||
if (errors)
|
||||
if (errors.constraints)
|
||||
throw new BadRequestException(
|
||||
Object.values(errors.constraints)[0],
|
||||
);
|
||||
|
6
api/src/utils/test/fixtures/conversation.ts
vendored
6
api/src/utils/test/fixtures/conversation.ts
vendored
@ -136,8 +136,10 @@ export const installConversationTypeFixtures = async () => {
|
||||
conversationFixtures.map((conversationFixture) => ({
|
||||
...conversationFixture,
|
||||
sender: subscribers[parseInt(conversationFixture.sender)].id,
|
||||
current: blocks[parseInt(conversationFixture.current)].id,
|
||||
next: conversationFixture.next.map((n) => blocks[parseInt(n)].id),
|
||||
current: conversationFixture?.current
|
||||
? blocks[parseInt(conversationFixture.current)].id
|
||||
: null,
|
||||
next: conversationFixture.next?.map((n) => blocks[parseInt(n)].id),
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
10
api/src/utils/test/fixtures/nlpvalue.ts
vendored
10
api/src/utils/test/fixtures/nlpvalue.ts
vendored
@ -51,12 +51,10 @@ export const installNlpValueFixtures = async () => {
|
||||
|
||||
const NlpValue = mongoose.model(NlpValueModel.name, NlpValueModel.schema);
|
||||
const nlpValues = await NlpValue.insertMany(
|
||||
nlpValueFixtures.map((v) => {
|
||||
return {
|
||||
...v,
|
||||
entity: nlpEntities[parseInt(v.entity)].id,
|
||||
};
|
||||
}),
|
||||
nlpValueFixtures.map((v) => ({
|
||||
...v,
|
||||
entity: v?.entity ? nlpEntities[parseInt(v.entity)].id : null,
|
||||
})),
|
||||
);
|
||||
return { nlpEntities, nlpValues };
|
||||
};
|
||||
|
@ -13,7 +13,7 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
|
Loading…
Reference in New Issue
Block a user