mirror of
https://github.com/hexastack/hexabot
synced 2025-04-10 15:55:55 +00:00
fix: apply strict null checks updates to the Chat Module
This commit is contained in:
parent
4f88370c69
commit
90a7c2f5c2
@ -163,8 +163,8 @@ export class ChannelService {
|
||||
{
|
||||
id: req.session.passport.user.id,
|
||||
foreign_id: req.session.passport.user.id,
|
||||
first_name: req.session.passport.user.first_name,
|
||||
last_name: req.session.passport.user.last_name,
|
||||
first_name: req.session.passport.user.first_name || 'Anonymous',
|
||||
last_name: req.session.passport.user.last_name || 'Anonymous',
|
||||
locale: '',
|
||||
language: '',
|
||||
gender: '',
|
||||
|
@ -61,11 +61,11 @@ describe('BlockController', () => {
|
||||
let blockController: BlockController;
|
||||
let blockService: BlockService;
|
||||
let categoryService: CategoryService;
|
||||
let category: Category;
|
||||
let block: Block;
|
||||
let blockToDelete: Block;
|
||||
let hasNextBlocks: Block;
|
||||
let hasPreviousBlocks: Block;
|
||||
let category: Category | null;
|
||||
let block: Block | null;
|
||||
let blockToDelete: Block | null;
|
||||
let hasNextBlocks: Block | null;
|
||||
let hasPreviousBlocks: Block | null;
|
||||
const FIELDS_TO_POPULATE = [
|
||||
'trigger_labels',
|
||||
'assign_labels',
|
||||
@ -162,9 +162,9 @@ describe('BlockController', () => {
|
||||
const result = await blockController.find([], {});
|
||||
const blocksWithCategory = blockFixtures.map((blockFixture) => ({
|
||||
...blockFixture,
|
||||
category: category.id,
|
||||
category: category!.id,
|
||||
nextBlocks:
|
||||
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks.id] : [],
|
||||
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks!.id] : [],
|
||||
}));
|
||||
|
||||
expect(blockService.find).toHaveBeenCalledWith({}, undefined);
|
||||
@ -185,6 +185,7 @@ describe('BlockController', () => {
|
||||
blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [],
|
||||
nextBlocks:
|
||||
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
|
||||
attachedToBlock: null,
|
||||
}));
|
||||
|
||||
expect(blockService.findAndPopulate).toHaveBeenCalledWith({}, undefined);
|
||||
@ -195,13 +196,13 @@ describe('BlockController', () => {
|
||||
describe('findOne', () => {
|
||||
it('should find one block by id', async () => {
|
||||
jest.spyOn(blockService, 'findOne');
|
||||
const result = await blockController.findOne(hasNextBlocks.id, []);
|
||||
expect(blockService.findOne).toHaveBeenCalledWith(hasNextBlocks.id);
|
||||
const result = await blockController.findOne(hasNextBlocks!.id, []);
|
||||
expect(blockService.findOne).toHaveBeenCalledWith(hasNextBlocks!.id);
|
||||
expect(result).toEqualPayload(
|
||||
{
|
||||
...blockFixtures.find(({ name }) => name === hasNextBlocks.name),
|
||||
category: category.id,
|
||||
nextBlocks: [hasPreviousBlocks.id],
|
||||
...blockFixtures.find(({ name }) => name === hasNextBlocks!.name),
|
||||
category: category!.id,
|
||||
nextBlocks: [hasPreviousBlocks!.id],
|
||||
},
|
||||
[...IGNORED_TEST_FIELDS, 'attachedToBlock'],
|
||||
);
|
||||
@ -210,16 +211,17 @@ describe('BlockController', () => {
|
||||
it('should find one block by id, and populate its category and previousBlocks', async () => {
|
||||
jest.spyOn(blockService, 'findOneAndPopulate');
|
||||
const result = await blockController.findOne(
|
||||
hasPreviousBlocks.id,
|
||||
hasPreviousBlocks!.id,
|
||||
FIELDS_TO_POPULATE,
|
||||
);
|
||||
expect(blockService.findOneAndPopulate).toHaveBeenCalledWith(
|
||||
hasPreviousBlocks.id,
|
||||
hasPreviousBlocks!.id,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...blockFixtures.find(({ name }) => name === 'hasPreviousBlocks'),
|
||||
category,
|
||||
previousBlocks: [hasNextBlocks],
|
||||
attachedToBlock: null,
|
||||
});
|
||||
});
|
||||
|
||||
@ -227,14 +229,15 @@ describe('BlockController', () => {
|
||||
jest.spyOn(blockService, 'findOneAndPopulate');
|
||||
block = await blockService.findOne({ name: 'attachment' });
|
||||
const result = await blockController.findOne(
|
||||
block.id,
|
||||
block!.id,
|
||||
FIELDS_TO_POPULATE,
|
||||
);
|
||||
expect(blockService.findOneAndPopulate).toHaveBeenCalledWith(block.id);
|
||||
expect(blockService.findOneAndPopulate).toHaveBeenCalledWith(block!.id);
|
||||
expect(result).toEqualPayload({
|
||||
...blockFixtures.find(({ name }) => name === 'attachment'),
|
||||
category,
|
||||
previousBlocks: [],
|
||||
attachedToBlock: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -244,12 +247,12 @@ describe('BlockController', () => {
|
||||
jest.spyOn(blockService, 'create');
|
||||
const mockedBlockCreateDto: BlockCreateDto = {
|
||||
name: 'block with nextBlocks',
|
||||
nextBlocks: [hasNextBlocks.id],
|
||||
nextBlocks: [hasNextBlocks!.id],
|
||||
patterns: ['Hi'],
|
||||
trigger_labels: [],
|
||||
assign_labels: [],
|
||||
trigger_channels: [],
|
||||
category: category.id,
|
||||
category: category!.id,
|
||||
options: {
|
||||
typing: 0,
|
||||
fallback: {
|
||||
@ -281,15 +284,17 @@ describe('BlockController', () => {
|
||||
describe('deleteOne', () => {
|
||||
it('should delete block', async () => {
|
||||
jest.spyOn(blockService, 'deleteOne');
|
||||
const result = await blockController.deleteOne(blockToDelete.id);
|
||||
const result = await blockController.deleteOne(blockToDelete!.id);
|
||||
|
||||
expect(blockService.deleteOne).toHaveBeenCalledWith(blockToDelete.id);
|
||||
expect(blockService.deleteOne).toHaveBeenCalledWith(blockToDelete!.id);
|
||||
expect(result).toEqual({ acknowledged: true, deletedCount: 1 });
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when attempting to delete a block by id', async () => {
|
||||
await expect(blockController.deleteOne(blockToDelete.id)).rejects.toThrow(
|
||||
new NotFoundException(`Block with ID ${blockToDelete.id} not found`),
|
||||
await expect(
|
||||
blockController.deleteOne(blockToDelete!.id),
|
||||
).rejects.toThrow(
|
||||
new NotFoundException(`Block with ID ${blockToDelete!.id} not found`),
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -300,16 +305,16 @@ describe('BlockController', () => {
|
||||
const updateBlock: BlockUpdateDto = {
|
||||
name: 'modified block name',
|
||||
};
|
||||
const result = await blockController.updateOne(block.id, updateBlock);
|
||||
const result = await blockController.updateOne(block!.id, updateBlock);
|
||||
|
||||
expect(blockService.updateOne).toHaveBeenCalledWith(
|
||||
block.id,
|
||||
block!.id,
|
||||
updateBlock,
|
||||
);
|
||||
expect(result).toEqualPayload(
|
||||
{
|
||||
...blockFixtures.find(({ name }) => name === block.name),
|
||||
category: category.id,
|
||||
...blockFixtures.find(({ name }) => name === block!.name),
|
||||
category: category!.id,
|
||||
...updateBlock,
|
||||
},
|
||||
[...IGNORED_TEST_FIELDS, 'attachedToBlock'],
|
||||
@ -322,9 +327,9 @@ describe('BlockController', () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
blockController.updateOne(blockToDelete.id, updateBlock),
|
||||
blockController.updateOne(blockToDelete!.id, updateBlock),
|
||||
).rejects.toThrow(
|
||||
new NotFoundException(`Block with ID ${blockToDelete.id} not found`),
|
||||
new NotFoundException(`Block with ID ${blockToDelete!.id} not found`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -219,9 +219,12 @@ export class BlockController extends BaseController<
|
||||
this.validate({
|
||||
dto: block,
|
||||
allowedIds: {
|
||||
category: (await this.categoryService.findOne(block.category))?.id,
|
||||
attachedBlock: (await this.blockService.findOne(block.attachedBlock))
|
||||
?.id,
|
||||
category: block.category
|
||||
? (await this.categoryService.findOne(block.category))?.id
|
||||
: null,
|
||||
attachedBlock: block.attachedBlock
|
||||
? (await this.blockService.findOne(block.attachedBlock))?.id
|
||||
: null,
|
||||
nextBlocks: (
|
||||
await this.blockService.find({
|
||||
_id: {
|
||||
|
@ -36,8 +36,8 @@ import { ContextVarController } from './context-var.controller';
|
||||
describe('ContextVarController', () => {
|
||||
let contextVarController: ContextVarController;
|
||||
let contextVarService: ContextVarService;
|
||||
let contextVar: ContextVar;
|
||||
let contextVarToDelete: ContextVar;
|
||||
let contextVar: ContextVar | null;
|
||||
let contextVarToDelete: ContextVar | null;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
@ -91,11 +91,11 @@ describe('ContextVarController', () => {
|
||||
describe('findOne', () => {
|
||||
it('should return the existing contextVar', async () => {
|
||||
jest.spyOn(contextVarService, 'findOne');
|
||||
const result = await contextVarController.findOne(contextVar.id);
|
||||
const result = await contextVarController.findOne(contextVar!.id);
|
||||
|
||||
expect(contextVarService.findOne).toHaveBeenCalledWith(contextVar.id);
|
||||
expect(contextVarService.findOne).toHaveBeenCalledWith(contextVar!.id);
|
||||
expect(result).toEqualPayload(
|
||||
contextVarFixtures.find(({ label }) => label === contextVar.label),
|
||||
contextVarFixtures.find(({ label }) => label === contextVar!.label)!,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -121,11 +121,11 @@ describe('ContextVarController', () => {
|
||||
it('should delete a contextVar by id', async () => {
|
||||
jest.spyOn(contextVarService, 'deleteOne');
|
||||
const result = await contextVarController.deleteOne(
|
||||
contextVarToDelete.id,
|
||||
contextVarToDelete!.id,
|
||||
);
|
||||
|
||||
expect(contextVarService.deleteOne).toHaveBeenCalledWith(
|
||||
contextVarToDelete.id,
|
||||
contextVarToDelete!.id,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
acknowledged: true,
|
||||
@ -135,10 +135,10 @@ describe('ContextVarController', () => {
|
||||
|
||||
it('should throw a NotFoundException when attempting to delete a contextVar by id', async () => {
|
||||
await expect(
|
||||
contextVarController.deleteOne(contextVarToDelete.id),
|
||||
contextVarController.deleteOne(contextVarToDelete!.id),
|
||||
).rejects.toThrow(
|
||||
new NotFoundException(
|
||||
`Context var with ID ${contextVarToDelete.id} not found.`,
|
||||
`Context var with ID ${contextVarToDelete!.id} not found.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -152,12 +152,12 @@ describe('ContextVarController', () => {
|
||||
.spyOn(contextVarService, 'deleteMany')
|
||||
.mockResolvedValue(deleteResult);
|
||||
const result = await contextVarController.deleteMany([
|
||||
contextVarToDelete.id,
|
||||
contextVar.id,
|
||||
contextVarToDelete!.id,
|
||||
contextVar!.id,
|
||||
]);
|
||||
|
||||
expect(contextVarService.deleteMany).toHaveBeenCalledWith({
|
||||
_id: { $in: [contextVarToDelete.id, contextVar.id] },
|
||||
_id: { $in: [contextVarToDelete!.id, contextVar!.id] },
|
||||
});
|
||||
expect(result).toEqual(deleteResult);
|
||||
});
|
||||
@ -175,7 +175,10 @@ describe('ContextVarController', () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
contextVarController.deleteMany([contextVarToDelete.id, contextVar.id]),
|
||||
contextVarController.deleteMany([
|
||||
contextVarToDelete!.id,
|
||||
contextVar!.id,
|
||||
]),
|
||||
).rejects.toThrow(
|
||||
new NotFoundException('Context vars with provided IDs not found'),
|
||||
);
|
||||
@ -189,16 +192,16 @@ describe('ContextVarController', () => {
|
||||
it('should return updated contextVar', async () => {
|
||||
jest.spyOn(contextVarService, 'updateOne');
|
||||
const result = await contextVarController.updateOne(
|
||||
contextVar.id,
|
||||
contextVar!.id,
|
||||
contextVarUpdatedDto,
|
||||
);
|
||||
|
||||
expect(contextVarService.updateOne).toHaveBeenCalledWith(
|
||||
contextVar.id,
|
||||
contextVar!.id,
|
||||
contextVarUpdatedDto,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...contextVarFixtures.find(({ label }) => label === contextVar.label),
|
||||
...contextVarFixtures.find(({ label }) => label === contextVar!.label),
|
||||
...contextVarUpdatedDto,
|
||||
});
|
||||
});
|
||||
@ -206,12 +209,12 @@ describe('ContextVarController', () => {
|
||||
it('should throw a NotFoundException when attempting to update an non existing contextVar by id', async () => {
|
||||
await expect(
|
||||
contextVarController.updateOne(
|
||||
contextVarToDelete.id,
|
||||
contextVarToDelete!.id,
|
||||
contextVarUpdatedDto,
|
||||
),
|
||||
).rejects.toThrow(
|
||||
new NotFoundException(
|
||||
`ContextVar with ID ${contextVarToDelete.id} not found`,
|
||||
`ContextVar with ID ${contextVarToDelete!.id} not found`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
@ -53,10 +53,10 @@ describe('MessageController', () => {
|
||||
let messageService: MessageService;
|
||||
let subscriberService: SubscriberService;
|
||||
let userService: UserService;
|
||||
let sender: Subscriber;
|
||||
let recipient: Subscriber;
|
||||
let user: User;
|
||||
let message: Message;
|
||||
let sender: Subscriber | null;
|
||||
let recipient: Subscriber | null;
|
||||
let user: User | null;
|
||||
let message: Message | null;
|
||||
let allMessages: Message[];
|
||||
let allUsers: User[];
|
||||
let allSubscribers: Subscriber[];
|
||||
@ -129,9 +129,9 @@ describe('MessageController', () => {
|
||||
subscriberService = module.get<SubscriberService>(SubscriberService);
|
||||
messageController = module.get<MessageController>(MessageController);
|
||||
message = await messageService.findOne({ mid: 'mid-1' });
|
||||
sender = await subscriberService.findOne(message.sender);
|
||||
recipient = await subscriberService.findOne(message.recipient);
|
||||
user = await userService.findOne(message.sentBy);
|
||||
sender = await subscriberService.findOne(message!.sender!);
|
||||
recipient = await subscriberService.findOne(message!.recipient!);
|
||||
user = await userService.findOne(message!.sentBy!);
|
||||
allSubscribers = await subscriberService.findAll();
|
||||
allUsers = await userService.findAll();
|
||||
allMessages = await messageService.findAll();
|
||||
@ -153,31 +153,31 @@ describe('MessageController', () => {
|
||||
describe('findOne', () => {
|
||||
it('should find message by id, and populate its corresponding sender and recipient', async () => {
|
||||
jest.spyOn(messageService, 'findOneAndPopulate');
|
||||
const result = await messageController.findOne(message.id, [
|
||||
const result = await messageController.findOne(message!.id, [
|
||||
'sender',
|
||||
'recipient',
|
||||
]);
|
||||
|
||||
expect(messageService.findOneAndPopulate).toHaveBeenCalledWith(
|
||||
message.id,
|
||||
message!.id,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...messageFixtures.find(({ mid }) => mid === message.mid),
|
||||
...messageFixtures.find(({ mid }) => mid === message!.mid),
|
||||
sender,
|
||||
recipient,
|
||||
sentBy: user.id,
|
||||
sentBy: user!.id,
|
||||
});
|
||||
});
|
||||
it('should find message by id', async () => {
|
||||
jest.spyOn(messageService, 'findOne');
|
||||
const result = await messageController.findOne(message.id, []);
|
||||
const result = await messageController.findOne(message!.id, []);
|
||||
|
||||
expect(messageService.findOne).toHaveBeenCalledWith(message.id);
|
||||
expect(messageService.findOne).toHaveBeenCalledWith(message!.id);
|
||||
expect(result).toEqualPayload({
|
||||
...messageFixtures.find(({ mid }) => mid === message.mid),
|
||||
sender: sender.id,
|
||||
recipient: recipient.id,
|
||||
sentBy: user.id,
|
||||
...messageFixtures.find(({ mid }) => mid === message!.mid),
|
||||
sender: sender!.id,
|
||||
recipient: recipient!.id,
|
||||
sentBy: user!.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -189,10 +189,10 @@ describe('MessageController', () => {
|
||||
const result = await messageController.findPage(pageQuery, [], {});
|
||||
const messagesWithSenderAndRecipient = allMessages.map((message) => ({
|
||||
...message,
|
||||
sender: allSubscribers.find(({ id }) => id === message['sender']).id,
|
||||
recipient: allSubscribers.find(({ id }) => id === message['recipient'])
|
||||
.id,
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy']).id,
|
||||
sender: allSubscribers.find(({ id }) => id === message.sender)?.id,
|
||||
recipient: allSubscribers.find(({ id }) => id === message.recipient)
|
||||
?.id,
|
||||
sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id,
|
||||
}));
|
||||
|
||||
expect(messageService.find).toHaveBeenCalledWith({}, pageQuery);
|
||||
@ -208,9 +208,9 @@ describe('MessageController', () => {
|
||||
);
|
||||
const messages = allMessages.map((message) => ({
|
||||
...message,
|
||||
sender: allSubscribers.find(({ id }) => id === message['sender']),
|
||||
recipient: allSubscribers.find(({ id }) => id === message['recipient']),
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy']).id,
|
||||
sender: allSubscribers.find(({ id }) => id === message.sender),
|
||||
recipient: allSubscribers.find(({ id }) => id === message.recipient),
|
||||
sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id,
|
||||
}));
|
||||
|
||||
expect(messageService.findAndPopulate).toHaveBeenCalledWith(
|
||||
|
@ -48,7 +48,7 @@ describe('SubscriberController', () => {
|
||||
let subscriberService: SubscriberService;
|
||||
let labelService: LabelService;
|
||||
let userService: UserService;
|
||||
let subscriber: Subscriber;
|
||||
let subscriber: Subscriber | null;
|
||||
let allLabels: Label[];
|
||||
let allSubscribers: Subscriber[];
|
||||
let allUsers: User[];
|
||||
@ -114,38 +114,39 @@ describe('SubscriberController', () => {
|
||||
describe('findOne', () => {
|
||||
it('should find one subscriber by id', async () => {
|
||||
jest.spyOn(subscriberService, 'findOne');
|
||||
const result = await subscriberService.findOne(subscriber.id);
|
||||
const result = await subscriberService.findOne(subscriber!.id);
|
||||
const labelIDs = allLabels
|
||||
.filter((label) => subscriber.labels.includes(label.id))
|
||||
.filter((label) => subscriber!.labels.includes(label.id))
|
||||
.map(({ id }) => id);
|
||||
|
||||
expect(subscriberService.findOne).toHaveBeenCalledWith(subscriber.id);
|
||||
expect(subscriberService.findOne).toHaveBeenCalledWith(subscriber!.id);
|
||||
expect(result).toEqualPayload({
|
||||
...subscriberFixtures.find(
|
||||
({ first_name }) => first_name === subscriber.first_name,
|
||||
({ first_name }) => first_name === subscriber!.first_name,
|
||||
),
|
||||
labels: labelIDs,
|
||||
assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id).id,
|
||||
assignedTo: allUsers.find(({ id }) => subscriber!.assignedTo === id)
|
||||
?.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should find one subscriber by id, and populate its corresponding labels', async () => {
|
||||
jest.spyOn(subscriberService, 'findOneAndPopulate');
|
||||
const result = await subscriberController.findOne(subscriber.id, [
|
||||
const result = await subscriberController.findOne(subscriber!.id, [
|
||||
'labels',
|
||||
]);
|
||||
|
||||
expect(subscriberService.findOneAndPopulate).toHaveBeenCalledWith(
|
||||
subscriber.id,
|
||||
subscriber!.id,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...subscriberFixtures.find(
|
||||
({ first_name }) => first_name === subscriber.first_name,
|
||||
({ first_name }) => first_name === subscriber!.first_name,
|
||||
),
|
||||
labels: allLabels.filter((label) =>
|
||||
subscriber.labels.includes(label.id),
|
||||
subscriber!.labels.includes(label.id),
|
||||
),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber!.assignedTo === id),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -177,7 +178,7 @@ describe('SubscriberController', () => {
|
||||
({ labels, ...rest }) => ({
|
||||
...rest,
|
||||
labels: allLabels.filter((label) => labels.includes(label.id)),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber!.assignedTo === id),
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -145,7 +145,9 @@ export class SubscriberController extends BaseController<
|
||||
* @returns A streamable file containing the avatar image.
|
||||
*/
|
||||
@Get(':id/profile_pic')
|
||||
async getAvatar(@Param('id') id: string): Promise<StreamableFile> {
|
||||
async getAvatar(
|
||||
@Param('id') id: string,
|
||||
): Promise<StreamableFile | undefined> {
|
||||
const subscriber = await this.subscriberService.findOneAndPopulate(id);
|
||||
|
||||
if (!subscriber) {
|
||||
|
@ -89,7 +89,7 @@ export class BlockCreateDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@IsObjectId({ message: 'Category must be a valid objectId' })
|
||||
category: string;
|
||||
category: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Block has started conversation',
|
||||
|
@ -31,9 +31,9 @@ describe('BlockRepository', () => {
|
||||
let blockRepository: BlockRepository;
|
||||
let categoryRepository: CategoryRepository;
|
||||
let blockModel: Model<Block>;
|
||||
let category: Category;
|
||||
let hasPreviousBlocks: Block;
|
||||
let hasNextBlocks: Block;
|
||||
let category: Category | null;
|
||||
let hasPreviousBlocks: Block | null;
|
||||
let hasNextBlocks: Block | null;
|
||||
let validIds: string[];
|
||||
let validCategory: string;
|
||||
|
||||
@ -67,16 +67,19 @@ describe('BlockRepository', () => {
|
||||
it('should find one block by id, and populate its trigger_labels, assign_labels, nextBlocks, attachedBlock, category,previousBlocks', async () => {
|
||||
jest.spyOn(blockModel, 'findById');
|
||||
|
||||
const result = await blockRepository.findOneAndPopulate(hasNextBlocks.id);
|
||||
const result = await blockRepository.findOneAndPopulate(
|
||||
hasNextBlocks!.id,
|
||||
);
|
||||
expect(blockModel.findById).toHaveBeenCalledWith(
|
||||
hasNextBlocks.id,
|
||||
hasNextBlocks!.id,
|
||||
undefined,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...blockFixtures.find(({ name }) => name === hasNextBlocks.name),
|
||||
...blockFixtures.find(({ name }) => name === hasNextBlocks!.name),
|
||||
category,
|
||||
nextBlocks: [hasPreviousBlocks],
|
||||
previousBlocks: [],
|
||||
attachedToBlock: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -93,6 +96,7 @@ describe('BlockRepository', () => {
|
||||
blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [],
|
||||
nextBlocks:
|
||||
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
|
||||
attachedToBlock: null,
|
||||
}));
|
||||
|
||||
expect(blockModel.find).toHaveBeenCalledWith({}, undefined);
|
||||
@ -110,6 +114,7 @@ describe('BlockRepository', () => {
|
||||
blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [],
|
||||
nextBlocks:
|
||||
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
|
||||
attachedToBlock: null,
|
||||
}));
|
||||
|
||||
expect(blockModel.find).toHaveBeenCalledWith({}, undefined);
|
||||
@ -191,7 +196,7 @@ describe('BlockRepository', () => {
|
||||
category: validCategory,
|
||||
nextBlocks: [],
|
||||
attachedBlock: null,
|
||||
} as Block);
|
||||
} as unknown as Block);
|
||||
|
||||
const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne');
|
||||
|
||||
@ -233,7 +238,7 @@ describe('BlockRepository', () => {
|
||||
attachedBlock: null,
|
||||
nextBlocks: [validIds[0], validIds[1]],
|
||||
},
|
||||
] as Block[];
|
||||
] as unknown as Block[];
|
||||
|
||||
const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne');
|
||||
|
||||
|
@ -32,7 +32,7 @@ export class ContextVarRepository extends BaseRepository<ContextVar> {
|
||||
@Optional() blockService?: BlockService,
|
||||
) {
|
||||
super(eventEmitter, model, ContextVar);
|
||||
this.blockService = blockService;
|
||||
if (blockService) this.blockService = blockService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,19 +63,22 @@ describe('MessageRepository', () => {
|
||||
it('should find one message by id, and populate its sender and recipient', async () => {
|
||||
jest.spyOn(messageModel, 'findById');
|
||||
const message = await messageRepository.findOne({ mid: 'mid-1' });
|
||||
const sender = await subscriberRepository.findOne(message['sender']);
|
||||
const sender = await subscriberRepository.findOne(message!['sender']);
|
||||
const recipient = await subscriberRepository.findOne(
|
||||
message['recipient'],
|
||||
message!['recipient'],
|
||||
);
|
||||
const user = await userRepository.findOne(message['sentBy']);
|
||||
const result = await messageRepository.findOneAndPopulate(message.id);
|
||||
const user = await userRepository.findOne(message!['sentBy']);
|
||||
const result = await messageRepository.findOneAndPopulate(message!.id);
|
||||
|
||||
expect(messageModel.findById).toHaveBeenCalledWith(message.id, undefined);
|
||||
expect(messageModel.findById).toHaveBeenCalledWith(
|
||||
message!.id,
|
||||
undefined,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...messageFixtures.find(({ mid }) => mid === message.mid),
|
||||
...messageFixtures.find(({ mid }) => mid === message!.mid),
|
||||
sender,
|
||||
recipient,
|
||||
sentBy: user.id,
|
||||
sentBy: user!.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -92,7 +95,7 @@ describe('MessageRepository', () => {
|
||||
...message,
|
||||
sender: allSubscribers.find(({ id }) => id === message['sender']),
|
||||
recipient: allSubscribers.find(({ id }) => id === message['recipient']),
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy']).id,
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy'])?.id,
|
||||
}));
|
||||
|
||||
expect(messageModel.find).toHaveBeenCalledWith({}, undefined);
|
||||
|
@ -107,20 +107,20 @@ describe('SubscriberRepository', () => {
|
||||
});
|
||||
const allLabels = await labelRepository.findAll();
|
||||
const result = await subscriberRepository.findOneAndPopulate(
|
||||
subscriber.id,
|
||||
subscriber!.id,
|
||||
);
|
||||
const subscriberWithLabels = {
|
||||
...subscriberFixtures.find(
|
||||
({ first_name }) => first_name === subscriber.first_name,
|
||||
({ first_name }) => first_name === subscriber!.first_name,
|
||||
),
|
||||
labels: allLabels.filter((label) =>
|
||||
subscriber.labels.includes(label.id),
|
||||
subscriber!.labels.includes(label.id),
|
||||
),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber!.assignedTo === id),
|
||||
};
|
||||
|
||||
expect(subscriberModel.findById).toHaveBeenCalledWith(
|
||||
subscriber.id,
|
||||
subscriber!.id,
|
||||
undefined,
|
||||
);
|
||||
expect(result).toEqualPayload(subscriberWithLabels);
|
||||
|
@ -134,7 +134,7 @@ export class SubscriberRepository extends BaseRepository<
|
||||
*/
|
||||
async findOneByForeignIdAndPopulate(id: string): Promise<SubscriberFull> {
|
||||
const query = this.findByForeignIdQuery(id).populate(this.populate);
|
||||
const [result] = await this.execute(query, this.clsPopulate);
|
||||
const [result] = await this.execute(query, SubscriberFull);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ export class SubscriberRepository extends BaseRepository<
|
||||
async updateOneByForeignIdQuery(
|
||||
id: string,
|
||||
updates: SubscriberUpdateDto,
|
||||
): Promise<Subscriber> {
|
||||
): Promise<Subscriber | null> {
|
||||
return await this.updateOne({ foreign_id: id }, updates);
|
||||
}
|
||||
|
||||
@ -160,7 +160,9 @@ export class SubscriberRepository extends BaseRepository<
|
||||
*
|
||||
* @returns The updated subscriber entity.
|
||||
*/
|
||||
async handBackByForeignIdQuery(foreignId: string): Promise<Subscriber> {
|
||||
async handBackByForeignIdQuery(
|
||||
foreignId: string,
|
||||
): Promise<Subscriber | null> {
|
||||
return await this.updateOne(
|
||||
{
|
||||
foreign_id: foreignId,
|
||||
@ -183,7 +185,7 @@ export class SubscriberRepository extends BaseRepository<
|
||||
async handOverByForeignIdQuery(
|
||||
foreignId: string,
|
||||
userId: string,
|
||||
): Promise<Subscriber> {
|
||||
): Promise<Subscriber | null> {
|
||||
return await this.updateOne(
|
||||
{
|
||||
foreign_id: foreignId,
|
||||
|
@ -143,13 +143,13 @@ export class Block extends BlockStub {
|
||||
attachedBlock?: string;
|
||||
|
||||
@Transform(({ obj }) => obj.category.toString())
|
||||
category: string;
|
||||
category: string | null;
|
||||
|
||||
@Exclude()
|
||||
previousBlocks?: never;
|
||||
|
||||
@Exclude()
|
||||
attachedToBlock?: never | null;
|
||||
attachedToBlock?: never;
|
||||
}
|
||||
|
||||
@Schema({ timestamps: true })
|
||||
@ -161,7 +161,7 @@ export class BlockFull extends BlockStub {
|
||||
assign_labels: Label[];
|
||||
|
||||
@Type(() => Block)
|
||||
nextBlocks?: Block[];
|
||||
nextBlocks: Block[];
|
||||
|
||||
@Type(() => Block)
|
||||
attachedBlock?: Block;
|
||||
|
@ -86,7 +86,7 @@ export class SubscriberStub extends BaseSchema {
|
||||
type: Date,
|
||||
default: null,
|
||||
})
|
||||
assignedAt?: Date;
|
||||
assignedAt?: Date | null;
|
||||
|
||||
@Prop({
|
||||
type: Date,
|
||||
@ -132,10 +132,10 @@ export class Subscriber extends SubscriberStub {
|
||||
labels: string[];
|
||||
|
||||
@Transform(({ obj }) => (obj.assignedTo ? obj.assignedTo.toString() : null))
|
||||
assignedTo?: string;
|
||||
assignedTo?: string | null;
|
||||
|
||||
@Transform(({ obj }) => obj.avatar?.toString() || null)
|
||||
avatar?: string;
|
||||
avatar?: string | null;
|
||||
}
|
||||
|
||||
@Schema({ timestamps: true })
|
||||
|
@ -74,10 +74,10 @@ import { CategoryService } from './category.service';
|
||||
describe('BlockService', () => {
|
||||
let blockRepository: BlockRepository;
|
||||
let categoryRepository: CategoryRepository;
|
||||
let category: Category;
|
||||
let block: Block;
|
||||
let category: Category | null;
|
||||
let block: Block | null;
|
||||
let blockService: BlockService;
|
||||
let hasPreviousBlocks: Block;
|
||||
let hasPreviousBlocks: Block | null;
|
||||
let contentService: ContentService;
|
||||
let contentTypeService: ContentTypeService;
|
||||
let settingService: SettingService;
|
||||
@ -168,10 +168,10 @@ describe('BlockService', () => {
|
||||
describe('findOneAndPopulate', () => {
|
||||
it('should find one block by id, and populate its trigger_labels, assign_labels,attachedBlock,category,nextBlocks', async () => {
|
||||
jest.spyOn(blockRepository, 'findOneAndPopulate');
|
||||
const result = await blockService.findOneAndPopulate(block.id);
|
||||
const result = await blockService.findOneAndPopulate(block!.id);
|
||||
|
||||
expect(blockRepository.findOneAndPopulate).toHaveBeenCalledWith(
|
||||
block.id,
|
||||
block!.id,
|
||||
undefined,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
@ -179,6 +179,7 @@ describe('BlockService', () => {
|
||||
category,
|
||||
nextBlocks: [hasPreviousBlocks],
|
||||
previousBlocks: [],
|
||||
attachedToBlock: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -194,6 +195,7 @@ describe('BlockService', () => {
|
||||
blockFixture.name === 'hasPreviousBlocks' ? [block] : [],
|
||||
nextBlocks:
|
||||
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
|
||||
attachedToBlock: null,
|
||||
}));
|
||||
|
||||
expect(blockRepository.findAndPopulate).toHaveBeenCalledWith(
|
||||
@ -380,7 +382,7 @@ describe('BlockService', () => {
|
||||
},
|
||||
blockGetStarted,
|
||||
);
|
||||
expect(result).toEqual(blockGetStarted.patterns[3]);
|
||||
expect(result).toEqual(blockGetStarted.patterns?.[3]);
|
||||
});
|
||||
|
||||
it("should match payload when it's an attachment file", () => {
|
||||
@ -396,7 +398,7 @@ describe('BlockService', () => {
|
||||
},
|
||||
blockGetStarted,
|
||||
);
|
||||
expect(result).toEqual(blockGetStarted.patterns[4]);
|
||||
expect(result).toEqual(blockGetStarted.patterns?.[4]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -439,7 +441,7 @@ describe('BlockService', () => {
|
||||
describe('processMessage', () => {
|
||||
it('should process list message (with limit = 2 and skip = 0)', async () => {
|
||||
const contentType = await contentTypeService.findOne({ name: 'Product' });
|
||||
blockProductListMock.options.content.entity = contentType.id;
|
||||
blockProductListMock.options!.content!.entity = contentType!.id;
|
||||
const result = await blockService.processMessage(
|
||||
blockProductListMock,
|
||||
{
|
||||
@ -451,27 +453,27 @@ describe('BlockService', () => {
|
||||
'conv_id',
|
||||
);
|
||||
const elements = await contentService.findPage(
|
||||
{ status: true, entity: contentType.id },
|
||||
{ status: true, entity: contentType!.id },
|
||||
{ skip: 0, limit: 2, sort: ['createdAt', 'desc'] },
|
||||
);
|
||||
const flattenedElements = elements.map(Content.toElement);
|
||||
expect(result.format).toEqualPayload(
|
||||
blockProductListMock.options.content?.display,
|
||||
expect(result!.format).toEqualPayload(
|
||||
blockProductListMock.options!.content!.display,
|
||||
);
|
||||
expect(
|
||||
(result.message as StdOutgoingListMessage).elements,
|
||||
(result!.message as StdOutgoingListMessage).elements,
|
||||
).toEqualPayload(flattenedElements);
|
||||
expect((result.message as StdOutgoingListMessage).options).toEqualPayload(
|
||||
blockProductListMock.options.content,
|
||||
);
|
||||
expect(
|
||||
(result.message as StdOutgoingListMessage).pagination,
|
||||
(result!.message as StdOutgoingListMessage).options,
|
||||
).toEqualPayload(blockProductListMock.options!.content!);
|
||||
expect(
|
||||
(result!.message as StdOutgoingListMessage).pagination,
|
||||
).toEqualPayload({ total: 4, skip: 0, limit: 2 });
|
||||
});
|
||||
|
||||
it('should process list message (with limit = 2 and skip = 2)', async () => {
|
||||
const contentType = await contentTypeService.findOne({ name: 'Product' });
|
||||
blockProductListMock.options.content.entity = contentType.id;
|
||||
blockProductListMock.options!.content!.entity = contentType!.id;
|
||||
const result = await blockService.processMessage(
|
||||
blockProductListMock,
|
||||
{
|
||||
@ -483,20 +485,20 @@ describe('BlockService', () => {
|
||||
'conv_id',
|
||||
);
|
||||
const elements = await contentService.findPage(
|
||||
{ status: true, entity: contentType.id },
|
||||
{ status: true, entity: contentType!.id },
|
||||
{ skip: 2, limit: 2, sort: ['createdAt', 'desc'] },
|
||||
);
|
||||
const flattenedElements = elements.map(Content.toElement);
|
||||
expect(result.format).toEqual(
|
||||
blockProductListMock.options.content?.display,
|
||||
expect(result!.format).toEqual(
|
||||
blockProductListMock.options!.content?.display,
|
||||
);
|
||||
expect((result.message as StdOutgoingListMessage).elements).toEqual(
|
||||
expect((result!.message as StdOutgoingListMessage).elements).toEqual(
|
||||
flattenedElements,
|
||||
);
|
||||
expect((result.message as StdOutgoingListMessage).options).toEqual(
|
||||
blockProductListMock.options.content,
|
||||
expect((result!.message as StdOutgoingListMessage).options).toEqual(
|
||||
blockProductListMock.options!.content,
|
||||
);
|
||||
expect((result.message as StdOutgoingListMessage).pagination).toEqual({
|
||||
expect((result!.message as StdOutgoingListMessage).pagination).toEqual({
|
||||
total: 4,
|
||||
skip: 2,
|
||||
limit: 2,
|
||||
|
@ -197,7 +197,7 @@ describe('BlockService', () => {
|
||||
foreign_id: 'foreign-id-web-1',
|
||||
});
|
||||
|
||||
event.setSender(webSubscriber);
|
||||
event.setSender(webSubscriber!);
|
||||
|
||||
let hasBotSpoken = false;
|
||||
const clearMock = jest
|
||||
@ -210,15 +210,15 @@ describe('BlockService', () => {
|
||||
isFallback: boolean,
|
||||
) => {
|
||||
expect(actualConversation).toEqualPayload({
|
||||
sender: webSubscriber.id,
|
||||
sender: webSubscriber!.id,
|
||||
active: true,
|
||||
next: [],
|
||||
context: {
|
||||
user: {
|
||||
first_name: webSubscriber.first_name,
|
||||
last_name: webSubscriber.last_name,
|
||||
first_name: webSubscriber!.first_name,
|
||||
last_name: webSubscriber!.last_name,
|
||||
language: 'en',
|
||||
id: webSubscriber.id,
|
||||
id: webSubscriber!.id,
|
||||
},
|
||||
user_location: {
|
||||
lat: 0,
|
||||
@ -263,7 +263,7 @@ describe('BlockService', () => {
|
||||
const webSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-web-1',
|
||||
});
|
||||
event.setSender(webSubscriber);
|
||||
event.setSender(webSubscriber!);
|
||||
|
||||
const clearMock = jest
|
||||
.spyOn(botService, 'handleIncomingMessage')
|
||||
@ -278,10 +278,10 @@ describe('BlockService', () => {
|
||||
active: true,
|
||||
context: {
|
||||
user: {
|
||||
first_name: webSubscriber.first_name,
|
||||
last_name: webSubscriber.last_name,
|
||||
first_name: webSubscriber!.first_name,
|
||||
last_name: webSubscriber!.last_name,
|
||||
language: 'en',
|
||||
id: webSubscriber.id,
|
||||
id: webSubscriber!.id,
|
||||
},
|
||||
user_location: { lat: 0, lon: 0 },
|
||||
vars: {},
|
||||
@ -317,7 +317,7 @@ describe('BlockService', () => {
|
||||
const webSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-web-2',
|
||||
});
|
||||
event.setSender(webSubscriber);
|
||||
event.setSender(webSubscriber!);
|
||||
const captured = await botService.processConversationMessage(event);
|
||||
|
||||
expect(captured).toBe(false);
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
IncomingMessageType,
|
||||
StdOutgoingEnvelope,
|
||||
} from '../schemas/types/message';
|
||||
import { SubscriberContext } from '../schemas/types/subscriberContext';
|
||||
|
||||
import { BlockService } from './block.service';
|
||||
import { ConversationService } from './conversation.service';
|
||||
@ -70,14 +71,13 @@ export class BotService {
|
||||
);
|
||||
// Process message : Replace tokens with context data and then send the message
|
||||
const recipient = event.getSender();
|
||||
const envelope: StdOutgoingEnvelope =
|
||||
await this.blockService.processMessage(
|
||||
block,
|
||||
context,
|
||||
recipient?.context,
|
||||
fallback,
|
||||
conservationId,
|
||||
);
|
||||
const envelope = (await this.blockService.processMessage(
|
||||
block,
|
||||
context,
|
||||
recipient?.context as SubscriberContext,
|
||||
fallback,
|
||||
conservationId,
|
||||
)) as StdOutgoingEnvelope;
|
||||
// Send message through the right channel
|
||||
|
||||
const response = await event
|
||||
@ -252,13 +252,13 @@ export class BotService {
|
||||
assign_labels: [],
|
||||
trigger_labels: [],
|
||||
attachedBlock: undefined,
|
||||
category: undefined,
|
||||
category: undefined as any,
|
||||
previousBlocks: [],
|
||||
};
|
||||
convo.context.attempt++;
|
||||
fallback = true;
|
||||
} else {
|
||||
convo.context.attempt = 0;
|
||||
if (convo.context) convo.context.attempt = 0;
|
||||
fallbackBlock = undefined;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ export class ContextVarService extends BaseService<ContextVar> {
|
||||
block: Block | BlockFull,
|
||||
): Promise<Record<string, ContextVar>> {
|
||||
const vars = await this.find({
|
||||
name: { $in: block.capture_vars.map(({ context_var }) => context_var) },
|
||||
name: { $in: block.capture_vars?.map(({ context_var }) => context_var) },
|
||||
});
|
||||
return vars.reduce((acc, cv) => {
|
||||
acc[cv.name] = cv;
|
||||
|
@ -68,6 +68,9 @@ export class ConversationService extends BaseService<
|
||||
) {
|
||||
const msgType = event.getMessageType();
|
||||
const profile = event.getSender();
|
||||
|
||||
if (!convo.context) throw new Error('Missing conversation context');
|
||||
|
||||
// Capture channel specific context data
|
||||
convo.context.channel = event.getHandler().getName();
|
||||
convo.context.text = event.getText();
|
||||
@ -81,7 +84,7 @@ export class ConversationService extends BaseService<
|
||||
// Capture user entry in context vars
|
||||
if (captureVars && next.capture_vars && next.capture_vars.length > 0) {
|
||||
next.capture_vars.forEach((capture) => {
|
||||
let contextValue: string | Payload;
|
||||
let contextValue: string | Payload | undefined;
|
||||
|
||||
const nlp = event.getNLP();
|
||||
|
||||
@ -103,7 +106,7 @@ export class ConversationService extends BaseService<
|
||||
if (capture.entity === -1) {
|
||||
// Capture the whole message
|
||||
contextValue =
|
||||
['message', 'quick_reply'].indexOf(msgType) !== -1
|
||||
msgType && ['message', 'quick_reply'].indexOf(msgType) !== -1
|
||||
? event.getText()
|
||||
: event.getPayload();
|
||||
} else if (capture.entity === -2) {
|
||||
@ -113,13 +116,16 @@ export class ConversationService extends BaseService<
|
||||
contextValue =
|
||||
typeof contextValue === 'string' ? contextValue.trim() : contextValue;
|
||||
|
||||
if (contextVars[capture.context_var]?.permanent) {
|
||||
if (
|
||||
profile.context?.vars &&
|
||||
contextVars[capture.context_var]?.permanent
|
||||
) {
|
||||
Logger.debug(
|
||||
`Adding context var to subscriber: ${capture.context_var} = ${contextValue}`,
|
||||
);
|
||||
profile.context.vars[capture.context_var] = contextValue;
|
||||
} else {
|
||||
convo.context.vars[capture.context_var] = contextValue;
|
||||
convo.context!.vars[capture.context_var] = contextValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -158,6 +164,7 @@ export class ConversationService extends BaseService<
|
||||
|
||||
// Deal with load more in the case of a list display
|
||||
if (
|
||||
next.options &&
|
||||
next.options.content &&
|
||||
(next.options.content.display === OutgoingMessageFormat.list ||
|
||||
next.options.content.display === OutgoingMessageFormat.carousel)
|
||||
|
@ -48,11 +48,11 @@ describe('MessageService', () => {
|
||||
let allMessages: Message[];
|
||||
let allSubscribers: Subscriber[];
|
||||
let allUsers: User[];
|
||||
let message: Message;
|
||||
let sender: Subscriber;
|
||||
let recipient: Subscriber;
|
||||
let message: Message | null;
|
||||
let sender: Subscriber | null;
|
||||
let recipient: Subscriber | null;
|
||||
let messagesWithSenderAndRecipient: Message[];
|
||||
let user: User;
|
||||
let user: User | null;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
@ -91,15 +91,14 @@ describe('MessageService', () => {
|
||||
allUsers = await userRepository.findAll();
|
||||
allMessages = await messageRepository.findAll();
|
||||
message = await messageRepository.findOne({ mid: 'mid-1' });
|
||||
sender = await subscriberRepository.findOne(message['sender']);
|
||||
recipient = await subscriberRepository.findOne(message['recipient']);
|
||||
user = await userRepository.findOne(message['sentBy']);
|
||||
sender = await subscriberRepository.findOne(message!.sender!);
|
||||
recipient = await subscriberRepository.findOne(message!.recipient!);
|
||||
user = await userRepository.findOne(message!.sentBy!);
|
||||
messagesWithSenderAndRecipient = allMessages.map((message) => ({
|
||||
...message,
|
||||
sender: allSubscribers.find(({ id }) => id === message['sender']).id,
|
||||
recipient: allSubscribers.find(({ id }) => id === message['recipient'])
|
||||
.id,
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy']).id,
|
||||
sender: allSubscribers.find(({ id }) => id === message.sender)?.id,
|
||||
recipient: allSubscribers.find(({ id }) => id === message.recipient)?.id,
|
||||
sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id,
|
||||
}));
|
||||
});
|
||||
|
||||
@ -109,17 +108,17 @@ describe('MessageService', () => {
|
||||
describe('findOneAndPopulate', () => {
|
||||
it('should find message by id, and populate its corresponding sender and recipient', async () => {
|
||||
jest.spyOn(messageRepository, 'findOneAndPopulate');
|
||||
const result = await messageService.findOneAndPopulate(message.id);
|
||||
const result = await messageService.findOneAndPopulate(message!.id);
|
||||
|
||||
expect(messageRepository.findOneAndPopulate).toHaveBeenCalledWith(
|
||||
message.id,
|
||||
message!.id,
|
||||
undefined,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...messageFixtures.find(({ mid }) => mid === message.mid),
|
||||
...messageFixtures.find(({ mid }) => mid === message!.mid),
|
||||
sender,
|
||||
recipient,
|
||||
sentBy: user.id,
|
||||
sentBy: user!.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -131,9 +130,9 @@ describe('MessageService', () => {
|
||||
const result = await messageService.findPageAndPopulate({}, pageQuery);
|
||||
const messagesWithSenderAndRecipient = allMessages.map((message) => ({
|
||||
...message,
|
||||
sender: allSubscribers.find(({ id }) => id === message['sender']),
|
||||
recipient: allSubscribers.find(({ id }) => id === message['recipient']),
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy']).id,
|
||||
sender: allSubscribers.find(({ id }) => id === message.sender),
|
||||
recipient: allSubscribers.find(({ id }) => id === message.recipient),
|
||||
sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id,
|
||||
}));
|
||||
|
||||
expect(messageRepository.findPageAndPopulate).toHaveBeenCalledWith(
|
||||
@ -150,7 +149,7 @@ describe('MessageService', () => {
|
||||
new Date().setMonth(new Date().getMonth() + 1),
|
||||
);
|
||||
const result = await messageService.findHistoryUntilDate(
|
||||
sender,
|
||||
sender!,
|
||||
until,
|
||||
30,
|
||||
);
|
||||
@ -166,16 +165,16 @@ describe('MessageService', () => {
|
||||
it('should return history since given date', async () => {
|
||||
const since: Date = new Date();
|
||||
const result = await messageService.findHistorySinceDate(
|
||||
sender,
|
||||
sender!,
|
||||
since,
|
||||
30,
|
||||
);
|
||||
const messagesWithSenderAndRecipient = allMessages.map((message) => ({
|
||||
...message,
|
||||
sender: allSubscribers.find(({ id }) => id === message['sender']).id,
|
||||
recipient: allSubscribers.find(({ id }) => id === message['recipient'])
|
||||
.id,
|
||||
sentBy: allUsers.find(({ id }) => id === message['sentBy']).id,
|
||||
sender: allSubscribers.find(({ id }) => id === message.sender)?.id,
|
||||
recipient: allSubscribers.find(({ id }) => id === message.recipient)
|
||||
?.id,
|
||||
sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id,
|
||||
}));
|
||||
const historyMessages = messagesWithSenderAndRecipient.filter(
|
||||
(message) => message.createdAt > since,
|
||||
|
@ -46,8 +46,8 @@ export class MessageService extends BaseService<
|
||||
@Optional() gateway?: WebsocketGateway,
|
||||
) {
|
||||
super(messageRepository);
|
||||
this.logger = logger;
|
||||
this.gateway = gateway;
|
||||
if (logger) this.logger = logger;
|
||||
if (gateway) this.gateway = gateway;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,17 +93,17 @@ describe('SubscriberService', () => {
|
||||
const subscriber = await subscriberRepository.findOne({
|
||||
first_name: 'Jhon',
|
||||
});
|
||||
const result = await subscriberService.findOneAndPopulate(subscriber.id);
|
||||
const result = await subscriberService.findOneAndPopulate(subscriber!.id);
|
||||
|
||||
expect(subscriberService.findOneAndPopulate).toHaveBeenCalledWith(
|
||||
subscriber.id,
|
||||
subscriber!.id,
|
||||
);
|
||||
expect(result).toEqualPayload({
|
||||
...subscriber,
|
||||
labels: allLabels.filter((label) =>
|
||||
subscriber.labels.includes(label.id),
|
||||
subscriber!.labels.includes(label.id),
|
||||
),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id),
|
||||
assignedTo: allUsers.find(({ id }) => subscriber!.assignedTo === id),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -139,7 +139,7 @@ describe('SubscriberService', () => {
|
||||
expect(result).toEqualPayload({
|
||||
...subscriber,
|
||||
labels: allLabels
|
||||
.filter((label) => subscriber.labels.includes(label.id))
|
||||
.filter((label) => subscriber!.labels.includes(label.id))
|
||||
.map((label) => label.id),
|
||||
});
|
||||
});
|
||||
|
@ -51,7 +51,7 @@ export class SubscriberService extends BaseService<
|
||||
@Optional() gateway?: WebsocketGateway,
|
||||
) {
|
||||
super(repository);
|
||||
this.gateway = gateway;
|
||||
if (gateway) this.gateway = gateway;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
* 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 { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
||||
import { Request, Response } from 'express';
|
||||
import multer, { diskStorage, memoryStorage } from 'multer';
|
||||
@ -231,7 +231,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
...message,
|
||||
author: 'chatbot',
|
||||
read: true, // Temporary fix as read is false in the bd
|
||||
mid: anyMessage.mid,
|
||||
mid: anyMessage.mid || 'DEFAULT_MID',
|
||||
handover: !!anyMessage.handover,
|
||||
createdAt: anyMessage.createdAt,
|
||||
});
|
||||
@ -519,6 +519,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
|
||||
const fetchMessages = async (req: Request, res: Response, retrials = 1) => {
|
||||
try {
|
||||
if (!req.query.since)
|
||||
throw new BadRequestException(`QueryParam 'since' is missing`);
|
||||
|
||||
const since = new Date(req.query.since.toString());
|
||||
const messages = await this.pollMessages(req, since);
|
||||
if (messages.length === 0 && retrials <= 5) {
|
||||
@ -630,10 +633,12 @@ export default abstract class BaseWebChannelHandler<
|
||||
size: Buffer.byteLength(data.file),
|
||||
type: data.type,
|
||||
});
|
||||
next(null, {
|
||||
type: Attachment.getTypeByMime(attachment.type),
|
||||
url: Attachment.getAttachmentUrl(attachment.id, attachment.name),
|
||||
});
|
||||
|
||||
if (attachment)
|
||||
next(null, {
|
||||
type: Attachment.getTypeByMime(attachment.type),
|
||||
url: Attachment.getAttachmentUrl(attachment.id, attachment?.name),
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
'Web Channel Handler : Unable to write uploaded file',
|
||||
@ -677,6 +682,12 @@ export default abstract class BaseWebChannelHandler<
|
||||
size: file.size,
|
||||
type: file.mimetype,
|
||||
});
|
||||
if (!attachment) {
|
||||
this.logger.debug(
|
||||
'Web Channel Handler : failed to store attachment',
|
||||
);
|
||||
return next(null);
|
||||
}
|
||||
next(null, {
|
||||
type: Attachment.getTypeByMime(attachment.type),
|
||||
url: Attachment.getAttachmentUrl(attachment.id, attachment.name),
|
||||
@ -721,7 +732,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
return {
|
||||
isSocket: this.isSocketRequest(req),
|
||||
ipAddress: this.getIpAddress(req),
|
||||
agent: req.headers['user-agent'],
|
||||
agent: req.headers['user-agent'] || 'browser',
|
||||
};
|
||||
}
|
||||
|
||||
@ -965,11 +976,17 @@ export default abstract class BaseWebChannelHandler<
|
||||
type: Web.OutgoingMessageType.file,
|
||||
data: {
|
||||
type: message.attachment.type,
|
||||
url: message.attachment.payload.url,
|
||||
url: message.attachment.payload.url!,
|
||||
},
|
||||
};
|
||||
if (message.quickReplies && message.quickReplies.length > 0) {
|
||||
payload.data.quick_replies = message.quickReplies;
|
||||
return {
|
||||
...payload,
|
||||
data: {
|
||||
...payload.data,
|
||||
quick_replies: message.quickReplies,
|
||||
} as Web.OutgoingFileMessageData,
|
||||
};
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* 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 { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import { HelperService } from '@/helper/helper.service';
|
||||
@ -77,9 +77,14 @@ export class NlpService {
|
||||
async handleEntityDelete(entity: NlpEntity) {
|
||||
// Synchonize new entity with NLP provider
|
||||
try {
|
||||
const helper = await this.helperService.getDefaultNluHelper();
|
||||
await helper.deleteEntity(entity.foreign_id);
|
||||
this.logger.debug('Deleted entity successfully synced!', entity);
|
||||
if (entity.foreign_id) {
|
||||
const helper = await this.helperService.getDefaultNluHelper();
|
||||
await helper.deleteEntity(entity.foreign_id);
|
||||
this.logger.debug('Deleted entity successfully synced!', entity);
|
||||
} else {
|
||||
this.logger.error(`Entity ${entity} is missing foreign_id`);
|
||||
throw new NotFoundException(`Entity ${entity} is missing foreign_id`);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error('Unable to sync deleted entity', err);
|
||||
}
|
||||
@ -138,8 +143,10 @@ export class NlpService {
|
||||
const populatedValue = await this.nlpValueService.findOneAndPopulate(
|
||||
value.id,
|
||||
);
|
||||
await helper.deleteValue(populatedValue);
|
||||
this.logger.debug('Deleted value successfully synced!', value);
|
||||
if (populatedValue) {
|
||||
await helper.deleteValue(populatedValue);
|
||||
this.logger.debug('Deleted value successfully synced!', value);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error('Unable to sync deleted value', err);
|
||||
}
|
||||
|
3
api/src/utils/test/fixtures/block.ts
vendored
3
api/src/utils/test/fixtures/block.ts
vendored
@ -9,7 +9,7 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import { BlockCreateDto } from '@/chat/dto/block.dto';
|
||||
import { BlockModel, Block } from '@/chat/schemas/block.schema';
|
||||
import { Block, BlockModel } from '@/chat/schemas/block.schema';
|
||||
import { CategoryModel } from '@/chat/schemas/category.schema';
|
||||
import { FileType } from '@/chat/schemas/types/attachment';
|
||||
import { ButtonType } from '@/chat/schemas/types/button';
|
||||
@ -171,7 +171,6 @@ export const blockDefaultValues: TFixturesDefaultValues<Block> = {
|
||||
assign_labels: [],
|
||||
trigger_labels: [],
|
||||
starts_conversation: false,
|
||||
attachedToBlock: null,
|
||||
};
|
||||
|
||||
export const blockFixtures = getFixturesWithDefaultValues<Block>({
|
||||
|
@ -98,22 +98,22 @@ export const baseBlockInstance = {
|
||||
...modelInstance,
|
||||
};
|
||||
|
||||
export const blockEmpty: BlockFull = {
|
||||
export const blockEmpty = {
|
||||
...baseBlockInstance,
|
||||
name: 'Empty',
|
||||
patterns: [],
|
||||
message: [''],
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
// Translation Data
|
||||
export const textResult = ['Hi back !'];
|
||||
|
||||
export const textBlock: BlockFull = {
|
||||
export const textBlock = {
|
||||
name: 'message',
|
||||
patterns: ['Hi'],
|
||||
message: textResult,
|
||||
...baseBlockInstance,
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
export const quickRepliesResult = [
|
||||
"What's your favorite color?",
|
||||
@ -122,7 +122,7 @@ export const quickRepliesResult = [
|
||||
'Red',
|
||||
];
|
||||
|
||||
export const quickRepliesBlock: BlockFull = {
|
||||
export const quickRepliesBlock = {
|
||||
name: 'message',
|
||||
patterns: ['colors'],
|
||||
message: {
|
||||
@ -146,7 +146,7 @@ export const quickRepliesBlock: BlockFull = {
|
||||
],
|
||||
},
|
||||
...baseBlockInstance,
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
export const buttonsResult = [
|
||||
'What would you like to know about us?',
|
||||
@ -155,7 +155,7 @@ export const buttonsResult = [
|
||||
'Approach',
|
||||
];
|
||||
|
||||
export const buttonsBlock: BlockFull = {
|
||||
export const buttonsBlock = {
|
||||
name: 'message',
|
||||
patterns: ['about'],
|
||||
message: {
|
||||
@ -179,9 +179,9 @@ export const buttonsBlock: BlockFull = {
|
||||
],
|
||||
},
|
||||
...baseBlockInstance,
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
export const attachmentBlock: BlockFull = {
|
||||
export const attachmentBlock = {
|
||||
name: 'message',
|
||||
patterns: ['image'],
|
||||
message: {
|
||||
@ -195,7 +195,7 @@ export const attachmentBlock: BlockFull = {
|
||||
quickReplies: [],
|
||||
},
|
||||
...baseBlockInstance,
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
export const allBlocksStringsResult = [
|
||||
'Hi back !',
|
||||
@ -214,7 +214,7 @@ export const allBlocksStringsResult = [
|
||||
|
||||
/////////
|
||||
|
||||
export const blockGetStarted: BlockFull = {
|
||||
export const blockGetStarted = {
|
||||
...baseBlockInstance,
|
||||
name: 'Get Started',
|
||||
patterns: [
|
||||
@ -245,7 +245,7 @@ export const blockGetStarted: BlockFull = {
|
||||
],
|
||||
trigger_labels: customerLabelsMock,
|
||||
message: ['Welcome! How are you ? '],
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
const patternsProduct: Pattern[] = [
|
||||
'produit',
|
||||
@ -262,7 +262,7 @@ const patternsProduct: Pattern[] = [
|
||||
],
|
||||
];
|
||||
|
||||
export const blockProductListMock: BlockFull = {
|
||||
export const blockProductListMock = {
|
||||
...baseBlockInstance,
|
||||
name: 'test_list',
|
||||
patterns: patternsProduct,
|
||||
@ -278,11 +278,11 @@ export const blockProductListMock: BlockFull = {
|
||||
limit: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
export const blockCarouselMock: BlockFull = {
|
||||
export const blockCarouselMock = {
|
||||
...blockProductListMock,
|
||||
options: blockCarouselOptions,
|
||||
};
|
||||
} as unknown as BlockFull;
|
||||
|
||||
export const blocks: BlockFull[] = [blockGetStarted, blockEmpty];
|
||||
|
@ -13,14 +13,16 @@ type TSortProps<T> = {
|
||||
order?: 'desc' | 'asc';
|
||||
};
|
||||
|
||||
const sort = <R, S, T extends { createdAt?: string } = R & S>({
|
||||
type TCreateAt = { createdAt?: string | Date };
|
||||
|
||||
const sort = <R extends TCreateAt, S, T extends TCreateAt = R & S>({
|
||||
row1,
|
||||
row2,
|
||||
field = 'createdAt',
|
||||
order = 'desc',
|
||||
}: TSortProps<T>) => (order === 'asc' && row1[field] > row2[field] ? 1 : -1);
|
||||
|
||||
export const sortRowsBy = <R, S, T = R & S>(
|
||||
export const sortRowsBy = <R extends TCreateAt, S, T extends TCreateAt = R & S>(
|
||||
row1: T,
|
||||
row2: T,
|
||||
field?: keyof T,
|
||||
|
@ -65,12 +65,16 @@ type TAllowedKeys<T, TStub, TValue = (string | null | undefined)[]> = {
|
||||
>]: TValue;
|
||||
};
|
||||
|
||||
type TVirtualFields<T> = Pick<T, TFilterKeysOfType<T, undefined>>;
|
||||
|
||||
export type TValidateProps<T, TStub> = {
|
||||
dto:
|
||||
| Partial<TAllowedKeys<T, TStub>>
|
||||
| Partial<TAllowedKeys<T, TStub, string>>;
|
||||
allowedIds: TAllowedKeys<T, TStub> &
|
||||
TAllowedKeys<T, TStub, string | null | undefined>;
|
||||
allowedIds: Omit<
|
||||
TAllowedKeys<T, TStub, null | undefined | string | string[]>,
|
||||
keyof TVirtualFields<T>
|
||||
>;
|
||||
};
|
||||
|
||||
//populate types
|
||||
|
2
api/types/event-emitter.d.ts
vendored
2
api/types/event-emitter.d.ts
vendored
@ -93,7 +93,7 @@ declare module '@nestjs/event-emitter' {
|
||||
object,
|
||||
{
|
||||
block: BlockFull;
|
||||
passation: Subscriber;
|
||||
passation: Subscriber | null;
|
||||
'fallback-local': BlockFull;
|
||||
'fallback-global': EventWrapper<any, any>;
|
||||
intervention: Subscriber;
|
||||
|
Loading…
Reference in New Issue
Block a user