mirror of
https://github.com/hexastack/hexabot
synced 2025-05-05 21:34:41 +00:00
Merge pull request #483 from Hexastack/fix/settings-emit
fix: setting emit + unit tests
This commit is contained in:
commit
85f5733710
186
api/src/setting/repositories/setting.repository.spec.ts
Normal file
186
api/src/setting/repositories/setting.repository.spec.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* 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 { getModelToken, MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
|
||||||
|
import { installSettingFixtures } from '@/utils/test/fixtures/setting';
|
||||||
|
import {
|
||||||
|
closeInMongodConnection,
|
||||||
|
rootMongooseTestModule,
|
||||||
|
} from '@/utils/test/test';
|
||||||
|
|
||||||
|
import { Setting, SettingModel } from '../schemas/setting.schema';
|
||||||
|
import { SettingType } from '../schemas/types';
|
||||||
|
|
||||||
|
import { SettingRepository } from './setting.repository';
|
||||||
|
|
||||||
|
describe('SettingRepository', () => {
|
||||||
|
let settingRepository: SettingRepository;
|
||||||
|
let settingModel: Model<Setting>;
|
||||||
|
let eventEmitter: EventEmitter2;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
rootMongooseTestModule(installSettingFixtures),
|
||||||
|
MongooseModule.forFeature([SettingModel]),
|
||||||
|
],
|
||||||
|
providers: [SettingRepository, EventEmitter2],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
settingRepository = module.get<SettingRepository>(SettingRepository);
|
||||||
|
settingModel = module.get<Model<Setting>>(getModelToken(Setting.name));
|
||||||
|
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(closeInMongodConnection);
|
||||||
|
|
||||||
|
describe('preCreateValidate', () => {
|
||||||
|
it('should validate setting value during creation', async () => {
|
||||||
|
const mockSetting = new settingModel({
|
||||||
|
type: SettingType.text,
|
||||||
|
value: 'Sample Text',
|
||||||
|
});
|
||||||
|
jest.spyOn(settingRepository, 'validateSettingValue');
|
||||||
|
|
||||||
|
await settingRepository.preCreateValidate(mockSetting);
|
||||||
|
|
||||||
|
expect(settingRepository['validateSettingValue']).toHaveBeenCalledWith(
|
||||||
|
SettingType.text,
|
||||||
|
'Sample Text',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid value type', async () => {
|
||||||
|
const mockSetting = new settingModel({
|
||||||
|
type: SettingType.checkbox,
|
||||||
|
value: 'Invalid Value',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
settingRepository.preCreateValidate(mockSetting),
|
||||||
|
).rejects.toThrow('Setting Model : Value must be a boolean!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('preUpdateValidate', () => {
|
||||||
|
it('should validate updated setting value', async () => {
|
||||||
|
const criteria = { _id: '123' };
|
||||||
|
const updates = {
|
||||||
|
$set: { value: 'Updated Text' },
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.spyOn(settingRepository, 'findOne').mockResolvedValue({
|
||||||
|
type: SettingType.text,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
await settingRepository.preUpdateValidate(criteria, updates);
|
||||||
|
|
||||||
|
expect(settingRepository.findOne).toHaveBeenCalledWith(criteria);
|
||||||
|
expect(settingRepository['validateSettingValue']).toHaveBeenCalledWith(
|
||||||
|
SettingType.text,
|
||||||
|
'Updated Text',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('postUpdate', () => {
|
||||||
|
it('should emit an event after updating a setting', async () => {
|
||||||
|
const mockSetting = new settingModel({
|
||||||
|
group: 'general',
|
||||||
|
label: 'theme',
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.spyOn(eventEmitter, 'emit');
|
||||||
|
|
||||||
|
await settingRepository.postUpdate({} as any, mockSetting);
|
||||||
|
|
||||||
|
expect(eventEmitter.emit).toHaveBeenCalledWith(
|
||||||
|
'hook:general:theme',
|
||||||
|
mockSetting,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateSettingValue', () => {
|
||||||
|
it('should validate value types correctly', () => {
|
||||||
|
expect(() =>
|
||||||
|
settingRepository['validateSettingValue'](
|
||||||
|
SettingType.text,
|
||||||
|
'Valid Text',
|
||||||
|
),
|
||||||
|
).not.toThrow();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
settingRepository['validateSettingValue'](SettingType.checkbox, true),
|
||||||
|
).not.toThrow();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
settingRepository['validateSettingValue'](SettingType.number, 123),
|
||||||
|
).not.toThrow();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
settingRepository['validateSettingValue'](SettingType.text, 123),
|
||||||
|
).toThrow('Setting Model : Value must be a string!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateSettingValue', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
type: SettingType.text,
|
||||||
|
value: 123,
|
||||||
|
error: 'Setting Model : Value must be a string!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.checkbox,
|
||||||
|
value: 'true',
|
||||||
|
error: 'Setting Model : Value must be a boolean!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.number,
|
||||||
|
value: '123',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.multiple_text,
|
||||||
|
value: ['valid', 123],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.attachment,
|
||||||
|
value: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.secret,
|
||||||
|
value: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.select,
|
||||||
|
value: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: SettingType.multiple_attachment,
|
||||||
|
value: [123, 'valid'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach(({ type, value }) => {
|
||||||
|
it(`should throw an error when value type does not match SettingType.${type}`, () => {
|
||||||
|
expect(() =>
|
||||||
|
settingRepository['validateSettingValue'](type, value),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -7,18 +7,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import {
|
||||||
|
EventEmitter2,
|
||||||
|
IHookSettingsGroupLabelOperationMap,
|
||||||
|
} from '@nestjs/event-emitter';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import {
|
import {
|
||||||
Document,
|
Document,
|
||||||
FilterQuery,
|
FilterQuery,
|
||||||
Model,
|
Model,
|
||||||
|
Query,
|
||||||
Types,
|
Types,
|
||||||
UpdateQuery,
|
UpdateQuery,
|
||||||
UpdateWithAggregationPipeline,
|
UpdateWithAggregationPipeline,
|
||||||
} from 'mongoose';
|
} from 'mongoose';
|
||||||
|
|
||||||
import { I18nService } from '@/i18n/services/i18n.service';
|
|
||||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||||
|
|
||||||
import { Setting } from '../schemas/setting.schema';
|
import { Setting } from '../schemas/setting.schema';
|
||||||
@ -29,7 +32,6 @@ export class SettingRepository extends BaseRepository<Setting> {
|
|||||||
constructor(
|
constructor(
|
||||||
readonly eventEmitter: EventEmitter2,
|
readonly eventEmitter: EventEmitter2,
|
||||||
@InjectModel(Setting.name) readonly model: Model<Setting>,
|
@InjectModel(Setting.name) readonly model: Model<Setting>,
|
||||||
private readonly i18n: I18nService,
|
|
||||||
) {
|
) {
|
||||||
super(eventEmitter, model, Setting);
|
super(eventEmitter, model, Setting);
|
||||||
}
|
}
|
||||||
@ -55,6 +57,32 @@ export class SettingRepository extends BaseRepository<Setting> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event after a `Setting` has been updated.
|
||||||
|
*
|
||||||
|
* This method is used to synchronize global settings by emitting an event
|
||||||
|
* based on the `group` and `label` of the `Setting`.
|
||||||
|
*
|
||||||
|
* @param _query The Mongoose query object used to find and update the document.
|
||||||
|
* @param setting The updated `Setting` object.
|
||||||
|
*/
|
||||||
|
async postUpdate(
|
||||||
|
_query: Query<
|
||||||
|
Document<Setting, any, any>,
|
||||||
|
Document<Setting, any, any>,
|
||||||
|
unknown,
|
||||||
|
Setting,
|
||||||
|
'findOneAndUpdate'
|
||||||
|
>,
|
||||||
|
setting: Setting,
|
||||||
|
) {
|
||||||
|
const group = setting.group as keyof IHookSettingsGroupLabelOperationMap;
|
||||||
|
const label = setting.label as '*';
|
||||||
|
|
||||||
|
// Sync global settings var
|
||||||
|
this.eventEmitter.emit(`hook:${group}:${label}`, setting);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the `Setting` document after it has been retrieved.
|
* Validates the `Setting` document after it has been retrieved.
|
||||||
*
|
*
|
||||||
@ -65,7 +93,7 @@ export class SettingRepository extends BaseRepository<Setting> {
|
|||||||
*
|
*
|
||||||
* @param setting The `Setting` document to be validated.
|
* @param setting The `Setting` document to be validated.
|
||||||
*/
|
*/
|
||||||
private validateSettingValue(type: SettingType, value: any) {
|
public validateSettingValue(type: SettingType, value: any) {
|
||||||
if (
|
if (
|
||||||
(type === SettingType.text || type === SettingType.textarea) &&
|
(type === SettingType.text || type === SettingType.textarea) &&
|
||||||
typeof value !== 'string' &&
|
typeof value !== 'string' &&
|
||||||
|
Loading…
Reference in New Issue
Block a user