test: consolidate tests

This commit is contained in:
Mohamed Marrouchi
2025-06-11 11:02:46 +01:00
parent 6641b84f98
commit ab1d58ac17
6 changed files with 99 additions and 25 deletions

View File

@@ -95,8 +95,8 @@ export class BlockService extends BaseService<
block: B,
subscriber?: Subscriber,
) {
if (!subscriber) {
return block;
if (!subscriber || !subscriber.labels) {
return true; // No subscriber or labels to match against
}
const triggerLabels = block.trigger_labels.map((l: string | Label) =>

View File

@@ -207,12 +207,16 @@ describe('NlpSampleController', () => {
const patterns: NlpValueMatchPattern[] = [
{ entity: 'intent', match: 'value', value: 'nonexistent' },
];
jest.spyOn(nlpSampleService, 'findByPatternsAndPopulate');
const result = await nlpSampleController.findPage(
pageQuery,
['language', 'entities'],
{},
patterns,
);
expect(nlpSampleService.findByPatternsAndPopulate).toHaveBeenCalledTimes(
1,
);
expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});
@@ -220,7 +224,9 @@ describe('NlpSampleController', () => {
describe('count', () => {
it('should count the nlp samples', async () => {
jest.spyOn(nlpSampleService, 'count');
const result = await nlpSampleController.count({});
expect(nlpSampleService.count).toHaveBeenCalledTimes(1);
const count = nlpSampleFixtures.length;
expect(result).toEqual({ count });
});
@@ -478,7 +484,9 @@ describe('NlpSampleController', () => {
describe('filterCount', () => {
it('should count the nlp samples without patterns', async () => {
const filters = { text: 'Hello' };
jest.spyOn(nlpSampleService, 'countByPatterns');
const result = await nlpSampleController.filterCount(filters, []);
expect(nlpSampleService.countByPatterns).toHaveBeenCalledTimes(1);
expect(result).toEqual({ count: 1 });
});
@@ -487,7 +495,9 @@ describe('NlpSampleController', () => {
const patterns: NlpValueMatchPattern[] = [
{ entity: 'intent', match: 'value', value: 'greeting' },
];
jest.spyOn(nlpSampleService, 'countByPatterns');
const result = await nlpSampleController.filterCount(filters, patterns);
expect(nlpSampleService.countByPatterns).toHaveBeenCalledTimes(1);
expect(result).toEqual({ count: 1 });
});

View File

@@ -52,6 +52,35 @@ export class NlpSampleRepository extends BaseRepository<
super(model, NlpSample, NLP_SAMPLE_POPULATE, NlpSampleFull);
}
/**
* Normalize the filter query.
*
* @param filters - The filters to normalize.
* @returns The normalized filters.
*/
private normalizeFilters(
filters: TFilterQuery<NlpSample>,
): TFilterQuery<NlpSample> {
if (filters?.$and) {
return {
...filters,
$and: filters.$and.map((condition) => {
// @todo: think of a better way to handle language to objectId conversion
// This is a workaround for the fact that language is stored as an ObjectId
// in the database, but we want to filter by its string representation.
if ('language' in condition && condition.language) {
return {
...condition,
language: new Types.ObjectId(condition.language as string),
};
}
return condition;
}),
};
}
return filters;
}
/**
* Build the aggregation stages that restrict a *nlpSampleEntities* collection
* to links which:
@@ -77,27 +106,12 @@ export class NlpSampleRepository extends BaseRepository<
value: new Types.ObjectId(id),
}));
const normalizedFilters = this.normalizeFilters(filters);
return [
{
$match: {
// @todo: think of a better way to handle language to objectId conversion
// This is a workaround for the fact that language is stored as an ObjectId
// in the database, but we want to filter by its string representation.
...filters,
...(filters?.$and
? {
$and: filters.$and?.map((condition) => {
if ('language' in condition && condition.language) {
return {
language: new Types.ObjectId(
condition.language as string,
),
};
}
return condition;
}),
}
: {}),
...normalizedFilters,
},
},

View File

@@ -54,6 +54,7 @@ describe('NlpSampleService', () => {
let nlpEntityService: NlpEntityService;
let nlpSampleService: NlpSampleService;
let nlpSampleEntityService: NlpSampleEntityService;
let nlpValueService: NlpValueService;
let languageService: LanguageService;
let nlpSampleEntityRepository: NlpSampleEntityRepository;
let nlpSampleRepository: NlpSampleRepository;
@@ -100,6 +101,7 @@ describe('NlpSampleService', () => {
nlpEntityService,
nlpSampleService,
nlpSampleEntityService,
nlpValueService,
nlpSampleRepository,
nlpSampleEntityRepository,
nlpSampleEntityRepository,
@@ -109,6 +111,7 @@ describe('NlpSampleService', () => {
NlpEntityService,
NlpSampleService,
NlpSampleEntityService,
NlpValueService,
NlpSampleRepository,
NlpSampleEntityRepository,
NlpSampleEntityRepository,
@@ -364,17 +367,29 @@ describe('NlpSampleService', () => {
});
describe('findByPatterns', () => {
it('should return samples without providing patterns', async () => {
const result = await nlpSampleService.findByPatterns(
{ filters: {}, patterns: [] },
undefined,
);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
});
it('should return samples matching the given patterns', async () => {
// Assume pattern: entity 'intent', value 'greeting'
const patterns: NlpValueMatchPattern[] = [
{ entity: 'intent', match: 'value', value: 'greeting' },
];
jest.spyOn(nlpSampleRepository, 'findByEntities');
jest.spyOn(nlpValueService, 'findByPatterns');
const result = await nlpSampleService.findByPatterns(
{ filters: {}, patterns },
undefined,
);
expect(nlpSampleRepository.findByEntities).toHaveBeenCalled();
expect(nlpValueService.findByPatterns).toHaveBeenCalled();
expect(Array.isArray(result)).toBe(true);
expect(result[0].text).toBe('Hello');
});
@@ -384,11 +399,15 @@ describe('NlpSampleService', () => {
{ entity: 'intent', match: 'value', value: 'nonexistent' },
];
jest.spyOn(nlpSampleRepository, 'findByEntities');
jest.spyOn(nlpValueService, 'findByPatterns');
const result = await nlpSampleService.findByPatterns(
{ filters: {}, patterns },
undefined,
);
expect(nlpSampleRepository.findByEntities).not.toHaveBeenCalled();
expect(nlpValueService.findByPatterns).toHaveBeenCalled();
expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});
@@ -434,6 +453,19 @@ describe('NlpSampleService', () => {
});
});
it('should return populated NlpSampleFull without providing patterns', async () => {
const result = await nlpSampleService.findByPatternsAndPopulate(
{ filters: { text: /Hello/gi }, patterns: [] },
undefined,
);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
expect(result[0]).toBeInstanceOf(NlpSampleFull);
expect(result[0].entities).toBeDefined();
expect(Array.isArray(result[0].entities)).toBe(true);
});
it('should return an empty array if no samples match the patterns', async () => {
const patterns: NlpValueMatchPattern[] = [
{ entity: 'intent', match: 'value', value: 'nonexistent' },
@@ -474,15 +506,33 @@ describe('NlpSampleService', () => {
{ entity: 'intent', match: 'value', value: 'greeting' },
];
jest.spyOn(nlpSampleRepository, 'countByEntities');
jest.spyOn(nlpValueService, 'findByPatterns');
const count = await nlpSampleService.countByPatterns({
filters: {},
patterns,
});
expect(nlpSampleRepository.countByEntities).toHaveBeenCalled();
expect(nlpValueService.findByPatterns).toHaveBeenCalled();
expect(typeof count).toBe('number');
expect(count).toBe(2);
});
it('should return the correct count without providing patterns', async () => {
jest.spyOn(nlpSampleRepository, 'findByEntities');
jest.spyOn(nlpValueService, 'findByPatterns');
const count = await nlpSampleService.countByPatterns({
filters: {},
patterns: [],
});
expect(nlpSampleRepository.findByEntities).not.toHaveBeenCalled();
expect(nlpValueService.findByPatterns).not.toHaveBeenCalled();
expect(typeof count).toBe('number');
expect(count).toBeGreaterThan(2);
});
it('should return 0 if no samples match the patterns', async () => {
const patterns: NlpValueMatchPattern[] = [
{ entity: 'intent', match: 'value', value: 'nonexistent' },

View File

@@ -104,7 +104,7 @@ export class NlpSampleService extends BaseService<
* Same as `findByPatterns`, but also populates all relations declared
* in the repository (`populatePaths`).
*
* @param criteras `{ filters, patterns }`
* @param criteria `{ filters, patterns }`
* @param page Optional paging / sorting descriptor.
* @param projection Optional Mongo projection.
* @returns Promise resolving to the populated samples.

View File

@@ -378,7 +378,7 @@ export abstract class BaseRepository<
criteria: string | TFilterQuery<T>,
options?: ClassTransformOptions,
projection?: ProjectionType<T>,
) {
): Promise<T | null> {
if (!criteria) {
// @TODO : Issue a warning ?
return null;
@@ -768,7 +768,7 @@ export abstract class BaseRepository<
* @param filter Mongo filter selecting the documents to update.
* @param dto Update payload.
* @param options `{ shouldFlatten?: boolean }`.
* @returns MongoDB `UpdateWriteOpResult` describing the operation outcome.
* @returns Promise that resolves a MongoDB `UpdateWriteOpResult` describing the operation outcome.
*/
async updateMany<D extends Partial<U>>(
filter: TFilterQuery<T>,