mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
301 lines
10 KiB
TypeScript
301 lines
10 KiB
TypeScript
/*
|
|
* Copyright © 2024 Hexastack. All rights reserved.
|
|
*
|
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
|
*/
|
|
|
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
import { MongooseModule, getModelToken } from '@nestjs/mongoose';
|
|
import { Test } from '@nestjs/testing';
|
|
import { Model, Types } from 'mongoose';
|
|
|
|
import {
|
|
blockFixtures,
|
|
installBlockFixtures,
|
|
} from '@/utils/test/fixtures/block';
|
|
import {
|
|
closeInMongodConnection,
|
|
rootMongooseTestModule,
|
|
} from '@/utils/test/test';
|
|
|
|
import { Block, BlockModel } from '../schemas/block.schema';
|
|
import { Category, CategoryModel } from '../schemas/category.schema';
|
|
import { LabelModel } from '../schemas/label.schema';
|
|
|
|
import { BlockRepository } from './block.repository';
|
|
import { CategoryRepository } from './category.repository';
|
|
|
|
describe('BlockRepository', () => {
|
|
let blockRepository: BlockRepository;
|
|
let categoryRepository: CategoryRepository;
|
|
let blockModel: Model<Block>;
|
|
let category: Category;
|
|
let hasPreviousBlocks: Block;
|
|
let hasNextBlocks: Block;
|
|
let validIds: string[];
|
|
let validCategory: string;
|
|
|
|
beforeAll(async () => {
|
|
const module = await Test.createTestingModule({
|
|
imports: [
|
|
rootMongooseTestModule(installBlockFixtures),
|
|
MongooseModule.forFeature([BlockModel, CategoryModel, LabelModel]),
|
|
],
|
|
providers: [BlockRepository, CategoryRepository, EventEmitter2],
|
|
}).compile();
|
|
blockRepository = module.get<BlockRepository>(BlockRepository);
|
|
categoryRepository = module.get<CategoryRepository>(CategoryRepository);
|
|
blockModel = module.get<Model<Block>>(getModelToken('Block'));
|
|
validIds = ['64abc1234def567890fedcba', '64abc1234def567890fedcbc'];
|
|
validCategory = '64def5678abc123490fedcba';
|
|
|
|
category = await categoryRepository.findOne({ label: 'default' });
|
|
hasPreviousBlocks = await blockRepository.findOne({
|
|
name: 'hasPreviousBlocks',
|
|
});
|
|
hasNextBlocks = await blockRepository.findOne({
|
|
name: 'hasNextBlocks',
|
|
});
|
|
});
|
|
|
|
afterEach(jest.clearAllMocks);
|
|
afterAll(closeInMongodConnection);
|
|
|
|
describe('findOneAndPopulate', () => {
|
|
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);
|
|
expect(blockModel.findById).toHaveBeenCalledWith(hasNextBlocks.id);
|
|
expect(result).toEqualPayload({
|
|
...blockFixtures.find(({ name }) => name === hasNextBlocks.name),
|
|
category,
|
|
nextBlocks: [hasPreviousBlocks],
|
|
previousBlocks: [],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('findAndPopulate', () => {
|
|
it('should find blocks, and foreach block populate its trigger_labels, assign_labels, attachedBlock, category, previousBlocks', async () => {
|
|
jest.spyOn(blockModel, 'find');
|
|
const category = await categoryRepository.findOne({ label: 'default' });
|
|
const result = await blockRepository.findAndPopulate({});
|
|
const blocksWithCategory = blockFixtures.map((blockFixture) => ({
|
|
...blockFixture,
|
|
category,
|
|
previousBlocks:
|
|
blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [],
|
|
nextBlocks:
|
|
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
|
|
}));
|
|
|
|
expect(blockModel.find).toHaveBeenCalledWith({});
|
|
expect(result).toEqualPayload(blocksWithCategory);
|
|
});
|
|
|
|
it('should find blocks, and foreach block populate its trigger_labels, assign_labels, nextBlocks, attachedBlock, category', async () => {
|
|
jest.spyOn(blockModel, 'find');
|
|
const category = await categoryRepository.findOne({ label: 'default' });
|
|
const result = await blockRepository.findAndPopulate({});
|
|
const blocksWithCategory = blockFixtures.map((blockFixture) => ({
|
|
...blockFixture,
|
|
category,
|
|
previousBlocks:
|
|
blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [],
|
|
nextBlocks:
|
|
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
|
|
}));
|
|
|
|
expect(blockModel.find).toHaveBeenCalledWith({});
|
|
expect(result).toEqualPayload(blocksWithCategory);
|
|
});
|
|
});
|
|
|
|
describe('preUpdate', () => {
|
|
it('should remove references to a moved block when updating category', async () => {
|
|
const mockUpdateMany = jest.spyOn(blockRepository, 'updateMany');
|
|
const criteria = { _id: validIds[0] };
|
|
const updates = { $set: { category: validCategory } };
|
|
|
|
const mockFindOne = jest
|
|
.spyOn(blockRepository, 'findOne')
|
|
.mockResolvedValue({
|
|
id: validIds[0],
|
|
category: 'oldCategory',
|
|
} as Block);
|
|
|
|
await blockRepository.preUpdate({} as any, criteria, updates);
|
|
|
|
expect(mockFindOne).toHaveBeenCalledWith(criteria);
|
|
expect(mockUpdateMany).toHaveBeenCalledTimes(2);
|
|
expect(mockUpdateMany).toHaveBeenNthCalledWith(
|
|
1,
|
|
{ nextBlocks: validIds[0] },
|
|
{ $pull: { nextBlocks: validIds[0] } },
|
|
);
|
|
expect(mockUpdateMany).toHaveBeenNthCalledWith(
|
|
2,
|
|
{ attachedBlock: validIds[0] },
|
|
{ $set: { attachedBlock: null } },
|
|
);
|
|
});
|
|
|
|
it('should do nothing if no block is found for the criteria', async () => {
|
|
jest.spyOn(blockRepository, 'findOne').mockResolvedValue(null);
|
|
const mockUpdateMany = jest.spyOn(blockRepository, 'updateMany');
|
|
|
|
await blockRepository.preUpdate({} as any, { _id: 'nonexistent' }, {});
|
|
|
|
expect(mockUpdateMany).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('prepareBlocksInCategoryUpdateScope', () => {
|
|
it('should update blocks within the scope based on category and ids', async () => {
|
|
jest.spyOn(blockRepository, 'findOne').mockResolvedValue({
|
|
id: validIds[0],
|
|
category: 'oldCategory',
|
|
nextBlocks: [validIds[1]],
|
|
attachedBlock: validIds[1],
|
|
} as Block);
|
|
|
|
const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne');
|
|
|
|
await blockRepository.prepareBlocksInCategoryUpdateScope(
|
|
validCategory,
|
|
validIds,
|
|
);
|
|
|
|
expect(mockUpdateOne).toHaveBeenCalledWith(validIds[0], {
|
|
nextBlocks: [validIds[1]],
|
|
attachedBlock: validIds[1],
|
|
});
|
|
});
|
|
|
|
it('should not update blocks if the category already matches', async () => {
|
|
jest.spyOn(blockRepository, 'findOne').mockResolvedValue({
|
|
id: validIds[0],
|
|
category: validCategory,
|
|
nextBlocks: [],
|
|
attachedBlock: null,
|
|
} as Block);
|
|
|
|
const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne');
|
|
|
|
await blockRepository.prepareBlocksInCategoryUpdateScope(
|
|
validCategory,
|
|
validIds,
|
|
);
|
|
|
|
expect(mockUpdateOne).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('prepareBlocksOutOfCategoryUpdateScope', () => {
|
|
it('should update blocks outside the scope by removing references from attachedBlock', async () => {
|
|
const otherBlocks = [
|
|
{
|
|
id: '64abc1234def567890fedcab',
|
|
attachedBlock: validIds[0],
|
|
nextBlocks: [validIds[0]],
|
|
},
|
|
] as Block[];
|
|
|
|
const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne');
|
|
|
|
await blockRepository.prepareBlocksOutOfCategoryUpdateScope(
|
|
otherBlocks,
|
|
validIds,
|
|
);
|
|
|
|
expect(mockUpdateOne).toHaveBeenCalledWith('64abc1234def567890fedcab', {
|
|
attachedBlock: null,
|
|
});
|
|
});
|
|
|
|
it('should update blocks outside the scope by removing references from nextBlocks', async () => {
|
|
const otherBlocks = [
|
|
{
|
|
id: '64abc1234def567890fedcab',
|
|
attachedBlock: null,
|
|
nextBlocks: [validIds[0], validIds[1]],
|
|
},
|
|
] as Block[];
|
|
|
|
const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne');
|
|
|
|
await blockRepository.prepareBlocksOutOfCategoryUpdateScope(otherBlocks, [
|
|
validIds[0],
|
|
]);
|
|
|
|
expect(mockUpdateOne).toHaveBeenCalledWith('64abc1234def567890fedcab', {
|
|
nextBlocks: [new Types.ObjectId(validIds[1])],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('preUpdateMany', () => {
|
|
it('should update blocks in and out of the scope', async () => {
|
|
const mockFind = jest.spyOn(blockRepository, 'find').mockResolvedValue([
|
|
{
|
|
id: '64abc1234def567890fedcab',
|
|
attachedBlock: validIds[0],
|
|
nextBlocks: [validIds[0]],
|
|
},
|
|
] as Block[]);
|
|
|
|
const prepareBlocksInCategoryUpdateScope = jest.spyOn(
|
|
blockRepository,
|
|
'prepareBlocksInCategoryUpdateScope',
|
|
);
|
|
const prepareBlocksOutOfCategoryUpdateScope = jest.spyOn(
|
|
blockRepository,
|
|
'prepareBlocksOutOfCategoryUpdateScope',
|
|
);
|
|
|
|
await blockRepository.preUpdateMany(
|
|
{} as any,
|
|
{ _id: { $in: validIds } },
|
|
{ $set: { category: validCategory } },
|
|
);
|
|
|
|
expect(mockFind).toHaveBeenCalled();
|
|
expect(prepareBlocksInCategoryUpdateScope).toHaveBeenCalledWith(
|
|
validCategory,
|
|
['64abc1234def567890fedcab'],
|
|
);
|
|
expect(prepareBlocksOutOfCategoryUpdateScope).toHaveBeenCalledWith(
|
|
[
|
|
{
|
|
id: '64abc1234def567890fedcab',
|
|
attachedBlock: validIds[0],
|
|
nextBlocks: [validIds[0]],
|
|
},
|
|
],
|
|
['64abc1234def567890fedcab'],
|
|
);
|
|
});
|
|
|
|
it('should not perform updates if no category is provided', async () => {
|
|
const mockFind = jest.spyOn(blockRepository, 'find');
|
|
const prepareBlocksInCategoryUpdateScope = jest.spyOn(
|
|
blockRepository,
|
|
'prepareBlocksInCategoryUpdateScope',
|
|
);
|
|
const prepareBlocksOutOfCategoryUpdateScope = jest.spyOn(
|
|
blockRepository,
|
|
'prepareBlocksOutOfCategoryUpdateScope',
|
|
);
|
|
|
|
await blockRepository.preUpdateMany({} as any, {}, { $set: {} });
|
|
|
|
expect(mockFind).not.toHaveBeenCalled();
|
|
expect(prepareBlocksInCategoryUpdateScope).not.toHaveBeenCalled();
|
|
expect(prepareBlocksOutOfCategoryUpdateScope).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|