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