Skip to content

Commit

Permalink
add redis health indicator and improved unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
AHS12 committed Oct 26, 2024
1 parent 363e733 commit aa8cc06
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 84 deletions.
7 changes: 7 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { RedisModule } from '@nestjs-modules/ioredis';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
Expand All @@ -8,6 +9,7 @@ import { AppController } from './app.controller';
import { CreateModuleCommand } from './commands/create-module.command';
import { XSecureInstallCommand } from './commands/xsecurity.command';
import mikroOrmConfig from './config/mikro-orm.config';
import redisConfig from './config/redis.config';
import { XSecurityMiddleware } from './middlewares/xsecurity.middleware';
import { AuthModule } from './modules/auth/auth.module';
import { CacheModule } from './modules/cache/cache.module';
Expand Down Expand Up @@ -40,6 +42,11 @@ import { UserModule } from './modules/user/user.module';
},
],
}),
RedisModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => redisConfig(configService),
inject: [ConfigService],
}),
CommandModule,
HealthModule,
MiscModule,
Expand Down
69 changes: 69 additions & 0 deletions src/config/redis.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { RedisModuleOptions } from '@nestjs-modules/ioredis';
import { ConfigService } from '@nestjs/config';
import { config } from 'dotenv';
import { getConfigValue } from '../utils/helper';

// Load environment variables for CLI usage
config();

export class RedisConfig {
constructor(private readonly configService?: ConfigService) {}

configureOptions(): RedisModuleOptions {
return {
type: 'single',
options: {
host: getConfigValue<string>(
'REDIS_HOST',
'localhost',
this.configService,
),
port: Number(
getConfigValue<string>('REDIS_PORT', '6379', this.configService),
),
username: getConfigValue<string>(
'REDIS_USERNAME',
'',
this.configService,
),
password: getConfigValue<string>(
'REDIS_PASSWORD',
'',
this.configService,
),
db: Number(getConfigValue<string>('REDIS_DB', '0', this.configService)),
maxRetriesPerRequest: Number(
getConfigValue<string>('REDIS_MAX_RETRIES', '3', this.configService),
),
connectTimeout: Number(
getConfigValue<string>(
'REDIS_CONNECT_TIMEOUT',
'10000',
this.configService,
),
),
retryStrategy: (times: number) =>
Math.min(
times *
Number(
getConfigValue<string>(
'REDIS_RETRY_DELAY',
'50',
this.configService,
),
),
Number(
getConfigValue<string>(
'REDIS_RETRY_DELAY_MAX',
'2000',
this.configService,
),
),
),
},
};
}
}

