fix: unit tests

This commit is contained in:
Mohamed Marrouchi 2024-09-21 19:43:04 +01:00
parent 7bc5270551
commit 93bb9ee04f
21 changed files with 87 additions and 72 deletions

View File

@ -11,7 +11,6 @@ import { ForbiddenException, Injectable, Optional } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose'; import { InjectModel } from '@nestjs/mongoose';
import { Document, Model, Query, TFilterQuery } from 'mongoose'; import { Document, Model, Query, TFilterQuery } from 'mongoose';
import { LoggerService } from '@/logger/logger.service';
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository'; import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
import { Category } from '../schemas/category.schema'; import { Category } from '../schemas/category.schema';
@ -19,17 +18,13 @@ import { BlockService } from '../services/block.service';
@Injectable() @Injectable()
export class CategoryRepository extends BaseRepository<Category> { export class CategoryRepository extends BaseRepository<Category> {
private readonly logger: LoggerService;
private readonly blockService: BlockService; private readonly blockService: BlockService;
constructor( constructor(
@InjectModel(Category.name) readonly model: Model<Category>, @InjectModel(Category.name) readonly model: Model<Category>,
@Optional() blockService?: BlockService, @Optional() blockService?: BlockService,
@Optional() logger?: LoggerService,
) { ) {
super(model, Category); super(model, Category);
this.logger = logger;
this.blockService = blockService; this.blockService = blockService;
} }

View File

@ -16,6 +16,7 @@ import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
import { import {
Label, Label,
LABEL_POPULATE,
LabelDocument, LabelDocument,
LabelFull, LabelFull,
LabelPopulate, LabelPopulate,
@ -31,7 +32,7 @@ export class LabelRepository extends BaseRepository<
@InjectModel(Label.name) readonly model: Model<Label>, @InjectModel(Label.name) readonly model: Model<Label>,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
) { ) {
super(model, Label); super(model, Label, LABEL_POPULATE, LabelFull);
} }
/** /**

View File

@ -19,6 +19,7 @@ import { BaseRepository } from '@/utils/generics/base-repository';
import { import {
Message, Message,
MESSAGE_POPULATE,
MessageFull, MessageFull,
MessagePopulate, MessagePopulate,
} from '../schemas/message.schema'; } from '../schemas/message.schema';
@ -40,7 +41,12 @@ export class MessageRepository extends BaseRepository<
@Optional() nlpSampleService?: NlpSampleService, @Optional() nlpSampleService?: NlpSampleService,
@Optional() logger?: LoggerService, @Optional() logger?: LoggerService,
) { ) {
super(model, Message as new () => AnyMessage); super(
model,
Message as new () => AnyMessage,
MESSAGE_POPULATE,
MessageFull,
);
this.logger = logger; this.logger = logger;
this.nlpSampleService = nlpSampleService; this.nlpSampleService = nlpSampleService;
} }

View File

@ -24,6 +24,7 @@ import { BaseRepository } from '@/utils/generics/base-repository';
import { SubscriberUpdateDto } from '../dto/subscriber.dto'; import { SubscriberUpdateDto } from '../dto/subscriber.dto';
import { import {
Subscriber, Subscriber,
SUBSCRIBER_POPULATE,
SubscriberDocument, SubscriberDocument,
SubscriberFull, SubscriberFull,
SubscriberPopulate, SubscriberPopulate,
@ -39,7 +40,7 @@ export class SubscriberRepository extends BaseRepository<
@InjectModel(Subscriber.name) readonly model: Model<Subscriber>, @InjectModel(Subscriber.name) readonly model: Model<Subscriber>,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
) { ) {
super(model, Subscriber); super(model, Subscriber, SUBSCRIBER_POPULATE, SubscriberFull);
} }
/** /**

View File

@ -177,7 +177,10 @@ describe('BlockService', () => {
blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [], blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [],
})); }));
expect(blockRepository.findAndPopulate).toHaveBeenCalledWith({}); expect(blockRepository.findAndPopulate).toHaveBeenCalledWith(
{},
undefined,
);
expect(result).toEqualPayload(blocksWithCategory); expect(result).toEqualPayload(blocksWithCategory);
}); });
}); });

View File

@ -31,7 +31,7 @@ export class NlpSampleRepository extends BaseRepository<
@InjectModel(NlpSample.name) readonly model: Model<NlpSample>, @InjectModel(NlpSample.name) readonly model: Model<NlpSample>,
private readonly nlpSampleEntityRepository: NlpSampleEntityRepository, private readonly nlpSampleEntityRepository: NlpSampleEntityRepository,
) { ) {
super(model, NlpSample, NLP_SAMPLE_POPULATE); super(model, NlpSample, NLP_SAMPLE_POPULATE, NlpSampleFull);
} }
/** /**

View File

@ -12,10 +12,18 @@ import { Injectable } from '@nestjs/common';
import { BaseSeeder } from '@/utils/generics/base-seeder'; import { BaseSeeder } from '@/utils/generics/base-seeder';
import { NlpEntityRepository } from '../repositories/nlp-entity.repository'; import { NlpEntityRepository } from '../repositories/nlp-entity.repository';
import { NlpEntity } from '../schemas/nlp-entity.schema'; import {
NlpEntity,
NlpEntityFull,
NlpEntityPopulate,
} from '../schemas/nlp-entity.schema';
@Injectable() @Injectable()
export class NlpEntitySeeder extends BaseSeeder<NlpEntity> { export class NlpEntitySeeder extends BaseSeeder<
NlpEntity,
NlpEntityPopulate,
NlpEntityFull
> {
constructor(nlpEntityRepository: NlpEntityRepository) { constructor(nlpEntityRepository: NlpEntityRepository) {
super(nlpEntityRepository); super(nlpEntityRepository);
} }

View File

@ -14,10 +14,18 @@ import { BaseSeeder } from '@/utils/generics/base-seeder';
import { NlpEntityRepository } from '../repositories/nlp-entity.repository'; import { NlpEntityRepository } from '../repositories/nlp-entity.repository';
import { NlpValueRepository } from '../repositories/nlp-value.repository'; import { NlpValueRepository } from '../repositories/nlp-value.repository';
import { NlpValue } from '../schemas/nlp-value.schema'; import {
NlpValue,
NlpValueFull,
NlpValuePopulate,
} from '../schemas/nlp-value.schema';
@Injectable() @Injectable()
export class NlpValueSeeder extends BaseSeeder<NlpValue> { export class NlpValueSeeder extends BaseSeeder<
NlpValue,
NlpValuePopulate,
NlpValueFull
> {
constructor( constructor(
nlpValueRepository: NlpValueRepository, nlpValueRepository: NlpValueRepository,
private readonly nlpEntityRepository: NlpEntityRepository, private readonly nlpEntityRepository: NlpEntityRepository,

View File

@ -92,10 +92,8 @@ describe('ModelController', () => {
describe('find', () => { describe('find', () => {
it('should find models', async () => { it('should find models', async () => {
jest.spyOn(modelService, 'findAndPopulate'); jest.spyOn(modelService, 'findAndPopulate');
const result = await modelController.find([], {}); const result = await modelController.find(['permissions'], {});
expect(modelService.findAndPopulate).toHaveBeenCalledWith({}, [ expect(modelService.findAndPopulate).toHaveBeenCalledWith({});
'permissions',
]);
expect(result).toEqualPayload( expect(result).toEqualPayload(
modelFixtures.map((modelFixture) => ({ modelFixtures.map((modelFixture) => ({
...modelFixture, ...modelFixture,
@ -122,9 +120,7 @@ describe('ModelController', () => {
return acc; return acc;
}, []); }, []);
expect(modelService.findAndPopulate).toHaveBeenCalledWith({}, [ expect(modelService.findAndPopulate).toHaveBeenCalledWith({});
'permissions',
]);
expect(result).toEqualPayload(modelsWithPermissionsAndUsers); expect(result).toEqualPayload(modelsWithPermissionsAndUsers);
}); });
}); });

View File

@ -148,9 +148,7 @@ describe('UserController', () => {
it('should find one user and populate its roles', async () => { it('should find one user and populate its roles', async () => {
jest.spyOn(userService, 'findOneAndPopulate'); jest.spyOn(userService, 'findOneAndPopulate');
const result = await userController.findOne(user.id, ['roles']); const result = await userController.findOne(user.id, ['roles']);
expect(userService.findOneAndPopulate).toHaveBeenCalledWith(user.id, [ expect(userService.findOneAndPopulate).toHaveBeenCalledWith(user.id);
'roles',
]);
expect(result).toEqualPayload( expect(result).toEqualPayload(
{ {
...userFixtures.find(({ username }) => username === 'admin'), ...userFixtures.find(({ username }) => username === 'admin'),
@ -166,9 +164,7 @@ describe('UserController', () => {
it('should find users, and for each user populate the corresponding roles', async () => { it('should find users, and for each user populate the corresponding roles', async () => {
jest.spyOn(userService, 'findPageAndPopulate'); jest.spyOn(userService, 'findPageAndPopulate');
const result = await userService.findPageAndPopulate({}, pageQuery, [ const result = await userService.findPageAndPopulate({}, pageQuery);
'roles',
]);
const usersWithRoles = userFixtures.reduce((acc, currUser) => { const usersWithRoles = userFixtures.reduce((acc, currUser) => {
acc.push({ acc.push({
@ -181,7 +177,6 @@ describe('UserController', () => {
expect(userService.findPageAndPopulate).toHaveBeenCalledWith( expect(userService.findPageAndPopulate).toHaveBeenCalledWith(
{}, {},
pageQuery, pageQuery,
['roles'],
); );
expect(result).toEqualPayload(usersWithRoles, [ expect(result).toEqualPayload(usersWithRoles, [
...IGNORED_FIELDS, ...IGNORED_FIELDS,

View File

@ -23,7 +23,7 @@ import { Model as ModelType } from './../schemas/model.schema';
import { ModelRepository } from '../repositories/model.repository'; import { ModelRepository } from '../repositories/model.repository';
import { PermissionRepository } from '../repositories/permission.repository'; import { PermissionRepository } from '../repositories/permission.repository';
import { ModelModel } from '../schemas/model.schema'; import { ModelModel } from '../schemas/model.schema';
import { PermissionModel, Permission } from '../schemas/permission.schema'; import { Permission, PermissionModel } from '../schemas/permission.schema';
describe('ModelRepository', () => { describe('ModelRepository', () => {
let modelRepository: ModelRepository; let modelRepository: ModelRepository;
@ -69,7 +69,7 @@ describe('ModelRepository', () => {
jest.spyOn(modelModel, 'find'); jest.spyOn(modelModel, 'find');
const allModels = await modelRepository.findAll(); const allModels = await modelRepository.findAll();
const allPermissions = await permissionRepository.findAll(); const allPermissions = await permissionRepository.findAll();
const result = await modelRepository.findAndPopulate({}, ['permissions']); const result = await modelRepository.findAndPopulate({});
const modelsWithPermissions = allModels.reduce((acc, currModel) => { const modelsWithPermissions = allModels.reduce((acc, currModel) => {
acc.push({ acc.push({
...currModel, ...currModel,

View File

@ -13,6 +13,7 @@ import { MongooseModule, getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { Model } from 'mongoose'; import { Model } from 'mongoose';
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { IGNORED_TEST_FIELDS } from '@/utils/test/constants'; import { IGNORED_TEST_FIELDS } from '@/utils/test/constants';
import { installPermissionFixtures } from '@/utils/test/fixtures/permission'; import { installPermissionFixtures } from '@/utils/test/fixtures/permission';
@ -53,7 +54,12 @@ describe('UserRepository', () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [
rootMongooseTestModule(installPermissionFixtures), rootMongooseTestModule(installPermissionFixtures),
MongooseModule.forFeature([UserModel, PermissionModel, RoleModel]), MongooseModule.forFeature([
UserModel,
PermissionModel,
RoleModel,
AttachmentModel,
]),
], ],
providers: [ providers: [
LoggerService, LoggerService,
@ -85,9 +91,7 @@ describe('UserRepository', () => {
describe('findOneAndPopulate', () => { describe('findOneAndPopulate', () => {
it('should find one user and populate its role', async () => { it('should find one user and populate its role', async () => {
jest.spyOn(userModel, 'findById'); jest.spyOn(userModel, 'findById');
const result = await userRepository.findOneAndPopulate(user.id, [ const result = await userRepository.findOneAndPopulate(user.id);
'roles',
]);
expect(userModel.findById).toHaveBeenCalledWith(user.id); expect(userModel.findById).toHaveBeenCalledWith(user.id);
expect(result).toEqualPayload( expect(result).toEqualPayload(
{ {
@ -106,9 +110,7 @@ describe('UserRepository', () => {
jest.spyOn(userRepository, 'findPageAndPopulate'); jest.spyOn(userRepository, 'findPageAndPopulate');
const allUsers = await userRepository.findAll(); const allUsers = await userRepository.findAll();
const allRoles = await roleRepository.findAll(); const allRoles = await roleRepository.findAll();
const result = await userRepository.findPageAndPopulate({}, pageQuery, [ const result = await userRepository.findPageAndPopulate({}, pageQuery);
'roles',
]);
const usersWithRoles = allUsers.reduce((acc, currUser) => { const usersWithRoles = allUsers.reduce((acc, currUser) => {
acc.push({ acc.push({
...currUser, ...currUser,

View File

@ -12,10 +12,10 @@ import { Injectable } from '@nestjs/common';
import { BaseSeeder } from '@/utils/generics/base-seeder'; import { BaseSeeder } from '@/utils/generics/base-seeder';
import { ModelRepository } from '../repositories/model.repository'; import { ModelRepository } from '../repositories/model.repository';
import { Model } from '../schemas/model.schema'; import { Model, ModelFull, ModelPopulate } from '../schemas/model.schema';
@Injectable() @Injectable()
export class ModelSeeder extends BaseSeeder<Model> { export class ModelSeeder extends BaseSeeder<Model, ModelPopulate, ModelFull> {
constructor(private readonly modelRepository: ModelRepository) { constructor(private readonly modelRepository: ModelRepository) {
super(modelRepository); super(modelRepository);
} }

View File

@ -12,10 +12,18 @@ import { Injectable } from '@nestjs/common';
import { BaseSeeder } from '@/utils/generics/base-seeder'; import { BaseSeeder } from '@/utils/generics/base-seeder';
import { PermissionRepository } from '../repositories/permission.repository'; import { PermissionRepository } from '../repositories/permission.repository';
import { Permission } from '../schemas/permission.schema'; import {
Permission,
PermissionFull,
PermissionPopulate,
} from '../schemas/permission.schema';
@Injectable() @Injectable()
export class PermissionSeeder extends BaseSeeder<Permission> { export class PermissionSeeder extends BaseSeeder<
Permission,
PermissionPopulate,
PermissionFull
> {
constructor(private readonly permissionRepository: PermissionRepository) { constructor(private readonly permissionRepository: PermissionRepository) {
super(permissionRepository); super(permissionRepository);
} }

View File

@ -12,10 +12,10 @@ import { Injectable } from '@nestjs/common';
import { BaseSeeder } from '@/utils/generics/base-seeder'; import { BaseSeeder } from '@/utils/generics/base-seeder';
import { RoleRepository } from '../repositories/role.repository'; import { RoleRepository } from '../repositories/role.repository';
import { Role } from '../schemas/role.schema'; import { Role, RoleFull, RolePopulate } from '../schemas/role.schema';
@Injectable() @Injectable()
export class RoleSeeder extends BaseSeeder<Role> { export class RoleSeeder extends BaseSeeder<Role, RolePopulate, RoleFull> {
constructor(private readonly roleRepository: RoleRepository) { constructor(private readonly roleRepository: RoleRepository) {
super(roleRepository); super(roleRepository);
} }

View File

@ -12,10 +12,10 @@ import { Injectable } from '@nestjs/common';
import { BaseSeeder } from '@/utils/generics/base-seeder'; import { BaseSeeder } from '@/utils/generics/base-seeder';
import { UserRepository } from '../repositories/user.repository'; import { UserRepository } from '../repositories/user.repository';
import { User } from '../schemas/user.schema'; import { User, UserFull, UserPopulate } from '../schemas/user.schema';
@Injectable() @Injectable()
export class UserSeeder extends BaseSeeder<User> { export class UserSeeder extends BaseSeeder<User, UserPopulate, UserFull> {
constructor(private readonly userRepository: UserRepository) { constructor(private readonly userRepository: UserRepository) {
super(userRepository); super(userRepository);
} }

View File

@ -70,7 +70,7 @@ describe('ModelService', () => {
jest.spyOn(modelRepository, 'findAndPopulate'); jest.spyOn(modelRepository, 'findAndPopulate');
const models = await modelRepository.findAll(); const models = await modelRepository.findAll();
const permissions = await permissionRepository.findAll(); const permissions = await permissionRepository.findAll();
const result = await modelService.findAndPopulate({}, ['permissions']); const result = await modelService.findAndPopulate({});
const modelsWithPermissions = models.reduce((acc, currModel) => { const modelsWithPermissions = models.reduce((acc, currModel) => {
acc.push({ acc.push({
...currModel, ...currModel,
@ -80,9 +80,10 @@ describe('ModelService', () => {
}); });
return acc; return acc;
}, []); }, []);
expect(modelRepository.findAndPopulate).toHaveBeenCalledWith({}, [ expect(modelRepository.findAndPopulate).toHaveBeenCalledWith(
'permissions', {},
]); undefined,
);
expect(result).toEqualPayload(modelsWithPermissions); expect(result).toEqualPayload(modelsWithPermissions);
}); });
}); });

View File

@ -99,10 +99,8 @@ describe('UserService', () => {
describe('findOneAndPopulate', () => { describe('findOneAndPopulate', () => {
it('should find one user and populate its role', async () => { it('should find one user and populate its role', async () => {
jest.spyOn(userRepository, 'findOneAndPopulate'); jest.spyOn(userRepository, 'findOneAndPopulate');
const result = await userService.findOneAndPopulate(user.id, ['roles']); const result = await userService.findOneAndPopulate(user.id);
expect(userRepository.findOneAndPopulate).toHaveBeenCalledWith(user.id, [ expect(userRepository.findOneAndPopulate).toHaveBeenCalledWith(user.id);
'roles',
]);
expect(result).toEqualPayload( expect(result).toEqualPayload(
{ {
...userFixtures.find(({ username }) => username === 'admin'), ...userFixtures.find(({ username }) => username === 'admin'),
@ -118,9 +116,7 @@ describe('UserService', () => {
const pageQuery = getPageQuery<User>({ sort: ['_id', 'asc'] }); const pageQuery = getPageQuery<User>({ sort: ['_id', 'asc'] });
jest.spyOn(userRepository, 'findPageAndPopulate'); jest.spyOn(userRepository, 'findPageAndPopulate');
const allUsers = await userRepository.findAll(); const allUsers = await userRepository.findAll();
const result = await userService.findPageAndPopulate({}, pageQuery, [ const result = await userService.findPageAndPopulate({}, pageQuery);
'roles',
]);
const usersWithRoles = allUsers.reduce((acc, currUser) => { const usersWithRoles = allUsers.reduce((acc, currUser) => {
acc.push({ acc.push({
...currUser, ...currUser,
@ -132,7 +128,6 @@ describe('UserService', () => {
expect(userRepository.findPageAndPopulate).toHaveBeenCalledWith( expect(userRepository.findPageAndPopulate).toHaveBeenCalledWith(
{}, {},
pageQuery, pageQuery,
['roles'],
); );
expect(result).toEqualPayload(usersWithRoles); expect(result).toEqualPayload(usersWithRoles);
}); });

View File

@ -7,7 +7,6 @@
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited. * 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/ */
import { LoggerService } from '@nestjs/common';
import { Cache } from 'cache-manager'; import { Cache } from 'cache-manager';
export function Cacheable(cacheKey: string) { export function Cacheable(cacheKey: string) {
@ -20,7 +19,6 @@ export function Cacheable(cacheKey: string) {
descriptor.value = async function (...args: any[]) { descriptor.value = async function (...args: any[]) {
const cache: Cache = this.cacheManager; const cache: Cache = this.cacheManager;
const logger: LoggerService = this.logger; // Access the logger from the instance
if (!cache) { if (!cache) {
throw new Error( throw new Error(
@ -28,19 +26,15 @@ export function Cacheable(cacheKey: string) {
); );
} }
if (!logger) {
throw new Error('Cacheable() requires the the logger service.');
}
// Try to get cached data // Try to get cached data
try { try {
const cachedResult = await cache.get(cacheKey); const cachedResult = await cache.get(cacheKey);
if (cachedResult) { if (cachedResult) {
logger.debug(`Cache hit for key: ${cacheKey}`);
return cachedResult; return cachedResult;
} }
} catch (error) { } catch (error) {
logger.error(`Cache get error for key: ${cacheKey}:`, error); // eslint-disable-next-line no-console
console.error(`Cache get error for key: ${cacheKey}:`, error);
} }
// Call the original method if cache miss // Call the original method if cache miss
@ -49,9 +43,9 @@ export function Cacheable(cacheKey: string) {
// Set the new result in cache // Set the new result in cache
try { try {
await cache.set(cacheKey, result); await cache.set(cacheKey, result);
logger.debug(`Cache set for key: ${cacheKey}`);
} catch (error) { } catch (error) {
logger.error(`Cache set error for key: ${cacheKey}:`, error); // eslint-disable-next-line no-console
console.error(`Cache set error for key: ${cacheKey}:`, error);
} }
return result; return result;

View File

@ -161,9 +161,7 @@ export abstract class BaseRepository<
} }
async findOneAndPopulate(criteria: string | TFilterQuery<T>) { async findOneAndPopulate(criteria: string | TFilterQuery<T>) {
if (!this.populate || !this.clsPopulate) { this.ensureCanPopulate();
throw new Error('Cannot populate query');
}
const query = this.findOneQuery(criteria).populate(this.populate); const query = this.findOneQuery(criteria).populate(this.populate);
return await this.executeOne(query, this.clsPopulate); return await this.executeOne(query, this.clsPopulate);
} }

View File

@ -10,8 +10,12 @@
import { BaseRepository } from './base-repository'; import { BaseRepository } from './base-repository';
import { BaseSchema } from './base-schema'; import { BaseSchema } from './base-schema';
export abstract class BaseSeeder<T, P extends string = never> { export abstract class BaseSeeder<
constructor(protected readonly repository: BaseRepository<T, P>) {} T,
P extends string = never,
TFull extends Omit<T, P> = never,
> {
constructor(protected readonly repository: BaseRepository<T, P, TFull>) {}
async findAll(): Promise<T[]> { async findAll(): Promise<T[]> {
return await this.repository.findAll(); return await this.repository.findAll();