export default (configService?: ConfigService) =>
new RedisConfig(configService).configureOptions();
71 changes: 19 additions & 52 deletions src/modules/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,31 @@
import { EntityManager, EntityRepository } from '@mikro-orm/core';
import { getRepositoryToken } from '@mikro-orm/nestjs';
import { ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { Test, TestingModule } from '@nestjs/testing';
import { CacheModule } from '../cache/cache.module';
import { CacheService } from '../cache/cache.service';
import { MiscModule } from '../misc/misc.module';
import { User } from '../user/entities/user.entity';
import { UserTransformer } from '../user/transformer/user.transformer';
import { UserService } from '../user/user.service';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt.strategy';

const mockCacheService = {
get: jest.fn(),
set: jest.fn(),
del: jest.fn(),
delAll: jest.fn(),
const mockUserService = {
findByEmailWithRoleAndPermissions: jest.fn().mockResolvedValue({
id: 1,
name: 'John Doe',
email: '[email protected]',
password: '$2b$10$q81aKunjGaLbPvt5biUjFeSXLKhXVsMtsNxF8.Nwjx8I5l7OcU7sy',
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
roles: [],
permissions: [],
}),
updateLoginDate: jest.fn(),
};

describe('AuthService', () => {
let service: AuthService;
let mockUserRepository: Partial<EntityRepository<User>>;
let mockEntityManager: Partial<EntityManager>;
let mockConfigService: Partial<ConfigService>;

beforeEach(async () => {
mockUserRepository = {
findOne: jest.fn().mockReturnValue({
id: 1,
name: 'John Doe',
email: '[email protected]',
password:
'$2b$10$q81aKunjGaLbPvt5biUjFeSXLKhXVsMtsNxF8.Nwjx8I5l7OcU7sy',
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
}),
create: jest.fn().mockImplementation((dto) => ({
...dto,
id: 1,
createdAt: new Date(),
updatedAt: new Date(),
})),
assign: jest.fn().mockImplementation((user, dto) => {
Object.assign(user, dto);
}),
};

mockEntityManager = {
persistAndFlush: jest.fn(),
flush: jest.fn(),
removeAndFlush: jest.fn(),
};

mockConfigService = {
get: jest.fn((key: string) => {
if (key === 'JWT_SECRET') {
Expand All @@ -70,25 +42,20 @@ describe('AuthService', () => {
signOptions: { expiresIn: '1h' },
}),
MiscModule,
CacheModule,
],
providers: [
AuthService,
UserService,
UserTransformer,
{
provide: UserService,
useValue: mockUserService,
},
{
provide: JwtStrategy,
useFactory: (configService: ConfigService) =>
new JwtStrategy(configService),
inject: [ConfigService],
},
{ provide: ConfigService, useValue: mockConfigService },
{ provide: EntityManager, useValue: mockEntityManager },
{ provide: getRepositoryToken(User), useValue: mockUserRepository },
{
provide: CacheService,
useValue: mockCacheService,
},
],
}).compile();

Expand All @@ -108,8 +75,8 @@ describe('AuthService', () => {
isActive: true,
createdAt: expect.any(Date),
AccessToken: expect.any(String),
roles: undefined,
permissions: undefined,
roles: [],
permissions: [],
});
});
});
32 changes: 2 additions & 30 deletions src/modules/cache/cache.module.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
import { RedisModule } from '@nestjs-modules/ioredis';
import { Global, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ConfigModule } from '@nestjs/config';
import { CacheService } from './cache.service';

@Global()
@Module({
imports: [
ConfigModule,
RedisModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'single',
options: {
host: configService.get('REDIS_HOST'),
port: Number(configService.get<number>('REDIS_PORT')),
username: configService.get('REDIS_USERNAME'),
password: configService.get('REDIS_PASSWORD'),
db: Number(configService.get<number>('REDIS_DB')),
maxRetriesPerRequest: Number(
configService.get<number>('REDIS_MAX_RETRIES'),
),
connectTimeout: Number(
configService.get<number>('REDIS_CONNECT_TIMEOUT'),
),
retryStrategy: (times) =>
Math.min(
times * Number(configService.get<number>('REDIS_RETRY_DELAY')),
Number(configService.get<number>('REDIS_RETRY_DELAY_MAX')),
),
},
}),
inject: [ConfigService],
}),
],
imports: [ConfigModule],
providers: [CacheService],
exports: [CacheService],
})
Expand Down
3 changes: 3 additions & 0 deletions src/modules/health/health.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
MemoryHealthIndicator,
MikroOrmHealthIndicator,
} from '@nestjs/terminus';
import { RedisHealthIndicator } from './redis-health-indicator.service';

@Controller('health')
export class HealthController {
Expand All @@ -18,6 +19,7 @@ export class HealthController {
private db: MikroOrmHealthIndicator,
private disk: DiskHealthIndicator,
private memory: MemoryHealthIndicator,
private redis: RedisHealthIndicator,
) {}

@Get()
Expand All @@ -31,6 +33,7 @@ export class HealthController {
() => this.db.pingCheck('database'),
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
() => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
() => this.redis.isHealthy('redis'),
]);
}
}
7 changes: 5 additions & 2 deletions src/modules/health/health.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { RedisModule } from '@nestjs-modules/ioredis';
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
import { HttpModule } from '@nestjs/axios';
import { RedisHealthIndicator } from './redis-health-indicator.service';

@Module({
imports: [TerminusModule, HttpModule],
imports: [TerminusModule, HttpModule, RedisModule],
controllers: [HealthController],
providers: [RedisHealthIndicator],
})
export class HealthModule {}
Loading

0 comments on commit aa8cc06

Please sign in to comment.