Repository: dhiazfathra/nestjs-starter
Pull request #24: feat: add chaos testing module for Redis cache with failure simulation
Author: login: dhiazfathra
Labels: name: released
Created: 2025-05-19T02:48:00Z | Merged: 2025-05-19T11:05:27Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced chaos testing capabilities for Redis caching, allowing simulation of random failures and runtime toggling of cache availability.
  - Added admin-protected API endpoints to enable/disable Redis caching, set failure probability, and check chaos status.
  - Integrated a GitHub Actions workflow to automate chaos testing during CI/CD.

- **Documentation**
  - Updated README with details on chaos testing features, API usage, and workflow instructions.

- **Bug Fixes**
  - None.

- **Refactor**
  - Switched all uses of the `Role` enum to use the version from the Prisma client package.

- **Tests**
  - Added comprehensive tests for chaos-related cache behavior and new controller endpoints.

- **Chores**
  - Increased Jest global branch coverage threshold from 70% to 75%.
  - Raised Codecov coverage targets from 70% to 80%.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.github/workflows/chaos-test.yml b/.github/workflows/chaos-test.yml
new file mode 100644
index 0000000..9c00646
--- /dev/null
+++ b/.github/workflows/chaos-test.yml
@@ -0,0 +1,112 @@
+name: Chaos Testing
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+    branches: [main, master]
+  workflow_dispatch:
+    inputs:
+      redis_failure_probability:
+        description: 'Redis failure probability (0-1)'
+        required: true
+        default: '0.3'
+        type: string
+
+jobs:
+  chaos-test:
+    runs-on: ubuntu-latest
+
+    services:
+      redis:
+        image: redis:7-alpine
+        ports:
+          - 6379:6379
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+      postgres:
+        image: postgres:17-alpine
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_USER: postgres
+          POSTGRES_DB: test
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '22.x'
+          cache: 'npm'
+
+      - name: Install dependencies
+        run: npm ci
+
+      - name: Build application
+        run: npm run build
+
+      - name: Start application in background
+        run: |
+          npm run start:prod &
+          sleep 10
+        env:
+          NODE_ENV: test
+          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+          REDIS_HOST: localhost
+          REDIS_PORT: 6379
+          JWT_SECRET: test-secret
+
+      - name: Run chaos tests
+        run: |
+          # Create test user with admin role
+          curl -X POST http://localhost:3000/auth/register \
+            -H "Content-Type: application/json" \
+            -d '{"email":"admin@example.com","password":"Password123!","name":"Admin User"}'
+
+          # Login to get JWT token
+          TOKEN=$(curl -X POST http://localhost:3000/auth/login \
+            -H "Content-Type: application/json" \
+            -d '{"email":"admin@example.com","password":"Password123!"}' \
+            | jq -r '.access_token')
+
+          # Set Redis failure probability
+          PROBABILITY="${{ github.event.inputs.redis_failure_probability || '0.3' }}"
+          curl -X POST http://localhost:3000/chaos/set-probability \
+            -H "Content-Type: application/json" \
+            -H "Authorization: Bearer $TOKEN" \
+            -d "{\"probability\":$PROBABILITY}"
+
+          # Run a series of API calls to test resilience
+          for i in {1..50}; do
+            # Test various endpoints under chaos conditions
+            curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health || true
+
+            # Add more API calls to test here
+
+            echo "Completed test iteration $i"
+            sleep 1
+          done
+
+          # Get chaos testing status
+          curl -X GET http://localhost:3000/chaos/status \
+            -H "Authorization: Bearer $TOKEN"
+
+      - name: Check application logs
+        run: |
+          # Check for error patterns that would indicate resilience issues
+          if grep -q "FATAL ERROR" logs/error.log; then
+            echo "Fatal errors detected during chaos testing"
+            exit 1
+          fi
diff --git a/README.md b/README.md
index 702c81e..e6cace6 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ A NestJS TypeScript starter project with user authentication, following best pra
 - 🔑 **Role-Based Access Control** - User and Admin roles with proper guards
 - 🗃️ **Database Integration** - PostgreSQL with Prisma ORM
 - 🚀 **Redis Caching** - Performance optimization with Redis-based caching
+- 🧪 **Chaos Testing** - Resilience testing with Redis fault injection
 - 🛡️ **Rate Limiting** - Protection against abuse and DoS attacks
 - 📊 **Bundle Analysis** - Monitor and optimize bundle size with Codecov integration
 - ✅ **Validation** - Request validation using class-validator
@@ -208,6 +209,62 @@ const value = await cacheService.getOrSet(
 );
 ```
 
+## Chaos Testing
+
+This project includes chaos testing capabilities to ensure the application remains resilient when Redis experiences failures. The chaos testing system allows you to simulate Redis failures in a controlled manner.
+
+### Features
+
+- **Redis Fault Injection** - Simulate Redis failures with configurable probability
+- **Redis Toggle** - Enable/disable Redis cache entirely for testing
+- **API Control** - Manage chaos testing through a secure API
+- **GitHub Actions Integration** - Automated chaos testing in CI/CD pipeline
+
+### API Endpoints
+
+The chaos testing API is protected and requires admin authentication:
+
+```
+# Toggle Redis on/off
+POST /chaos/toggle-redis
+{ "enabled": true|false }
+
+# Set Redis failure probability (0-1)
+POST /chaos/set-probability
+{ "probability": 0.3 }
+
+# Get current chaos testing status
+GET /chaos/status
+```
+
+### GitHub Actions Workflow
+
+The project includes a GitHub Actions workflow for automated chaos testing:
+
+- **Trigger Events**: Runs on pushes to main/master, pull requests, or manual triggers
+- **Configurable**: Adjustable Redis failure probability
+- **Comprehensive**: Tests application resilience under Redis failures
+- **Verification**: Ensures no fatal errors occur during chaos conditions
+
+To manually trigger chaos testing:
+
+1. Go to the Actions tab in your GitHub repository
+2. Select the "Chaos Testing" workflow
+3. Click "Run workflow"
+4. Set your desired Redis failure probability
+5. Click "Run workflow"
+
+### Implementation
+
+Chaos testing is implemented in the `CacheService` with the following mechanisms:
+
+- Probability-based failure simulation
+- Graceful fallbacks for all cache operations
+- Comprehensive logging of simulated failures
+- Automatic factory execution when cache is disabled
+
+This approach ensures your application can handle Redis failures without affecting user experience.
+
 ## Distributed Tracing with Jaeger
 
 This project implements distributed tracing using Jaeger to help monitor and troubleshoot your application, especially in microservices architectures.
diff --git a/codecov.yml b/codecov.yml
index b8f719b..92322b4 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -7,7 +7,7 @@ coverage:
     project:
       default:
         # basic
-        target: 70%
+        target: 80%
         threshold: 5%
         # advanced
         if_not_found: success
@@ -16,7 +16,7 @@ coverage:
     patch:
       default:
         # basic
-        target: 70%
+        target: 80%
         threshold: 5%
         # advanced
         if_no_uploads: error
diff --git a/package.json b/package.json
index e81adee..8012929 100644
--- a/package.json
+++ b/package.json
@@ -122,7 +122,7 @@
     "testEnvironment": "node",
     "coverageThreshold": {
       "global": {
-        "branches": 70,
+        "branches": 75,
         "functions": 90,
         "lines": 90,
         "statements": 90
diff --git a/src/app.module.ts b/src/app.module.ts
index 695a5b4..aa8cc5a 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -11,6 +11,7 @@ import { HealthModule } from './health/health.module';
 import { MetricsMiddleware } from './health/metrics.middleware';
 import { TracingModule } from './tracing/tracing.module';
 import { TracingMiddleware } from './tracing/tracing.middleware';
+import { ChaosModule } from './chaos/chaos.module';
 
 @Module({
   imports: [
@@ -29,6 +30,7 @@ import { TracingMiddleware } from './tracing/tracing.middleware';
     AuthModule,
     HealthModule,
     TracingModule,
+    ChaosModule,
   ],
   controllers: [AppController],
   providers: [AppService],
diff --git a/src/auth/decorators/roles.decorator.spec.ts b/src/auth/decorators/roles.decorator.spec.ts
index 38b9991..f43d290 100644
--- a/src/auth/decorators/roles.decorator.spec.ts
+++ b/src/auth/decorators/roles.decorator.spec.ts
@@ -1,5 +1,5 @@
 import { SetMetadata } from '@nestjs/common';
-import { Role } from '../../common/enums/role.enum';
+import { Role } from '@prisma/client';
 import { Roles } from './roles.decorator';
 
 jest.mock('@nestjs/common', () => ({
diff --git a/src/auth/decorators/roles.decorator.ts b/src/auth/decorators/roles.decorator.ts
index 357e3ff..d19e675 100644
--- a/src/auth/decorators/roles.decorator.ts
+++ b/src/auth/decorators/roles.decorator.ts
@@ -1,4 +1,4 @@
 import { SetMetadata } from '@nestjs/common';
-import { Role } from '../../common/enums/role.enum';
+import { Role } from '@prisma/client';
 
 export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
diff --git a/src/auth/guards/roles.guard.spec.ts b/src/auth/guards/roles.guard.spec.ts
index 1ca11cd..d3a43af 100644
--- a/src/auth/guards/roles.guard.spec.ts
+++ b/src/auth/guards/roles.guard.spec.ts
@@ -1,7 +1,7 @@
 import { ExecutionContext } from '@nestjs/common';
 import { Reflector } from '@nestjs/core';
 import { Test, TestingModule } from '@nestjs/testing';
-import { Role } from '../../common/enums/role.enum';
+import { Role } from '@prisma/client';
 import { RolesGuard } from './roles.guard';
 
 describe('RolesGuard', () => {
diff --git a/src/auth/guards/roles.guard.ts b/src/auth/guards/roles.guard.ts
index aa3bbaf..d879333 100644
--- a/src/auth/guards/roles.guard.ts
+++ b/src/auth/guards/roles.guard.ts
@@ -1,6 +1,6 @@
 import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
 import { Reflector } from '@nestjs/core';
-import { Role } from '../../common/enums/role.enum';
+import { Role } from '@prisma/client';
 
 @Injectable()
 export class RolesGuard implements CanActivate {
diff --git a/src/cache/cache.module.ts b/src/cache/cache.module.ts
index 6c551dc..5680d98 100644
--- a/src/cache/cache.module.ts
+++ b/src/cache/cache.module.ts
@@ -1,6 +1,6 @@
-import { CacheModule } from '@nestjs/cache-manager';
 import { Module } from '@nestjs/common';
 import { ConfigModule, ConfigService } from '@nestjs/config';
+import { CacheModule } from '@nestjs/cache-manager';
 import { redisStore } from 'cache-manager-redis-store';
 import { CacheService } from './cache.service';
 
@@ -21,7 +21,7 @@ import { CacheService } from './cache.service';
       }),
     }),
   ],
-  providers: [CacheService],
+  providers: [CacheService, ConfigService],
   exports: [CacheModule, CacheService],
 })
 export class RedisCacheModule {}
diff --git a/src/cache/cache.service.spec.ts b/src/cache/cache.service.spec.ts
index ff1790e..31e0033 100644
--- a/src/cache/cache.service.spec.ts
+++ b/src/cache/cache.service.spec.ts
@@ -1,5 +1,6 @@
 import { CACHE_MANAGER } from '@nestjs/cache-manager';
 import { Logger } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
 import { Test, TestingModule } from '@nestjs/testing';
 import { Cache } from 'cache-manager';
 import { CacheService } from './cache.service';
@@ -7,6 +8,7 @@ import { CacheService } from './cache.service';
 describe('CacheService', () => {
   let service: CacheService;
   let cacheManager: Cache;
+  let _configService: ConfigService;
 
   const mockCacheManager = {
     get: jest.fn(),
@@ -22,14 +24,28 @@ describe('CacheService', () => {
           provide: CACHE_MANAGER,
           useValue: mockCacheManager,
         },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockImplementation((key, defaultValue) => {
+              if (key === 'CHAOS_REDIS_PROBABILITY') {
+                return 0.2; // Return test value for chaos probability
+              }
+              return defaultValue;
+            }),
+          },
+        },
       ],
     }).compile();
 
     service = module.get<CacheService>(CacheService);
     cacheManager = module.get(CACHE_MANAGER);
+    _configService = module.get(ConfigService);
 
     // Mock the logger to avoid console output during tests
     jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
+    jest.clearAllMocks();
   });
 
   afterEach(() => {
@@ -172,4 +188,99 @@ describe('CacheService', () => {
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'log');
+      await service.toggleRedis(false);
+
+      const result = await service.get('test-key');
+
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith('Redis cache disabled');
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+
+      await service.set('test-key', 'test-value');
+
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+
+      await service.del('test-key');
+
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+
+      await service.set('test-key', 'test-value');
+
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+
+      await service.del('test-key');
+
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should enable Redis cache and log the status', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'log');
+      await service.toggleRedis(true);
+
+      expect(service['isRedisEnabled']).toBe(true);
+      expect(loggerSpy).toHaveBeenCalledWith('Redis cache enabled');
+    });
+  });
 });
diff --git a/src/cache/cache.service.ts b/src/cache/cache.service.ts
index 447fbf1..834cb00 100644
--- a/src/cache/cache.service.ts
+++ b/src/cache/cache.service.ts
@@ -1,19 +1,38 @@
 import { CACHE_MANAGER } from '@nestjs/cache-manager';
-import { Inject, Injectable, Logger } from '@nestjs/common';
+import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
 import { Cache } from 'cache-manager';
 
 @Injectable()
-export class CacheService {
+export class CacheService implements OnModuleInit {
   private readonly logger = new Logger(CacheService.name);
+  private isRedisEnabled = true;
+  private chaosProbability = 0;
 
-  constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {}
+  constructor(
+    @Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
+    private readonly configService: ConfigService,
+  ) {}
+
+  async onModuleInit(): Promise<void> {
+    this.chaosProbability = this.configService.get<number>(
+      'CHAOS_REDIS_PROBABILITY',
+      0,
+    );
+  }
+
+  private shouldSimulateFailure(): boolean {
+    return this.chaosProbability > 0 && Math.random() < this.chaosProbability;
+  }
 
-  /**
-   * Get a value from cache
-   * @param key - Cache key
-   * @returns The cached value or undefined if not found
-   */
   async get<T>(key: string): Promise<T | undefined> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled for get operation on key ${key}`,
+      );
+      return undefined;
+    }
+
     try {
       return await this.cacheManager.get<T>(key);
     } catch (error) {
@@ -22,26 +41,29 @@ export class CacheService {
     }
   }
 
-  /**
-   * Set a value in cache
-   * @param key - Cache key
-   * @param value - Value to cache
-   * @param ttl - Time to live in seconds (optional)
-   */
   async set<T>(key: string, value: T, ttl?: number): Promise<void> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled for set operation on key ${key}`,
+      );
+      return;
+    }
+
     try {
-      // Pass ttl directly as the third parameter
       await this.cacheManager.set(key, value, ttl);
     } catch (error) {
       this.logger.error(`Failed to set cache key ${key}:`, error);
     }
   }
 
-  /**
-   * Delete a value from cache
-   * @param key - Cache key
-   */
   async del(key: string): Promise<void> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled for delete operation on key ${key}`,
+      );
+      return;
+    }
+
     try {
       await this.cacheManager.del(key);
     } catch (error) {
@@ -49,18 +71,18 @@ export class CacheService {
     }
   }
 
-  /**
-   * Get a value from cache or set it if not found
-   * @param key - Cache key
-   * @param factory - Function to generate value if not in cache
-   * @param ttl - Time to live in seconds (optional)
-   * @returns The cached or newly generated value
-   */
   async getOrSet<T>(
     key: string,
     factory: () => Promise<T>,
     ttl?: number,
   ): Promise<T> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled, directly executing factory for key ${key}`,
+      );
+      return factory();
+    }
+
     const cachedValue = await this.get<T>(key);
 
     if (cachedValue !== undefined) {
@@ -79,4 +101,14 @@ export class CacheService {
       throw error;
     }
   }
+
+  async toggleRedis(enabled: boolean): Promise<void> {
+    this.isRedisEnabled = enabled;
+    this.logger.log(`Redis cache ${enabled ? 'enabled' : 'disabled'}`);
+  }
+
+  async setChaosProbability(probability: number): Promise<void> {
+    this.chaosProbability = probability;
+    this.logger.log(`Set Redis chaos probability to ${probability}`);
+  }
 }
diff --git a/src/chaos/chaos.controller.spec.ts b/src/chaos/chaos.controller.spec.ts
new file mode 100644
index 0000000..3b1f8f7
--- /dev/null
+++ b/src/chaos/chaos.controller.spec.ts
@@ -0,0 +1,84 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { CacheService } from '../cache/cache.service';
+import { ChaosController } from './chaos.controller';
+
+describe('ChaosController', () => {
+  let controller: ChaosController;
+  let cacheService: {
+    toggleRedis: jest.Mock;
+    setChaosProbability: jest.Mock;
+    isRedisEnabled: boolean;
+    chaosProbability: number;
+  };
+
+  beforeEach(async () => {
+    cacheService = {
+      toggleRedis: jest.fn(),
+      setChaosProbability: jest.fn(),
+      isRedisEnabled: true,
+      chaosProbability: 0,
+    };
+
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [ChaosController],
+      providers: [{ provide: CacheService, useValue: cacheService }],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue({ canActivate: () => true })
+      .overrideGuard(RolesGuard)
+      .useValue({ canActivate: () => true })
+      .compile();
+
+    controller = module.get<ChaosController>(ChaosController);
+  });
+
+  describe('toggleRedis', () => {
+    it('should toggle Redis status to disabled', async () => {
+      const dto = { enabled: false };
+      const result = await controller.toggleRedis(dto);
+
+      expect(result).toEqual({ message: 'Redis cache disabled' });
+      expect(cacheService.toggleRedis).toHaveBeenCalledWith(false);
+    });
+
+    it('should toggle Redis status to enabled', async () => {
+      const dto = { enabled: true };
+      const result = await controller.toggleRedis(dto);
+
+      expect(result).toEqual({ message: 'Redis cache enabled' });
+      expect(cacheService.toggleRedis).toHaveBeenCalledWith(true);
+    });
+  });
+
+  describe('setProbability', () => {
+    it('should set chaos probability', async () => {
+      const dto = { probability: 0.5 };
+      const result = await controller.setProbability(dto);
+
+      expect(result).toEqual({ message: 'Set Redis chaos probability to 0.5' });
+      expect(cacheService.setChaosProbability).toHaveBeenCalledWith(0.5);
+    });
+
+    it('should reject invalid probability values', async () => {
+      const dto = { probability: 1.5 };
+      await expect(controller.setProbability(dto)).rejects.toThrow();
+    });
+  });
+
+  describe('getStatus', () => {
+    it('should return current chaos status', async () => {
+      cacheService.isRedisEnabled = false;
+      cacheService.chaosProbability = 0.3;
+
+      const result = await controller.getStatus();
+
+      expect(result).toEqual({
+        message: 'Chaos testing status',
+        redisEnabled: false,
+        chaosProbability: 0.3,
+      });
+    });
+  });
+});
diff --git a/src/chaos/chaos.controller.ts b/src/chaos/chaos.controller.ts
new file mode 100644
index 0000000..bf8b728
--- /dev/null
+++ b/src/chaos/chaos.controller.ts
@@ -0,0 +1,54 @@
+import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
+import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
+import { Role } from '@prisma/client';
+import { Roles } from '../auth/decorators/roles.decorator';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { CacheService } from '../cache/cache.service';
+
+export class ToggleRedisDto {
+  enabled: boolean;
+}
+
+export class SetChaosProbabilityDto {
+  probability: number;
+}
+
+@ApiTags('Chaos Testing')
+@Controller('chaos')
+@UseGuards(JwtAuthGuard, RolesGuard)
+@Roles(Role.ADMIN)
+export class ChaosController {
+  constructor(private readonly cacheService: CacheService) {}
+
+  @Post('toggle-redis')
+  @ApiOperation({ summary: 'Toggle Redis cache on/off' })
+  @ApiResponse({ status: 200, description: 'Redis cache toggled successfully' })
+  async toggleRedis(@Body() dto: ToggleRedisDto) {
+    await this.cacheService.toggleRedis(dto.enabled);
+    return { message: `Redis cache ${dto.enabled ? 'enabled' : 'disabled'}` };
+  }
+
+  @Post('set-probability')
+  @ApiOperation({ summary: 'Set Redis failure probability' })
+  @ApiResponse({ status: 200, description: 'Probability set successfully' })
+  @ApiResponse({ status: 400, description: 'Invalid probability value' })
+  async setProbability(@Body() dto: SetChaosProbabilityDto) {
+    if (dto.probability < 0 || dto.probability > 1) {
+      throw new Error('Probability must be between 0 and 1');
+    }
+    await this.cacheService.setChaosProbability(dto.probability);
+    return { message: `Set Redis chaos probability to ${dto.probability}` };
+  }
+
+  @Get('status')
+  @ApiOperation({ summary: 'Get current chaos testing status' })
+  @ApiResponse({ status: 200, description: 'Status retrieved successfully' })
+  async getStatus() {
+    return {
+      message: 'Chaos testing status',
+      redisEnabled: this.cacheService['isRedisEnabled'],
+      chaosProbability: this.cacheService['chaosProbability'],
+    };
+  }
+}
diff --git a/src/chaos/chaos.module.ts b/src/chaos/chaos.module.ts
new file mode 100644
index 0000000..554d01d
--- /dev/null
+++ b/src/chaos/chaos.module.ts
@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { ChaosController } from './chaos.controller';
+import { RedisCacheModule } from '../cache/cache.module';
+
+@Module({
+  imports: [RedisCacheModule],
+  controllers: [ChaosController],
+})
+export class ChaosModule {}
diff --git a/src/common/enums/role.enum.ts b/src/common/enums/role.enum.ts
deleted file mode 100644
index 7c99d21..0000000
--- a/src/common/enums/role.enum.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export enum Role {
-  USER = 'USER',
-  ADMIN = 'ADMIN',
-}
diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts
index f61a40e..c42b63e 100644
--- a/src/users/entities/user.entity.ts
+++ b/src/users/entities/user.entity.ts
@@ -1,5 +1,5 @@
 import { ApiProperty } from '@nestjs/swagger';
-import { Role } from '../../common/enums/role.enum';
+import { Role } from '@prisma/client';
 
 /**
  * User entity representing a user in the system
diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts
index 9162e1a..f1b145f 100644
--- a/src/users/users.controller.ts
+++ b/src/users/users.controller.ts
@@ -15,10 +15,10 @@ import {
   ApiResponse,
   ApiTags,
 } from '@nestjs/swagger';
+import { Role } from '@prisma/client';
 import { Roles } from '../auth/decorators/roles.decorator';
 import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
 import { RolesGuard } from '../auth/guards/roles.guard';
-import { Role } from '../common/enums/role.enum';
 import { CreateUserDto } from './dto/create-user.dto';
 import { UpdateUserDto } from './dto/update-user.dto';
 import { User } from './entities/user.entity';
diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts
index 467377c..9392cf3 100644
--- a/src/users/users.service.spec.ts
+++ b/src/users/users.service.spec.ts
@@ -1,7 +1,7 @@
 import { ConflictException, NotFoundException } from '@nestjs/common';
 import { Test, TestingModule } from '@nestjs/testing';
+import { Role } from '@prisma/client';
 import * as bcrypt from 'bcrypt';
-import { Role } from '../common/enums/role.enum';
 import { CacheService } from '../cache/cache.service';
 import { PrismaService } from '../prisma/prisma.service';
 import { UsersService } from './users.service';


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T02:48:32Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 13**

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b9d093dea3c15c5463cb5fa40a4c1296cb49fa56 and b6f393a6ff80a22816fc38ed600e6c23089b9573.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `.github/workflows/chaos-test.yml` (1 hunks)
* `package.json` (1 hunks)
* `src/app.module.ts` (2 hunks)
* `src/cache/cache.module.ts` (2 hunks)
* `src/cache/cache.service.spec.ts` (3 hunks)
* `src/cache/cache.service.ts` (3 hunks)
* `src/chaos/chaos.controller.spec.ts` (1 hunks)
* `src/chaos/chaos.controller.ts` (1 hunks)
* `src/chaos/chaos.module.ts` (1 hunks)
* `src/users/enums/role.enum.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/chaos/chaos.controller.ts (1)</summary><blockquote>

<details>
<summary>src/auth/decorators/roles.decorator.ts (1)</summary>

* `Roles` (4-4)

</details>

</blockquote></details>
<details>
<summary>src/cache/cache.service.ts (2)</summary><blockquote>

<details>
<summary>src/prisma/prisma.service.ts (1)</summary>

* `Injectable` (4-20)

</details>
<details>
<summary>src/users/users.service.ts (1)</summary>

* `Injectable` (12-155)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>src/users/enums/role.enum.ts</summary>

[warning] 2-3: src/users/enums/role.enum.ts#L2-L3
Added lines #L2 - L3 were not covered by tests

</details>

</details>
<details>
<summary>🪛 actionlint (1.7.7)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

46-46: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

---

49-49: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

</details>

</details>
<details>
<summary>🪛 Checkov (3.2.334)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

[MEDIUM] 66-67: Basic Auth Credentials

(CKV_SECRET_4)

</details>

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

[warning] 3-3: truthy value should be one of [false, true]

(truthy)

---

[error] 19-19: trailing spaces

(trailing-spaces)

---

[error] 30-30: trailing spaces

(trailing-spaces)

---

[error] 53-53: trailing spaces

(trailing-spaces)

---

[error] 56-56: trailing spaces

(trailing-spaces)

---

[error] 59-59: trailing spaces

(trailing-spaces)

---

[error] 70-70: trailing spaces

(trailing-spaces)

---

[error] 77-77: trailing spaces

(trailing-spaces)

---

[error] 83-83: trailing spaces

(trailing-spaces)

---

[error] 90-90: trailing spaces

(trailing-spaces)

---

[error] 95-95: trailing spaces

(trailing-spaces)

---

[error] 97-97: trailing spaces

(trailing-spaces)

---

[error] 101-101: trailing spaces

(trailing-spaces)

---

[error] 105-105: trailing spaces

(trailing-spaces)

</details>

</details>
<details>
<summary>🪛 GitHub Check: ESLint</summary>

<details>
<summary>src/cache/cache.service.spec.ts</summary>

[failure] 203-203: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 212-212: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 227-227: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 229-229: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 239-239: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 241-241: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 252-252: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 254-254: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 265-265: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 267-267: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>package.json (1)</summary>

`125-125`: **Great improvement to code quality standards!**

Increasing the branch coverage threshold from 70% to 75% aligns well with the addition of chaos testing functionality. This demonstrates a commitment to maintaining high-quality, well-tested code as new features are introduced.

</details>
<details>
<summary>src/app.module.ts (2)</summary>

`14-14`: **Clean import of the new ChaosModule.**

The import follows NestJS conventions and maintains code organization.

---

`33-33`: **Proper integration of ChaosModule in the application.**

The ChaosModule is correctly added to the imports array, following the existing pattern and enabling the chaos testing functionality throughout the application.

</details>
<details>
<summary>src/users/enums/role.enum.ts (1)</summary>

`1-4`: <details>
<summary>✅ Verification successful</summary>

**Well-structured Role enumeration for access control.**

This Role enum follows TypeScript best practices by using string literals for values. The restriction of chaos testing capabilities to ADMIN users (as mentioned in the summary) is a good security practice.



While the static analysis indicates this enum lacks direct test coverage, its usage should be verified in tests for components that depend on it:

---

🏁 Script executed:

```shell
#!/bin/bash
# Check how the Role enum is used in the codebase
rg -A 3 "Role\.ADMIN" 
```

Length of output: 2590

---



</details>

**Role enum is properly used and tested**

The `Role` enum is referenced across controllers, guards, decorators, and even in the Prisma seed script. Unit tests in:
- `src/auth/guards/roles.guard.spec.ts` cover both `Role.ADMIN` and `Role.USER`
- `src/auth/decorators/roles.decorator.spec.ts` verify metadata for single and multiple roles

These usages effectively exercise the enum’s values. No additional direct tests are needed. Approving the implementation as-is.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 2-3: src/users/enums/role.enum.ts#L2-L3
Added lines #L2 - L3 were not covered by tests

</details>

</details>

</details>
<details>
<summary>src/chaos/chaos.module.ts (1)</summary>

`1-9`: **Well-structured ChaosModule following NestJS patterns.**

The module correctly imports RedisCacheModule and declares ChaosController, following NestJS architectural patterns. The clean organization maintains separation of concerns while enabling the chaos testing functionality.

</details>
<details>
<summary>src/cache/cache.module.ts (2)</summary>

`3-3`: **Clean import reordering.**

The import order follows the convention of grouping NestJS core modules first, followed by other imports.

---

`24-24`: **Proper dependency injection setup.**

Adding ConfigService to the providers array ensures it's available for injection in the CacheService, which is necessary for implementing the chaos testing features that need access to configuration values.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-19T02:57:07Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (12)</summary><blockquote>

<details>
<summary>src/cache/cache.service.spec.ts (2)</summary><blockquote>

`45-48`: **Move jest.clearAllMocks() before creating spies**

The current order will discard the logger spies immediately after creation. Move the clearAllMocks call before creating the spies:

```diff
-    // Mock the logger to avoid console output during tests
-    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
-    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
-    jest.clearAllMocks();
+    // Reset mocks from previous test runs
+    jest.clearAllMocks();
+
+    // Mock the logger to avoid console output during tests
+    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
```

---

`192-273`: **Add Math.random mock restoration in afterEach**

Tests that mock Math.random don't restore it afterward. This could affect subsequent tests in this file or others that rely on genuine randomness. 

```diff
afterEach(() => {
  jest.clearAllMocks();
+  if ((Math.random as jest.Mock)?.mockRestore) {
+    (Math.random as jest.Mock).mockRestore();
+  }
});
```

Remove non-visible "middot" characters (··· · ·) flagged by ESLint from indentation in lines 203, 212, 227, 229, 239, 241, 252, 254, 265, 267. These cause lint failures but don't affect runtime behavior.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: ESLint</summary>

[failure] 203-203: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 212-212: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 227-227: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 229-229: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 239-239: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 241-241: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 252-252: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 254-254: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 265-265: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 267-267: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

</details>

</details>

</blockquote></details>
<details>
<summary>src/chaos/chaos.controller.spec.ts (1)</summary><blockquote>

`56-59`: **Expand invalid probability validation tests**

The test only checks for probability values greater than 1. Add tests for negative values and non-numeric inputs to ensure robust controller-level validation:

```diff
it('should reject invalid probability values', async () => {
  const dto = { probability: 1.5 };
  await expect(controller.setProbability(dto)).rejects.toThrow();
+  
+  // Test negative value
+  await expect(controller.setProbability({ probability: -0.1 })).rejects.toThrow();
+  
+  // Test non-numeric input
+  await expect(controller.setProbability({ probability: 'abc' as any })).rejects.toThrow();
});
```

</blockquote></details>
<details>
<summary>.github/workflows/chaos-test.yml (3)</summary><blockquote>

`46-53`: **Upgrade GitHub Actions to latest major versions**

Both actions/checkout@v3 and actions/setup-node@v3 have newer v4 releases with performance & security improvements:

```diff
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4

-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 actionlint (1.7.7)</summary>

46-46: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

---

49-49: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 53-53: trailing spaces

(trailing-spaces)

</details>

</details>

---

`64-70`: **Avoid committing plaintext credentials in workflow file**

Hard-coding DATABASE_URL with user/password and JWT_SECRET in the workflow exposes them in build logs and forked PRs.

```diff
-          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+          DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
-          JWT_SECRET: test-secret
+          JWT_SECRET: ${{ secrets.CI_JWT_SECRET }}
```

Move these secrets to repository/environment secrets and reference them via ${{ secrets.<NAME> }}.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Checkov (3.2.334)</summary>

[MEDIUM] 66-67: Basic Auth Credentials

(CKV_SECRET_4)

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 70-70: trailing spaces

(trailing-spaces)

</details>

</details>

---

`91-100`: **Improve the chaos test loop for better resilience testing**

The current loop only tests a health endpoint serially with fixed sleeps. Consider:

1. Randomizing endpoints to test different parts of the application:
```diff
 for i in {1..50}; do
-  # Test various endpoints under chaos conditions
-  curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health || true
-  
-  # Add more API calls to test here
+  # Randomly select an endpoint to test
+  ENDPOINTS=("health" "users" "users/profile" "auth/profile")
+  ENDPOINT=${ENDPOINTS[$RANDOM % ${#ENDPOINTS[@]}]}
+  echo "Testing endpoint: $ENDPOINT"
+  curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000/$ENDPOINT" \
+    -H "Authorization: Bearer $TOKEN" || true
```

2. Adding parallelism to better test concurrency issues:
```diff
-for i in {1..50}; do
+ITERATIONS="${{ github.event.inputs.iterations || '50' }}"
+echo "Running $ITERATIONS test iterations with some in parallel"
+for i in $(seq 1 $ITERATIONS); do
+  # Run every 5th iteration in background to test concurrent requests
+  if [ $((i % 5)) -eq 0 ]; then
+    (
       # Test code
+      echo "Completed parallel test iteration $i"
+    ) &
+  else
     # Test code
+    echo "Completed test iteration $i"
+  fi
```

3. Make iteration count configurable as a workflow input parameter.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 95-95: trailing spaces

(trailing-spaces)

---

[error] 97-97: trailing spaces

(trailing-spaces)

</details>

</details>

</blockquote></details>
<details>
<summary>src/chaos/chaos.controller.ts (3)</summary><blockquote>

`9-15`: _🛠️ Refactor suggestion_

**Add validation decorators to DTOs to prevent malformed payloads**  

`ToggleRedisDto` and `SetChaosProbabilityDto` still accept any JSON shape, so `ValidationPipe` cannot protect the endpoint.  This was raised earlier but not applied.  
 

```diff
-import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { IsBoolean, IsNumber, Min, Max } from 'class-validator';

 export class ToggleRedisDto {
-  enabled: boolean;
+  @IsBoolean()
+  enabled: boolean;
 }

 export class SetChaosProbabilityDto {
-  probability: number;
+  @IsNumber()
+  @Min(0)
+  @Max(1)
+  probability: number;
 }
```

---

`36-39`: _⚠️ Potential issue_

**Throw `BadRequestException` instead of bare `Error`**  

Using a native `Error` yields a 500 response, not the advertised 400.  The previous review already flagged this.  
 

```diff
-import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { Controller, Get, Post, Body, UseGuards, BadRequestException } from '@nestjs/common';

-    if (dto.probability < 0 || dto.probability > 1) {
-      throw new Error('Probability must be between 0 and 1');
+    if (dto.probability < 0 || dto.probability > 1) {
+      throw new BadRequestException('Probability must be between 0 and 1');
     }
```

---

`50-51`: _🛠️ Refactor suggestion_

**Stop reaching into private fields; expose getters on `CacheService`**  

Bracket-notation access breaks encapsulation and will fail if the field name changes (e.g. after minification).  
 

```diff
-      redisEnabled: this.cacheService['isRedisEnabled'],
-      chaosProbability: this.cacheService['chaosProbability'],
+      redisEnabled: this.cacheService.getRedisEnabled(),
+      chaosProbability: this.cacheService.getChaosProbability(),
```

Add in `CacheService`:

```diff
+  getRedisEnabled(): boolean {
+    return this.isRedisEnabled;
+  }
+
+  getChaosProbability(): number {
+    return this.chaosProbability;
+  }
```

</blockquote></details>
<details>
<summary>src/cache/cache.service.ts (3)</summary><blockquote>

`17-22`: _⚠️ Potential issue_

**Parse numeric env var to avoid `NaN` at runtime**  

`ConfigService.get<number>()` still returns a string, so `chaosProbability` may become `NaN`.  
 

```diff
-    this.chaosProbability = this.configService.get<number>(
-      'CHAOS_REDIS_PROBABILITY',
-      0,
-    );
+    const raw = this.configService.get<string>('CHAOS_REDIS_PROBABILITY', '0');
+    const parsed = parseFloat(raw);
+    this.chaosProbability = Number.isFinite(parsed) ? parsed : 0;
```

---

`24-26`: _🛠️ Refactor suggestion_

**`shouldSimulateFailure()` is evaluated multiple times per call**  

`getOrSet` runs the probability check once, then delegates to `get` / `set`, which each re-evaluate it, effectively tripling the failure rate.  
 

```diff
-    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+    const chaos = !this.isRedisEnabled || this.shouldSimulateFailure();
+    if (chaos) {
       this.logger.warn(
         `[Chaos] Cache disabled, directly executing factory for key ${key}`,
       );
       return factory();
     }

-    const cachedValue = await this.get<T>(key);
+    // Bypass inner calls if chaos already decided
+    const cachedValue = chaos ? undefined : await this.cacheManager.get<T>(key);
```
(you would apply the same single-evaluation approach for `get`, `set`, and `del`).


Also applies to: 79-85

---

`105-113`: _🧹 Nitpick (assertive)_

**`toggleRedis` / `setChaosProbability` don’t need to be `async`**  

Both are synchronous property assignments—marking them `async` misleads callers and forces unnecessary `await`s in the controller.  
 

```diff
-  async toggleRedis(enabled: boolean): Promise<void> {
+  toggleRedis(enabled: boolean): void {
     this.isRedisEnabled = enabled;
     this.logger.log(`Redis cache ${enabled ? 'enabled' : 'disabled'}`);
   }

-  async setChaosProbability(probability: number): Promise<void> {
+  setChaosProbability(probability: number): void {
     this.chaosProbability = probability;
     this.logger.log(`Set Redis chaos probability to ${probability}`);
   }
```

Remember to drop the `await` keywords in `ChaosController`.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b6f393a6ff80a22816fc38ed600e6c23089b9573 and 8e856ba85a73456548d7f4f52b9922132bab3978.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `.github/workflows/chaos-test.yml` (1 hunks)
* `package.json` (1 hunks)
* `src/app.module.ts` (2 hunks)
* `src/cache/cache.module.ts` (2 hunks)
* `src/cache/cache.service.spec.ts` (3 hunks)
* `src/cache/cache.service.ts` (3 hunks)
* `src/chaos/chaos.controller.spec.ts` (1 hunks)
* `src/chaos/chaos.controller.ts` (1 hunks)
* `src/chaos/chaos.module.ts` (1 hunks)
* `src/users/enums/role.enum.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/cache/cache.service.ts (1)</summary><blockquote>

<details>
<summary>src/users/users.service.ts (1)</summary>

* `Injectable` (12-155)

</details>

</blockquote></details>
<details>
<summary>src/chaos/chaos.controller.ts (2)</summary><blockquote>

<details>
<summary>src/auth/decorators/roles.decorator.ts (1)</summary>

* `Roles` (4-4)

</details>
<details>
<summary>src/cache/index.ts (1)</summary>

* `CacheService` (2-2)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>src/users/enums/role.enum.ts</summary>

[warning] 2-3: src/users/enums/role.enum.ts#L2-L3
Added lines #L2 - L3 were not covered by tests

</details>

</details>
<details>
<summary>🪛 GitHub Check: ESLint</summary>

<details>
<summary>src/cache/cache.service.spec.ts</summary>

[failure] 203-203: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 212-212: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 227-227: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 229-229: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 239-239: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 241-241: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 252-252: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 254-254: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 265-265: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 267-267: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

</details>

</details>
<details>
<summary>🪛 actionlint (1.7.7)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

46-46: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

---

49-49: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

</details>

</details>
<details>
<summary>🪛 Checkov (3.2.334)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

[MEDIUM] 66-67: Basic Auth Credentials

(CKV_SECRET_4)

</details>

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

[warning] 3-3: truthy value should be one of [false, true]

(truthy)

---

[error] 19-19: trailing spaces

(trailing-spaces)

---

[error] 30-30: trailing spaces

(trailing-spaces)

---

[error] 53-53: trailing spaces

(trailing-spaces)

---

[error] 56-56: trailing spaces

(trailing-spaces)

---

[error] 59-59: trailing spaces

(trailing-spaces)

---

[error] 70-70: trailing spaces

(trailing-spaces)

---

[error] 77-77: trailing spaces

(trailing-spaces)

---

[error] 83-83: trailing spaces

(trailing-spaces)

---

[error] 90-90: trailing spaces

(trailing-spaces)

---

[error] 95-95: trailing spaces

(trailing-spaces)

---

[error] 97-97: trailing spaces

(trailing-spaces)

---

[error] 101-101: trailing spaces

(trailing-spaces)

---

[error] 105-105: trailing spaces

(trailing-spaces)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>package.json (1)</summary>

`125-125`: **Good coverage threshold increase**

Increasing the branch coverage threshold from 70% to 75% aligns well with the added chaos testing functionality and its test suite. This change encourages maintaining good test coverage for the new Redis cache chaos testing features.

</details>
<details>
<summary>src/app.module.ts (2)</summary>

`14-14`: **LGTM: Clean ChaosModule import**

The import statement for the ChaosModule is correctly placed with the other module imports.

---

`33-33`: **LGTM: Module integration looks good**

The ChaosModule is properly added to the imports array, integrating the chaos testing capabilities into the application.

</details>
<details>
<summary>src/users/enums/role.enum.ts (1)</summary>

`1-4`: <details>
<summary>✅ Verification successful</summary>

**Add test coverage for role-based authorization**

The Role enum looks good, but static analysis indicates it lacks test coverage. Consider adding tests that verify the role-based authorization logic that uses this enum.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the Role enum is used in any tests or auth guards

echo "Checking for Role enum usage in tests:"
rg -A 3 -B 3 "Role\." --glob "*.spec.ts"

echo "Checking for Role enum usage in guards or auth-related files:"
rg -A 3 -B 3 "Role\." --glob "**/guards/*.ts" --glob "**/auth/*.ts"
```

Length of output: 10312

---



</details>

**Role enum is already covered by existing tests**

Tests referencing `Role` are present and validate its use in authorization logic:

- src/users/users.service.spec.ts (user creation role checks)  
- src/auth/auth.service.spec.ts (JWT payload includes `role`)  
- src/auth/guards/roles.guard.spec.ts (guard allows/denies based on `Role.ADMIN`/`Role.USER`)  
- src/auth/decorators/roles.decorator.spec.ts (metadata set with `Role.ADMIN`, `Role.USER`)  

No additional tests are needed for coverage of the `Role` enum.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 2-3: src/users/enums/role.enum.ts#L2-L3
Added lines #L2 - L3 were not covered by tests

</details>

</details>

</details>
<details>
<summary>src/cache/cache.module.ts (1)</summary>

`24-24`: **Good addition of ConfigService to providers**

Adding ConfigService to the providers array enables the CacheService to access configuration data for chaos testing features. This change aligns well with the Redis cache failure simulation objective.

</details>
<details>
<summary>src/cache/cache.service.spec.ts (1)</summary>

`27-37`: **Well-structured mock of ConfigService for chaos testing**

The mock implementation of ConfigService is well-designed, returning a specific test value (0.2) for the chaos probability configuration. This enables proper testing of the chaos features without relying on actual environment configuration.

</details>
<details>
<summary>src/chaos/chaos.controller.spec.ts (2)</summary>

`16-35`: **Good test setup with proper guard overrides**

The test module setup correctly mocks the CacheService and overrides the JwtAuthGuard and RolesGuard to allow access during tests. This approach properly isolates the controller tests from authentication concerns.

---

`62-74`: **Well-implemented status endpoint test**

The test for the getStatus endpoint properly verifies that the controller returns the current chaos testing status with all expected fields. Good practice to set specific values on the mock before testing to ensure accurate assertions.

</details>
<details>
<summary>.github/workflows/chaos-test.yml (1)</summary>

`8-14`: **Good implementation of workflow_dispatch with configurable parameters**

Using workflow_dispatch with a configurable redis_failure_probability input allows for flexible manual testing. This is a good practice for chaos testing workflows where you might want to adjust failure rates to simulate different scenarios.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-19T07:37:38Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>src/cache/cache.service.spec.ts (3)</summary><blockquote>

`45-49`: **Reorder jest.clearAllMocks() to avoid clearing spies**

The current order clears all mocks after creating spies, which wipes out the spies you just created. Move jest.clearAllMocks() before the spy calls to ensure the spies remain active.


```diff
-    // Mock the logger to avoid console output during tests
-    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
-    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
-    jest.clearAllMocks();
+    // Reset mocks from previous test runs
+    jest.clearAllMocks();
+    
+    // Mock the logger to avoid console output during tests
+    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
```

---

`200-207`: **Restore Math.random mock after test to avoid affecting other tests**

The mock for Math.random is not being restored, which could affect subsequent tests that rely on randomness.


```diff
it('should simulate failure based on probability', async () => {
-  jest.spyOn(Math, 'random').mockReturnValue(0.1);
+  const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1);
  await service.setChaosProbability(0.2);

  const result = await service.get('test-key');
  expect(result).toBeUndefined();
  expect(cacheManager.get).not.toHaveBeenCalled();
+  randomSpy.mockRestore();
});
```

Alternatively, add a restore in the afterEach block:

```diff
afterEach(() => {
  jest.clearAllMocks();
+  if ((Math.random as jest.Mock)?.mockRestore) {
+    (Math.random as jest.Mock).mockRestore();
+  }
});
```

---

`219-222`: **Verify ConfigService.get call in onModuleInit test**

The test verifies that chaosProbability is set correctly but doesn't verify that ConfigService.get was called with the correct key.


```diff
it('should initialize chaos probability from config on module init', async () => {
  await service.onModuleInit();
  expect(service['chaosProbability']).toBe(0.2);
+  expect(_configService.get).toHaveBeenCalledWith('CHAOS_REDIS_PROBABILITY', expect.any(Number));
});
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8e856ba85a73456548d7f4f52b9922132bab3978 and 3e11cd538b379c1f3d14ded403b0daed59e02877.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `README.md` (2 hunks)
* `src/cache/cache.service.spec.ts` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>README.md</summary>

227-227: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>README.md (1)</summary>

`212-267`: **Well-documented chaos testing implementation with clear instructions**

The chaos testing documentation is comprehensive and well-structured, covering the purpose, features, API endpoints, GitHub Actions workflow, and implementation details. This makes it easy for users to understand and utilize the chaos testing capabilities.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

227-227: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</details>
<details>
<summary>src/cache/cache.service.spec.ts (2)</summary>

`192-273`: **Comprehensive test coverage for chaos testing functionality**

The tests thoroughly cover the chaos testing functionality, including:
- Redis disabling behavior
- Probability-based failure simulation
- Factory execution when Redis is disabled
- Config initialization
- Warning logs for skipped operations

This ensures that the chaos testing feature behaves as expected and properly handles failure scenarios.

---

`27-37`: **Well-implemented ConfigService mock with targeted test value**

The ConfigService mock is well-implemented, returning a specific test value for the chaos probability configuration and falling back to default values for other keys.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-19T07:57:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>.github/workflows/chaos-test.yml (3)</summary><blockquote>

`92-100`: **Randomize & parallelize the chaos-test loop**  
A fixed, serial 50-iteration loop against only `/health` may miss intermittent failures. As suggested in earlier reviews, consider:
- Making the iteration count configurable via a workflow input  
- Randomly picking from a set of endpoints (user CRUD, cache endpoints, etc.)  
- Running batches in parallel (e.g. `xargs -P` or background subshells)  

This will surface concurrency/race issues more reliably.

---

`66-70`: _⚠️ Potential issue_

**Avoid committing plaintext `DATABASE_URL` and `JWT_SECRET`**  
These values should also come from GitHub Actions secrets, not be hard-coded:

```diff
       env:
-        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+        DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
         REDIS_HOST:    localhost
         REDIS_PORT:    6379
-        JWT_SECRET:    test-secret
+        JWT_SECRET:    ${{ secrets.CI_JWT_SECRET }}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Checkov (3.2.334)</summary>

[MEDIUM] 66-67: Basic Auth Credentials

(CKV_SECRET_4)

</details>

</details>

---

`106-112`: _⚠️ Potential issue_

**Ensure log file exists & check multiple error patterns**  
The current `grep` only looks for `"FATAL ERROR"` in `logs/error.log`, but:
1. You’re not redirecting your app’s output into that file.
2. Only one pattern is checked.

Proposed diff to capture logs and scan a broader set of error messages:

```diff
-      - name: Check application logs
-        run: |
-          # Check for error patterns that would indicate resilience issues
-          if grep -q "FATAL ERROR" logs/error.log; then
-            echo "Fatal errors detected during chaos testing"
-            exit 1
-          fi
+      - name: Check application logs
+        run: |
+          mkdir -p logs
+          # Ensure application logs are captured
+          pkill -f "npm run start:prod" || true
+          mv application.log logs/error.log 2>/dev/null || true
+
+          ERROR_PATTERNS=("FATAL ERROR" "Unhandled exception" "Error:" "failed with code")
+          FAILURES=0
+          for PATTERN in "${ERROR_PATTERNS[@]}"; do
+            COUNT=$(grep -c "$PATTERN" logs/error.log || echo 0)
+            if [ "$COUNT" -gt 0 ]; then
+              echo "Found $COUNT occurrences of '$PATTERN'"
+              FAILURES=$((FAILURES + COUNT))
+            fi
+          done
+          if [ "$FAILURES" -gt 0 ]; then
+            echo "$FAILURES total errors detected during chaos testing"
+            exit 1
+          fi
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3e11cd538b379c1f3d14ded403b0daed59e02877 and d9ebb69a1a6d79a09396d510176bcde40179af24.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `.github/workflows/chaos-test.yml` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Checkov (3.2.334)</summary>

<details>
<summary>.github/workflows/chaos-test.yml</summary>

[MEDIUM] 66-67: Basic Auth Credentials

(CKV_SECRET_4)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>.github/workflows/chaos-test.yml (1)</summary>

`1-15`: _🧹 Nitpick (assertive)_

**Consider changing `redis_failure_probability` input to a numeric type**  
Currently the workflow input is declared as a string, which requires extra handling/quoting when passing into your curl payload. You can simplify this by using `type: number` and a numeric default:

```diff
       redis_failure_probability:
         description: 'Redis failure probability (0-1)'
         required: true
-        default: '0.3'
-        type: string
+        default: 0.3
+        type: number
```




> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-19T08:20:56Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>src/chaos/chaos.controller.ts (4)</summary><blockquote>

`9-11`: **Add validation decorators to ToggleRedisDto**

This DTO lacks validation decorators, which means malformed or incorrectly typed data could be accepted, potentially causing issues.


Add class-validator decorators:

```diff
+import { IsBoolean, IsNumber, Min, Max } from 'class-validator';

 export class ToggleRedisDto {
+  @IsBoolean()
   enabled: boolean;
 }
```

---

`13-15`: **Add validation with range constraints to SetChaosProbabilityDto**

The probability value requires range validation (0-1) but lacks class-validator decorators to enforce this at the API boundary.


Add appropriate validation decorators:

```diff
 export class SetChaosProbabilityDto {
+  @IsNumber()
+  @Min(0)
+  @Max(1)
   probability: number;
 }
```

---

`36-39`: **Use NestJS HTTP exceptions instead of native `Error`**

Throwing a bare `Error` will cause Nest to translate it into a 500 response, not the 400 indicated in your ApiResponse.


Replace with a BadRequestException for correct HTTP semantics:

```diff
+import { BadRequestException, Body, Controller, Get, Post, UseGuards } from '@nestjs/common';

// ...

   async setProbability(@Body() dto: SetChaosProbabilityDto) {
     if (dto.probability < 0 || dto.probability > 1) {
-      throw new Error('Probability must be between 0 and 1');
+      throw new BadRequestException('Probability must be between 0 and 1');
     }
```

---

`49-52`: **Avoid direct access to private fields from outside the service**

Accessing `this.cacheService['isRedisEnabled']` and `['chaosProbability']` breaks encapsulation and creates brittle dependencies on implementation details.


Consider adding getter methods to the CacheService instead:

```diff
// In this controller:
   return {
     message: 'Chaos testing status',
-    redisEnabled: this.cacheService['isRedisEnabled'],
-    chaosProbability: this.cacheService['chaosProbability'],
+    redisEnabled: this.cacheService.getRedisStatus(),
+    chaosProbability: this.cacheService.getChaosProbability(),
   };
```

Then implement these methods in CacheService:

```typescript
// In cache.service.ts:
getRedisStatus(): boolean {
  return this.isRedisEnabled;
}

getChaosProbability(): number {
  return this.chaosProbability;
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d9ebb69a1a6d79a09396d510176bcde40179af24 and 4b5d743b81b3c4d6cb84b108ea5db76dd55572ee.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `src/auth/decorators/roles.decorator.spec.ts` (1 hunks)
* `src/auth/decorators/roles.decorator.ts` (1 hunks)
* `src/auth/guards/roles.guard.spec.ts` (1 hunks)
* `src/auth/guards/roles.guard.ts` (1 hunks)
* `src/chaos/chaos.controller.ts` (1 hunks)
* `src/common/enums/role.enum.ts` (0 hunks)
* `src/users/entities/user.entity.ts` (1 hunks)
* `src/users/users.controller.ts` (1 hunks)
* `src/users/users.service.spec.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* src/common/enums/role.enum.ts

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>src/auth/guards/roles.guard.ts (1)</summary>

`3-3`: **Approve the import source change for Role enum.**

The import of Role enum has been updated to use `@prisma/client` instead of a local enum definition. This change is part of a broader refactoring to centralize role definitions across the application.

</details>
<details>
<summary>src/auth/guards/roles.guard.spec.ts (1)</summary>

`4-4`: **Approve the Role enum import update in the test file.**

The Role enum import has been updated to use `@prisma/client` instead of a local path, maintaining consistency with the corresponding implementation file.

</details>
<details>
<summary>src/users/users.controller.ts (1)</summary>

`18-18`: **Approve the Role enum import source change.**

The Role enum import has been changed to use `@prisma/client` consistently with other files in the codebase. This ensures a single source of truth for role definitions.

</details>
<details>
<summary>src/users/users.service.spec.ts (2)</summary>

`3-3`: **Approve the Role enum import source change.**

The Role enum import has been consistently updated to use `@prisma/client` instead of a local enum definition, unifying the role definition source across the application.

---

`640-641`: **Role reference correctly uses the updated import.**

The direct reference to `Role.USER` correctly uses the updated import from `@prisma/client`.

</details>
<details>
<summary>src/users/entities/user.entity.ts (1)</summary>

`2-2`: <details>
<summary>✅ Verification successful</summary>

**Replace local Role enum with Prisma client’s enum**  
The import has been correctly updated to use the `Role` enum from `@prisma/client`, ensuring a single source of truth and eliminating duplication.  
  
  
To confirm there are no lingering imports of the old local enum, you can run:

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify no remaining imports of the old Role enum
rg -n "common/enums/role.enum"
```

Length of output: 30

---



</details>

**Replace local Role enum with Prisma client’s enum**  
A repository-wide search for `common/enums/role.enum` returned no matches, confirming there are no lingering imports of the old local enum. This change is ready to merge.

</details>
<details>
<summary>src/auth/decorators/roles.decorator.ts (1)</summary>

`2-2`: **Use Prisma client’s Role enum in the Roles decorator**  
Updating the import to `@prisma/client` aligns the decorator with the centralized enum and avoids stale references.

</details>
<details>
<summary>src/auth/decorators/roles.decorator.spec.ts (1)</summary>

`2-2`: **Update test to use Prisma client’s Role enum**  
The spec now correctly imports `Role` from `@prisma/client`. Test logic and assertions remain valid with the enum values.

</details>
<details>
<summary>src/chaos/chaos.controller.ts (1)</summary>

`1-8`: <details>
<summary>❓ Verification inconclusive</summary>

**Ensure test coverage for chaos testing endpoints**

New features like chaos testing should have thorough test coverage, especially since they can significantly impact application behavior.


Let's verify test coverage exists for this controller:

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for test files for the chaos controller
fd "chaos.*spec.ts" src/
```

Length of output: 61

---



</details>

**Chaos controller tests detected**  
A test file exists at `src/chaos/chaos.controller.spec.ts`. Please verify that it provides comprehensive coverage for all chaos endpoints, including success, failure, and edge cases, to ensure the new chaos-testing features behave reliably under all scenarios.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-19T08:31:23Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (6)</summary><blockquote>

<details>
<summary>src/cache/cache.service.spec.ts (5)</summary><blockquote>

`51-53`: _⚠️ Potential issue_

**Add Math.random mock restoration in afterEach**

Multiple tests are stubbing Math.random without restoring it, which can affect other tests that rely on randomness.



```diff
afterEach(() => {
  jest.clearAllMocks();
+  if ((Math.random as jest.Mock)?.mockRestore) {
+    (Math.random as jest.Mock).mockRestore();
+  }
});
```

---

`203-212`: **Ensure Math.random mock is properly restored**

This test stubs Math.random but doesn't restore it, which can affect other tests.



If you're not adding the restoration in the global afterEach block, add it inside this test:

```diff
it('should simulate failure based on probability', async () => {
-  jest.spyOn(Math, 'random').mockReturnValue(0.1);
+  const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1);
  await service.setChaosProbability(0.2);

  const result = await service.get('test-key');
  expect(result).toBeUndefined();
  expect(cacheManager.get).not.toHaveBeenCalled();
+  randomSpy.mockRestore();
});
```

---

`45-48`: _⚠️ Potential issue_

**Fix the order of mocks and clearAllMocks**

The current implementation clears all mocks after creating spies, which effectively discards them. This makes it impossible to capture warnings/errors during tests.



```diff
-    // Mock the logger to avoid console output during tests
-    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
-    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
-    jest.clearAllMocks();
+    // Reset mocks from previous test runs
+    jest.clearAllMocks();
+
+    // Mock the logger to avoid console output during tests
+    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
```

---

`223-226`: _🛠️ Refactor suggestion_

**Verify ConfigService.get call in onModuleInit test**

The test confirms chaosProbability is set correctly but doesn't verify ConfigService.get was called with the expected key.



```diff
it('should initialize chaos probability from config on module init', async () => {
  await service.onModuleInit();
  expect(service['chaosProbability']).toBe(0.2);
+  expect(_configService.get).toHaveBeenCalledWith('CHAOS_REDIS_PROBABILITY', expect.any(Number));
});
```

---

`252-263`: _🧹 Nitpick (assertive)_

**Refactor similar chaos-triggered tests using parameterized testing**

The tests for chaos-triggered set and delete operations follow identical patterns with only the operation name and method changing. This creates unnecessary duplication.



```diff
-it('should log warning and skip set operation when chaos is triggered', async () => {
-  const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
-  jest.spyOn(Math, 'random').mockReturnValue(0.1);
-  await service.setChaosProbability(0.2);
-
-  await service.set('test-key', 'test-value');
-
-  expect(cacheManager.set).not.toHaveBeenCalled();
-  expect(loggerSpy).toHaveBeenCalledWith(
-    '[Chaos] Cache disabled for set operation on key test-key',
-  );
-});
-
-it('should log warning and skip delete operation when chaos is triggered', async () => {
-  const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
-  jest.spyOn(Math, 'random').mockReturnValue(0.1);
-  await service.setChaosProbability(0.2);
-
-  await service.del('test-key');
-
-  expect(cacheManager.del).not.toHaveBeenCalled();
-  expect(loggerSpy).toHaveBeenCalledWith(
-    '[Chaos] Cache disabled for delete operation on key test-key',
-  );
-});
+describe('chaos-triggered operations', () => {
+  let randomSpy;
+  
+  beforeEach(() => {
+    randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1);
+    service.setChaosProbability(0.2);
+  });
+  
+  afterEach(() => {
+    randomSpy.mockRestore();
+  });
+  
+  it.each([
+    ['set', 'set', 'test-value'],
+    ['delete', 'del', undefined]
+  ])('should log warning and skip %s operation when chaos is triggered', async (operationName, methodName, value) => {
+    const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+    
+    if (value !== undefined) {
+      await service[methodName]('test-key', value);
+    } else {
+      await service[methodName]('test-key');
+    }
+    
+    expect(cacheManager[methodName]).not.toHaveBeenCalled();
+    expect(loggerSpy).toHaveBeenCalledWith(
+      `[Chaos] Cache disabled for ${operationName} operation on key test-key`,
+    );
+  });
+});
```


Also applies to: 265-276

</blockquote></details>
<details>
<summary>src/chaos/chaos.controller.spec.ts (1)</summary><blockquote>

`55-68`: _🛠️ Refactor suggestion_

**Expand negative-path assertions for better test coverage**

The current test only checks for probability values greater than 1. It should also verify validation for negative numbers and non-numeric inputs.



```diff
describe('setProbability', () => {
  it('should set chaos probability', async () => {
    const dto = { probability: 0.5 };
    const result = await controller.setProbability(dto);

    expect(result).toEqual({ message: 'Set Redis chaos probability to 0.5' });
    expect(cacheService.setChaosProbability).toHaveBeenCalledWith(0.5);
  });

  it('should reject invalid probability values', async () => {
    const dto = { probability: 1.5 };
    await expect(controller.setProbability(dto)).rejects.toThrow();
+   await expect(controller.setProbability({ probability: -0.1 })).rejects.toThrow();
+   await expect(controller.setProbability({ probability: 'abc' as any })).rejects.toThrow();
  });
+  
+  it('should accept edge case probability values', async () => {
+    await controller.setProbability({ probability: 0 });
+    expect(cacheService.setChaosProbability).toHaveBeenCalledWith(0);
+    
+    await controller.setProbability({ probability: 1 });
+    expect(cacheService.setChaosProbability).toHaveBeenCalledWith(1);
+  });
});
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4b5d743b81b3c4d6cb84b108ea5db76dd55572ee and 8c227a20e7dbce34397fa09b47e1166a29db9eea.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `codecov.yml` (2 hunks)
* `src/cache/cache.service.spec.ts` (3 hunks)
* `src/chaos/chaos.controller.spec.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>codecov.yml (2)</summary>

`10-10`: **Stricter project coverage target: 80%**  
The project-wide coverage target has been increased from 70% to 80%, enforcing a higher quality bar. Please verify that the current overall coverage meets or exceeds this threshold to avoid CI failures.

---

`19-19`: **Stricter patch coverage target: 80%**  
The patch-level coverage threshold has also been raised to 80%, up from 70%. Ensure that incoming changes maintain this level of test coverage, and consider updating any contribution guidelines or PR templates to reflect the new requirement.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-19T08:51:53Z

Review comments:
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/40)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/41)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/42)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/43)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/44)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.del('test-key');
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/45)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.del('test-key');
+      
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/46)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.del('test-key');
+      
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      await service.set('test-key', 'test-value');
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/47)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.del('test-key');
+      
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/48)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.del('test-key');
+      
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      await service.del('test-key');
+      
path: src/cache/cache.service.spec.ts
user: login: github-advanced-security[bot]
body: ## Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Delete `······`

[Show more details](https://github.com/dhiazfathra/nestjs-starter/security/code-scanning/49)
created_at: 2025-05-19T02:48:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+      
+      await service.del('test-key');
+      
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      await service.set('test-key', 'test-value');
+      
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      await service.del('test-key');
+      
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+  });
path: src/cache/cache.service.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Remove non-visible “middot” characters flagged by ESLint**

ESLint reports formatting issues on several lines (`······`).  
These are typically stray `0x00B7` middle-dot characters that sneak in when copying code.  
They are harmless at runtime but cause noisy lint failures ‑ search & delete them ( VS Code → *Toggle Render Whitespace* helps ).

No code change needed other than deleting the invisible characters.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: ESLint</summary>

[failure] 203-203: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 212-212: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 227-227: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 229-229: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 239-239: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 241-241: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 252-252: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 254-254: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 265-265: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

---

[failure] 267-267: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.spec.ts between lines 192 and 273, ESLint flags
formatting issues due to invisible middle-dot characters (0x00B7) scattered in
the indentation. To fix this, open the file in an editor like VS Code, enable
rendering of whitespace characters, locate these stray middle-dot characters,
and delete them to clean up the formatting without changing any code logic.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 43ce5ec to 3e11cd5
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
path: src/cache/cache.service.spec.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Restore the `Math.random` mock to keep tests isolated**

Multiple tests stub `Math.random`.  Because the stub isn’t restored afterwards, any subsequent test (inside or **outside** this file) that relies on genuine randomness will receive the mocked value and may yield false positives/negatives.

Add a restore in `afterEach` (or inside the individual `it` blocks with `finally`):

```diff
afterEach(() => {
   jest.clearAllMocks();
+  if ((Math.random as jest.Mock)?.mockRestore) {
+    (Math.random as jest.Mock).mockRestore();
+  }
});
```



Also applies to: 250-258, 262-270

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: ESLint</summary>

[failure] 203-203: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.
Delete `······`

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.spec.ts around lines 200 to 207, the Math.random mock
is not restored after the test, causing side effects on other tests relying on
genuine randomness. Fix this by adding a restore call for the Math.random mock
in an afterEach block or by using a try-finally inside each affected it block to
ensure Math.random is reset after the test runs. Apply the same fix to the tests
in lines 250-258 and 262-270.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -22,14 +24,28 @@
           provide: CACHE_MANAGER,
           useValue: mockCacheManager,
         },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockImplementation((key, defaultValue) => {
+              if (key === 'CHAOS_REDIS_PROBABILITY') {
+                return 0.2; // Return test value for chaos probability
+              }
+              return defaultValue;
+            }),
+          },
+        },
       ],
     }).compile();
 
     service = module.get<CacheService>(CacheService);
     cacheManager = module.get(CACHE_MANAGER);
+    _configService = module.get(ConfigService);
 
     // Mock the logger to avoid console output during tests
     jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
+    jest.clearAllMocks();
   });
path: src/cache/cache.service.spec.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Move `jest.clearAllMocks()` *before* creating spies to avoid wiping them out**

`jest.clearAllMocks()` resets *all* existing mocks/spies.  
Calling it *after* `jest.spyOn(Logger.prototype, 'error'/'warn')` therefore discards those spies, so any warning/error produced during the test suite will not be captured.  
Place the clear-call first (or move the spies into their own helper executed **after** the clear), e.g.:

```diff
-    // Mock the logger to avoid console output during tests
-    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
-    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
-    jest.clearAllMocks();
+    // Reset mocks from previous test runs
+    jest.clearAllMocks();
+
+    // Mock the logger to avoid console output during tests
+    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    // Reset mocks from previous test runs
    jest.clearAllMocks();

    // Mock the logger to avoid console output during tests
    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
  });
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.spec.ts around lines 45 to 49, the call to
jest.clearAllMocks() is placed after creating spies on Logger.prototype.error
and Logger.prototype.warn, which removes those spies and prevents capturing
warnings or errors during tests. To fix this, move jest.clearAllMocks() to
before the jest.spyOn calls so that mocks are cleared first, then the spies are
created and remain active throughout the tests.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,76 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { ChaosController } from './chaos.controller';
+import { CacheService } from '../cache/cache.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+
+describe('ChaosController', () => {
+  let controller: ChaosController;
+  let cacheService: {
+    toggleRedis: jest.Mock;
+    setChaosProbability: jest.Mock;
+    isRedisEnabled: boolean;
+    chaosProbability: number;
+  };
+
+  beforeEach(async () => {
+    cacheService = {
+      toggleRedis: jest.fn(),
+      setChaosProbability: jest.fn(),
+      isRedisEnabled: true,
+      chaosProbability: 0,
+    };
+
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [ChaosController],
+      providers: [{ provide: CacheService, useValue: cacheService }],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue({ canActivate: () => true })
+      .overrideGuard(RolesGuard)
+      .useValue({ canActivate: () => true })
+      .compile();
+
+    controller = module.get<ChaosController>(ChaosController);
+  });
+
+  describe('toggleRedis', () => {
+    it('should toggle Redis status', async () => {
+      const dto = { enabled: false };
+      const result = await controller.toggleRedis(dto);
+
+      expect(result).toEqual({ message: 'Redis cache disabled' });
+      expect(cacheService.toggleRedis).toHaveBeenCalledWith(false);
+    });
+  });
+
+  describe('setProbability', () => {
+    it('should set chaos probability', async () => {
+      const dto = { probability: 0.5 };
+      const result = await controller.setProbability(dto);
+
+      expect(result).toEqual({ message: 'Set Redis chaos probability to 0.5' });
+      expect(cacheService.setChaosProbability).toHaveBeenCalledWith(0.5);
+    });
+
+    it('should reject invalid probability values', async () => {
+      const dto = { probability: 1.5 };
+      await expect(controller.setProbability(dto)).rejects.toThrow();
+    });
path: src/chaos/chaos.controller.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Expand negative-path assertions for better coverage**

The “invalid probability” test currently checks only `> 1`.  
Adding a case for negative numbers (e.g. `-0.1`) and non-numeric input will guarantee controller-level validation remains strict if DTO rules change.

```ts
await expect(controller.setProbability({ probability: -0.1 })).rejects.toThrow();
await expect(controller.setProbability({ probability: 'abc' as any })).rejects.toThrow();
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.controller.spec.ts around lines 56 to 59, the test for
invalid probability values only checks for values greater than 1. Expand this
negative-path test by adding assertions for negative numbers like -0.1 and
non-numeric inputs such as a string 'abc'. This will ensure the controller's
validation remains strict even if DTO validation rules change. Add these
additional expect statements to reject those invalid inputs.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,112 @@
+name: Chaos Testing
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+    branches: [main, master]
+  workflow_dispatch:
+    inputs:
+      redis_failure_probability:
+        description: 'Redis failure probability (0-1)'
+        required: true
+        default: '0.3'
+        type: string
+
+jobs:
+  chaos-test:
+    runs-on: ubuntu-latest
+    
+    services:
+      redis:
+        image: redis
+        ports:
+          - 6379:6379
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+      
+      postgres:
+        image: postgres:13
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_USER: postgres
+          POSTGRES_DB: test
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: '18.x'
+          cache: 'npm'
+          
+      - name: Install dependencies
+        run: npm ci
+        
+      - name: Build application
+        run: npm run build
+        
+      - name: Start application in background
+        run: |
+          npm run start:prod &
+          sleep 10
+        env:
+          NODE_ENV: test
+          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+          REDIS_HOST: localhost
+          REDIS_PORT: 6379
+          JWT_SECRET: test-secret
+          
+      - name: Run chaos tests
+        run: |
+          # Create test user with admin role
+          curl -X POST http://localhost:3000/auth/register \
+            -H "Content-Type: application/json" \
+            -d '{"email":"admin@example.com","password":"Password123!","name":"Admin User"}'
+          
+          # Login to get JWT token
+          TOKEN=$(curl -X POST http://localhost:3000/auth/login \
+            -H "Content-Type: application/json" \
+            -d '{"email":"admin@example.com","password":"Password123!"}' \
+            | jq -r '.access_token')
+          
+          # Set Redis failure probability
+          PROBABILITY="${{ github.event.inputs.redis_failure_probability || '0.3' }}"
+          curl -X POST http://localhost:3000/chaos/set-probability \
+            -H "Content-Type: application/json" \
+            -H "Authorization: Bearer $TOKEN" \
+            -d "{\"probability\":$PROBABILITY}"
+          
+          # Run a series of API calls to test resilience
+          for i in {1..50}; do
+            # Test various endpoints under chaos conditions
+            curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health || true
+            
+            # Add more API calls to test here
+            
+            echo "Completed test iteration $i"
+            sleep 1
+          done
path: .github/workflows/chaos-test.yml
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Long tight loop may hide intermittent failures – randomise & parallelise**

A fixed 50-iteration loop with 1 s sleeps exercises only the health endpoint serially.  
Consider:

* Randomising endpoints per iteration (user CRUD, etc.).
* Running a few iterations in parallel (`xargs -P` or background subshells) to better stress race conditions.
* Making the iteration count configurable via workflow input.

This improves the chaos test’s ability to surface real-world concurrency issues.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 95-95: trailing spaces

(trailing-spaces)

---

[error] 97-97: trailing spaces

(trailing-spaces)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In .github/workflows/chaos-test.yml around lines 91 to 100, the current loop
runs 50 serial iterations with fixed 1-second sleeps only testing the health
endpoint, which limits test coverage and concurrency. Modify the script to
randomize the API endpoints tested each iteration (including user CRUD and
others), run multiple iterations in parallel using background subshells or xargs
with parallelism, and make the total iteration count configurable via a workflow
input parameter to better simulate real-world concurrency and expose
intermittent failures.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,112 @@
+name: Chaos Testing
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+    branches: [main, master]
+  workflow_dispatch:
+    inputs:
+      redis_failure_probability:
+        description: 'Redis failure probability (0-1)'
+        required: true
+        default: '0.3'
+        type: string
+
+jobs:
+  chaos-test:
+    runs-on: ubuntu-latest
+    
+    services:
+      redis:
+        image: redis
+        ports:
+          - 6379:6379
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+      
+      postgres:
+        image: postgres:13
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_USER: postgres
+          POSTGRES_DB: test
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: '18.x'
+          cache: 'npm'
+          
+      - name: Install dependencies
+        run: npm ci
+        
+      - name: Build application
+        run: npm run build
+        
+      - name: Start application in background
+        run: |
+          npm run start:prod &
+          sleep 10
+        env:
+          NODE_ENV: test
+          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+          REDIS_HOST: localhost
+          REDIS_PORT: 6379
+          JWT_SECRET: test-secret
+          
path: .github/workflows/chaos-test.yml
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Avoid committing plaintext credentials & secrets**

Hard-coding `DATABASE_URL` with user/password and `JWT_SECRET` inside the workflow exposes them in build logs and forked PRs.

1.  Move secrets to repository/environment secrets:
    - `POSTGRES_PASSWORD`, `DATABASE_URL`, `JWT_SECRET`
2.  Reference them via `${{ secrets.<NAME> }}`.

```diff
-          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+          DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
...
-          JWT_SECRET: test-secret
+          JWT_SECRET: ${{ secrets.CI_JWT_SECRET }}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
          DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
          REDIS_HOST: localhost
          REDIS_PORT: 6379
          JWT_SECRET: ${{ secrets.CI_JWT_SECRET }}
          
```

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Checkov (3.2.334)</summary>

[MEDIUM] 66-67: Basic Auth Credentials

(CKV_SECRET_4)

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 70-70: trailing spaces

(trailing-spaces)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In .github/workflows/chaos-test.yml around lines 66 to 70, the DATABASE_URL and
JWT_SECRET are hard-coded with plaintext credentials, which exposes sensitive
information in build logs and forked PRs. To fix this, remove the hard-coded
values and instead reference these secrets using GitHub Actions secrets syntax
like `${{ secrets.DATABASE_URL }}`, `${{ secrets.POSTGRES_PASSWORD }}`, and `${{
secrets.JWT_SECRET }}`. Ensure these secrets are properly added to the
repository or environment secrets before referencing them in the workflow.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,112 @@
+name: Chaos Testing
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+    branches: [main, master]
+  workflow_dispatch:
+    inputs:
+      redis_failure_probability:
+        description: 'Redis failure probability (0-1)'
+        required: true
+        default: '0.3'
+        type: string
+
+jobs:
+  chaos-test:
+    runs-on: ubuntu-latest
+    
+    services:
+      redis:
+        image: redis
+        ports:
+          - 6379:6379
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+      
+      postgres:
+        image: postgres:13
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_USER: postgres
+          POSTGRES_DB: test
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: '18.x'
+          cache: 'npm'
+          
path: .github/workflows/chaos-test.yml
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Upgrade GitHub Actions to latest major versions**

Both `actions/checkout@v3` and `actions/setup-node@v3` have newer v4 releases bringing performance & security improvements.  
No breaking changes for default use-cases.

```diff
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
...
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18.x'
          cache: 'npm'
```

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 actionlint (1.7.7)</summary>

49-49: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 53-53: trailing spaces

(trailing-spaces)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In .github/workflows/chaos-test.yml around lines 48 to 53, update the GitHub
Actions versions for better performance and security by changing
actions/setup-node@v3 to actions/setup-node@v4 and similarly update
actions/checkout@v3 to actions/checkout@v4 elsewhere in the workflow file.
Ensure no other configuration changes are needed as there are no breaking
changes for default use cases.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit d9ebb69
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { CacheService } from '../cache/cache.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { Roles } from '../auth/decorators/roles.decorator';
+import { Role } from '../common/enums/role.enum';
+
+export class ToggleRedisDto {
+  enabled: boolean;
+}
+
+export class SetChaosProbabilityDto {
+  probability: number;
+}
+
+@ApiTags('Chaos Testing')
+@Controller('chaos')
+@UseGuards(JwtAuthGuard, RolesGuard)
+@Roles(Role.ADMIN)
+export class ChaosController {
+  constructor(private readonly cacheService: CacheService) {}
+
+  @Post('toggle-redis')
+  @ApiOperation({ summary: 'Toggle Redis cache on/off' })
+  @ApiResponse({ status: 200, description: 'Redis cache toggled successfully' })
+  async toggleRedis(@Body() dto: ToggleRedisDto) {
+    await this.cacheService.toggleRedis(dto.enabled);
+    return { message: `Redis cache ${dto.enabled ? 'enabled' : 'disabled'}` };
+  }
+
+  @Post('set-probability')
+  @ApiOperation({ summary: 'Set Redis failure probability' })
+  @ApiResponse({ status: 200, description: 'Probability set successfully' })
+  @ApiResponse({ status: 400, description: 'Invalid probability value' })
+  async setProbability(@Body() dto: SetChaosProbabilityDto) {
+    if (dto.probability < 0 || dto.probability > 1) {
+      throw new Error('Probability must be between 0 and 1');
+    }
+    await this.cacheService.setChaosProbability(dto.probability);
+    return { message: `Set Redis chaos probability to ${dto.probability}` };
path: src/chaos/chaos.controller.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Use NestJS HTTP exceptions instead of native `Error`**

Throwing a bare `Error` causes Nest to translate it into a 500 response, not the advertised 400.  
Switch to `BadRequestException` (or a custom one) for correct semantics.

```diff
-    if (dto.probability < 0 || dto.probability > 1) {
-      throw new Error('Probability must be between 0 and 1');
-    }
+    if (dto.probability < 0 || dto.probability > 1) {
+      throw new BadRequestException('Probability must be between 0 and 1');
+    }
```

Don’t forget the import:

```typescript
import { BadRequestException } from '@nestjs/common';
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.controller.ts around lines 36 to 41, replace the native Error
thrown for invalid probability values with NestJS's BadRequestException to
ensure the response status is 400 instead of 500. Import BadRequestException
from '@nestjs/common' and throw new BadRequestException with the same error
message when dto.probability is out of range.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { CacheService } from '../cache/cache.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { Roles } from '../auth/decorators/roles.decorator';
+import { Role } from '../common/enums/role.enum';
+
+export class ToggleRedisDto {
+  enabled: boolean;
+}
+
+export class SetChaosProbabilityDto {
+  probability: number;
+}
+
+@ApiTags('Chaos Testing')
+@Controller('chaos')
+@UseGuards(JwtAuthGuard, RolesGuard)
+@Roles(Role.ADMIN)
+export class ChaosController {
+  constructor(private readonly cacheService: CacheService) {}
+
+  @Post('toggle-redis')
+  @ApiOperation({ summary: 'Toggle Redis cache on/off' })
+  @ApiResponse({ status: 200, description: 'Redis cache toggled successfully' })
+  async toggleRedis(@Body() dto: ToggleRedisDto) {
+    await this.cacheService.toggleRedis(dto.enabled);
+    return { message: `Redis cache ${dto.enabled ? 'enabled' : 'disabled'}` };
+  }
+
+  @Post('set-probability')
+  @ApiOperation({ summary: 'Set Redis failure probability' })
+  @ApiResponse({ status: 200, description: 'Probability set successfully' })
+  @ApiResponse({ status: 400, description: 'Invalid probability value' })
+  async setProbability(@Body() dto: SetChaosProbabilityDto) {
+    if (dto.probability < 0 || dto.probability > 1) {
+      throw new Error('Probability must be between 0 and 1');
+    }
+    await this.cacheService.setChaosProbability(dto.probability);
+    return { message: `Set Redis chaos probability to ${dto.probability}` };
+  }
+
+  @Get('status')
+  @ApiOperation({ summary: 'Get current chaos testing status' })
+  @ApiResponse({ status: 200, description: 'Status retrieved successfully' })
+  async getStatus() {
+    return {
+      message: 'Chaos testing status',
+      redisEnabled: this.cacheService['isRedisEnabled'],
+      chaosProbability: this.cacheService['chaosProbability'],
+    };
path: src/chaos/chaos.controller.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Avoid reaching into private fields from outside the service**

`this.cacheService['isRedisEnabled']` and `['chaosProbability']` break encapsulation and can be obfuscated by minifiers.  
Consider exposing read-only getters (e.g., `getRedisStatus()` and `getChaosProbability()`) on `CacheService` instead.

This keeps the service API explicit and future-proof.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.controller.ts around lines 50 to 52, avoid accessing private
fields of cacheService directly using bracket notation as it breaks
encapsulation and can be obfuscated by minifiers. Instead, add public getter
methods like getRedisStatus() and getChaosProbability() to CacheService, then
replace the direct field accesses with calls to these getters to maintain a
clear and stable service API.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { CacheService } from '../cache/cache.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { Roles } from '../auth/decorators/roles.decorator';
+import { Role } from '../common/enums/role.enum';
+
+export class ToggleRedisDto {
+  enabled: boolean;
+}
+
+export class SetChaosProbabilityDto {
+  probability: number;
+}
path: src/chaos/chaos.controller.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add validation decorators to prevent malformed payloads**

`ToggleRedisDto` and `SetChaosProbabilityDto` currently lack `class-validator` constraints, so invalid or stringly-typed bodies will slip through when `ValidationPipe` is enabled.  
Adding explicit decorators also improves Swagger docs.

```diff
-import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
+import { IsBoolean, IsNumber, Min, Max } from 'class-validator';

 export class ToggleRedisDto {
-  enabled: boolean;
+  @IsBoolean()
+  enabled: boolean;
 }

 export class SetChaosProbabilityDto {
-  probability: number;
+  @IsNumber()
+  @Min(0)
+  @Max(1)
+  probability: number;
 }
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.controller.ts around lines 9 to 15, the DTO classes
ToggleRedisDto and SetChaosProbabilityDto lack validation decorators, allowing
invalid or incorrectly typed payloads to pass through. Add appropriate
class-validator decorators such as @IsBoolean() for the enabled property in
ToggleRedisDto and @IsNumber() with constraints like @Min(0) and @Max(1) for the
probability property in SetChaosProbabilityDto. This will enforce proper
validation and improve Swagger documentation.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:06Z
author_association: NONE
diff_hunk: @@ -22,45 +41,48 @@ export class CacheService {
     }
   }
 
-  /**
-   * Set a value in cache
-   * @param key - Cache key
-   * @param value - Value to cache
-   * @param ttl - Time to live in seconds (optional)
-   */
   async set<T>(key: string, value: T, ttl?: number): Promise<void> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled for set operation on key ${key}`,
+      );
+      return;
+    }
+
     try {
-      // Pass ttl directly as the third parameter
       await this.cacheManager.set(key, value, ttl);
     } catch (error) {
       this.logger.error(`Failed to set cache key ${key}:`, error);
     }
   }
 
-  /**
-   * Delete a value from cache
-   * @param key - Cache key
-   */
   async del(key: string): Promise<void> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled for delete operation on key ${key}`,
+      );
+      return;
+    }
+
     try {
       await this.cacheManager.del(key);
     } catch (error) {
       this.logger.error(`Failed to delete cache key ${key}:`, error);
     }
   }
 
-  /**
-   * Get a value from cache or set it if not found
-   * @param key - Cache key
-   * @param factory - Function to generate value if not in cache
-   * @param ttl - Time to live in seconds (optional)
-   * @returns The cached or newly generated value
-   */
   async getOrSet<T>(
     key: string,
     factory: () => Promise<T>,
     ttl?: number,
   ): Promise<T> {
+    if (!this.isRedisEnabled || this.shouldSimulateFailure()) {
+      this.logger.warn(
+        `[Chaos] Cache disabled, directly executing factory for key ${key}`,
+      );
+      return factory();
+    }
+
path: src/cache/cache.service.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**`shouldSimulateFailure()` is evaluated up to 3× per `getOrSet` call**

`getOrSet` calls `shouldSimulateFailure()` once, then calls `get`, which evaluates it again, and (on miss) `set`, which evaluates it a third time.  
The effective failure rate is therefore higher than the configured probability.

Consider evaluating once and passing a flag through, or wrapping the whole operation in a single check to honour the intended probability.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.ts around lines 79 to 85, the method
shouldSimulateFailure() is called multiple times per getOrSet call, causing a
higher effective failure rate than intended. To fix this, evaluate
shouldSimulateFailure() once at the start of getOrSet, store the result in a
local variable, and pass this flag through to subsequent get and set calls or
wrap the entire getOrSet logic in a single conditional check using this flag to
ensure the failure simulation probability is honored correctly.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits b9d093d to 8e856ba
created_at: 2025-05-19T02:57:06Z
author_association: NONE
diff_hunk: @@ -79,4 +101,14 @@
       throw error;
     }
   }
+
+  async toggleRedis(enabled: boolean): Promise<void> {
+    this.isRedisEnabled = enabled;
+    this.logger.log(`Redis cache ${enabled ? 'enabled' : 'disabled'}`);
+  }
+
+  async setChaosProbability(probability: number): Promise<void> {
+    this.chaosProbability = probability;
+    this.logger.log(`Set Redis chaos probability to ${probability}`);
+  }
path: src/cache/cache.service.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**`toggleRedis` / `setChaosProbability` don’t need to be `async`**

Both methods are synchronous property assignments; marking them `async` adds an unnecessary promise layer and may mislead callers.

```diff
-  async toggleRedis(enabled: boolean): Promise<void> {
+  toggleRedis(enabled: boolean): void {
     this.isRedisEnabled = enabled;
     this.logger.log(`Redis cache ${enabled ? 'enabled' : 'disabled'}`);
   }

-  async setChaosProbability(probability: number): Promise<void> {
+  setChaosProbability(probability: number): void {
     this.chaosProbability = probability;
     this.logger.log(`Set Redis chaos probability to ${probability}`);
   }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  toggleRedis(enabled: boolean): void {
    this.isRedisEnabled = enabled;
    this.logger.log(`Redis cache ${enabled ? 'enabled' : 'disabled'}`);
  }

  setChaosProbability(probability: number): void {
    this.chaosProbability = probability;
    this.logger.log(`Set Redis chaos probability to ${probability}`);
  }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.ts lines 105 to 113, the methods toggleRedis and
setChaosProbability are marked async but only perform synchronous property
assignments and logging. Remove the async keyword from both methods to avoid
unnecessary promise wrapping and clarify their synchronous nature.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:06Z
author_association: NONE
diff_hunk: @@ -1,19 +1,38 @@
 import { CACHE_MANAGER } from '@nestjs/cache-manager';
-import { Inject, Injectable, Logger } from '@nestjs/common';
+import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
 import { Cache } from 'cache-manager';
 
 @Injectable()
-export class CacheService {
+export class CacheService implements OnModuleInit {
   private readonly logger = new Logger(CacheService.name);
+  private isRedisEnabled = true;
+  private chaosProbability = 0;
 
-  constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {}
+  constructor(
+    @Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
+    private readonly configService: ConfigService,
+  ) {}
+
+  async onModuleInit(): Promise<void> {
+    this.chaosProbability = this.configService.get<number>(
+      'CHAOS_REDIS_PROBABILITY',
+      0,
+    );
+  }
path: src/cache/cache.service.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Parse numeric env var to avoid NaN at runtime**

`ConfigService.get<number>` does not coerce strings; if `CHAOS_REDIS_PROBABILITY` is provided as `"0.25"` it will still be a string.  
Explicitly convert and fallback safely:

```diff
-    this.chaosProbability = this.configService.get<number>(
-      'CHAOS_REDIS_PROBABILITY',
-      0,
-    );
+    const raw = this.configService.get<string>('CHAOS_REDIS_PROBABILITY', '0');
+    this.chaosProbability = parseFloat(raw) || 0;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  async onModuleInit(): Promise<void> {
    const raw = this.configService.get<string>('CHAOS_REDIS_PROBABILITY', '0');
    this.chaosProbability = parseFloat(raw) || 0;
  }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.ts around lines 17 to 22, the environment variable
CHAOS_REDIS_PROBABILITY is retrieved as a string and assigned to
chaosProbability without conversion, which can cause NaN issues at runtime. To
fix this, explicitly parse the retrieved value to a number using a method like
parseFloat, and provide a fallback default value if parsing fails or results in
NaN, ensuring chaosProbability is always a valid number.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T02:57:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { ChaosController } from './chaos.controller';
+import { RedisCacheModule } from '../cache/cache.module';
+
+@Module({
+  imports: [RedisCacheModule],
+  controllers: [ChaosController],
+})
+export class ChaosModule {}
path: src/chaos/chaos.module.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**LGTM: Module structure follows NestJS conventions**

The ChaosModule correctly imports RedisCacheModule and registers ChaosController, following NestJS best practices for module organization.


Consider adding a module test file (chaos.module.spec.ts) to verify proper module initialization, which would improve test coverage.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.module.ts around lines 1 to 9, although the module structure
is correct, add a new test file named chaos.module.spec.ts to verify the proper
initialization of ChaosModule. Create tests that check if ChaosModule is defined
and if it correctly imports RedisCacheModule and registers ChaosController,
improving test coverage and ensuring module integrity.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T07:37:37Z
author_association: NONE
diff_hunk: @@ -172,4 +188,87 @@
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+      
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+      
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
path: src/cache/cache.service.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Verify ConfigService initialization in onModuleInit**

This test effectively verifies that the CacheService correctly initializes the chaos probability from ConfigService. However, it would be better to also verify that the ConfigService.get method was called with the correct key.

```diff
it('should initialize chaos probability from config on module init', async () => {
  await service.onModuleInit();
  expect(service['chaosProbability']).toBe(0.2);
+ expect(_configService.get).toHaveBeenCalledWith('CHAOS_REDIS_PROBABILITY', expect.any(Number));
});
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    it('should initialize chaos probability from config on module init', async () => {
      await service.onModuleInit();
      expect(service['chaosProbability']).toBe(0.2);
      expect(_configService.get).toHaveBeenCalledWith(
        'CHAOS_REDIS_PROBABILITY',
        expect.any(Number),
      );
    });
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.spec.ts around lines 219 to 222, the test checks that
chaosProbability is set correctly but does not verify that ConfigService.get was
called with the expected key. Modify the test to spy on ConfigService.get and
assert it was called with the correct configuration key during onModuleInit
execution.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T07:37:37Z
author_association: NONE
diff_hunk: @@ -0,0 +1,112 @@
+name: Chaos Testing
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+    branches: [main, master]
+  workflow_dispatch:
+    inputs:
+      redis_failure_probability:
+        description: 'Redis failure probability (0-1)'
+        required: true
+        default: '0.3'
+        type: string
+
+jobs:
+  chaos-test:
+    runs-on: ubuntu-latest
+    
+    services:
+      redis:
+        image: redis
+        ports:
+          - 6379:6379
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+      
+      postgres:
+        image: postgres:13
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_USER: postgres
+          POSTGRES_DB: test
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: '18.x'
+          cache: 'npm'
+          
+      - name: Install dependencies
+        run: npm ci
+        
+      - name: Build application
+        run: npm run build
+        
+      - name: Start application in background
+        run: |
+          npm run start:prod &
+          sleep 10
+        env:
+          NODE_ENV: test
+          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+          REDIS_HOST: localhost
+          REDIS_PORT: 6379
+          JWT_SECRET: test-secret
+          
+      - name: Run chaos tests
+        run: |
+          # Create test user with admin role
+          curl -X POST http://localhost:3000/auth/register \
+            -H "Content-Type: application/json" \
+            -d '{"email":"admin@example.com","password":"Password123!","name":"Admin User"}'
+          
+          # Login to get JWT token
+          TOKEN=$(curl -X POST http://localhost:3000/auth/login \
+            -H "Content-Type: application/json" \
+            -d '{"email":"admin@example.com","password":"Password123!"}' \
+            | jq -r '.access_token')
+          
+          # Set Redis failure probability
+          PROBABILITY="${{ github.event.inputs.redis_failure_probability || '0.3' }}"
+          curl -X POST http://localhost:3000/chaos/set-probability \
+            -H "Content-Type: application/json" \
+            -H "Authorization: Bearer $TOKEN" \
+            -d "{\"probability\":$PROBABILITY}"
+          
+          # Run a series of API calls to test resilience
+          for i in {1..50}; do
+            # Test various endpoints under chaos conditions
+            curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health || true
+            
+            # Add more API calls to test here
+            
+            echo "Completed test iteration $i"
+            sleep 1
+          done
+          
+          # Get chaos testing status
+          curl -X GET http://localhost:3000/chaos/status \
+            -H "Authorization: Bearer $TOKEN"
+          
+      - name: Check application logs
+        run: |
+          # Check for error patterns that would indicate resilience issues
+          if grep -q "FATAL ERROR" logs/error.log; then
+            echo "Fatal errors detected during chaos testing"
+            exit 1
+          fi
path: .github/workflows/chaos-test.yml
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance error detection in logs**

The current approach only checks for "FATAL ERROR" pattern in error logs. Consider:

1. Checking for different types of errors and exceptions
2. Adding a summary of found errors in the output

```diff
- # Check for error patterns that would indicate resilience issues
- if grep -q "FATAL ERROR" logs/error.log; then
-   echo "Fatal errors detected during chaos testing"
-   exit 1
- fi
+ # Check for various error patterns
+ ERROR_PATTERNS=("FATAL ERROR" "Unhandled exception" "Error:" "failed with code")
+ FAILURES=0
+ 
+ for PATTERN in "${ERROR_PATTERNS[@]}"; do
+   ERROR_COUNT=$(grep -c "$PATTERN" logs/error.log || true)
+   if [ "$ERROR_COUNT" -gt 0 ]; then
+     echo "Found $ERROR_COUNT occurrences of '$PATTERN'"
+     grep "$PATTERN" logs/error.log | head -n 5
+     FAILURES=$((FAILURES + ERROR_COUNT))
+   fi
+ done
+ 
+ if [ "$FAILURES" -gt 0 ]; then
+   echo "$FAILURES total errors detected during chaos testing"
+   exit 1
+ else
+   echo "No critical errors detected - chaos testing successful"
+ fi
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      - name: Check application logs
        run: |
          # Check for various error patterns
          ERROR_PATTERNS=("FATAL ERROR" "Unhandled exception" "Error:" "failed with code")
          FAILURES=0
          
          for PATTERN in "${ERROR_PATTERNS[@]}"; do
            ERROR_COUNT=$(grep -c "$PATTERN" logs/error.log || true)
            if [ "$ERROR_COUNT" -gt 0 ]; then
              echo "Found $ERROR_COUNT occurrences of '$PATTERN'"
              grep "$PATTERN" logs/error.log | head -n 5
              FAILURES=$((FAILURES + ERROR_COUNT))
            fi
          done
          
          if [ "$FAILURES" -gt 0 ]; then
            echo "$FAILURES total errors detected during chaos testing"
            exit 1
          else
            echo "No critical errors detected - chaos testing successful"
          fi
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In .github/workflows/chaos-test.yml around lines 106 to 112, the log check only
looks for the "FATAL ERROR" pattern, which limits error detection. Update the
script to search for multiple error and exception patterns relevant to
resilience issues. Additionally, modify the output to include a summary of all
found errors to provide clearer insight into the issues detected during chaos
testing.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T07:37:38Z
author_association: NONE
diff_hunk: @@ -208,6 +209,62 @@ const value = await cacheService.getOrSet(
 );
 ```
 
+## Chaos Testing
+
+This project includes chaos testing capabilities to ensure the application remains resilient when Redis experiences failures. The chaos testing system allows you to simulate Redis failures in a controlled manner.
+
+### Features
+
+- **Redis Fault Injection** - Simulate Redis failures with configurable probability
+- **Redis Toggle** - Enable/disable Redis cache entirely for testing
+- **API Control** - Manage chaos testing through a secure API
+- **GitHub Actions Integration** - Automated chaos testing in CI/CD pipeline
+
+### API Endpoints
+
+The chaos testing API is protected and requires admin authentication:
+
+```
+# Toggle Redis on/off
+POST /chaos/toggle-redis
+{ "enabled": true|false }
+
+# Set Redis failure probability (0-1)
+POST /chaos/set-probability
+{ "probability": 0.3 }
+
+# Get current chaos testing status
+GET /chaos/status
+```
path: README.md
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Add language specifier to code block for proper syntax highlighting**

The fenced code block for the API endpoints is missing a language specifier, which affects syntax highlighting and readability. This was also flagged by markdownlint.


```diff
-```
+```http
 # Toggle Redis on/off
 POST /chaos/toggle-redis
 { "enabled": true|false }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

227-227: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In README.md lines 227 to 238, the fenced code block showing API endpoints lacks
a language specifier, which prevents proper syntax highlighting. Add "http"
immediately after the opening triple backticks to specify the language for the
code block. This will improve readability and comply with markdownlint rules.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T07:57:49Z
author_association: NONE
diff_hunk: @@ -172,4 +188,87 @@ describe('CacheService', () => {
       expect(cacheManager.set).not.toHaveBeenCalled();
     });
   });
+
+  describe('Chaos Testing', () => {
+    it('should bypass cache when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should simulate failure based on probability', async () => {
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+
+      const result = await service.get('test-key');
+      expect(result).toBeUndefined();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should execute factory directly when Redis is disabled', async () => {
+      await service.toggleRedis(false);
+      const factory = jest.fn().mockResolvedValue('value');
+
+      const result = await service.getOrSet('test-key', factory);
+      expect(result).toBe('value');
+      expect(factory).toHaveBeenCalled();
+      expect(cacheManager.get).not.toHaveBeenCalled();
+    });
+
+    it('should initialize chaos probability from config on module init', async () => {
+      await service.onModuleInit();
+      expect(service['chaosProbability']).toBe(0.2);
+    });
+
+    it('should log warning and skip set operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+
+      await service.set('test-key', 'test-value');
+
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip delete operation when Redis is disabled', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      await service.toggleRedis(false);
+
+      await service.del('test-key');
+
+      expect(cacheManager.del).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for delete operation on key test-key',
+      );
+    });
+
+    it('should log warning and skip set operation when chaos is triggered', async () => {
+      const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
+      jest.spyOn(Math, 'random').mockReturnValue(0.1);
+      await service.setChaosProbability(0.2);
+
+      await service.set('test-key', 'test-value');
+
+      expect(cacheManager.set).not.toHaveBeenCalled();
+      expect(loggerSpy).toHaveBeenCalledWith(
+        '[Chaos] Cache disabled for set operation on key test-key',
+      );
+    });
path: src/cache/cache.service.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Similar test patterns for chaos-triggered operations**

The tests for chaos-triggered set and delete operations follow similar patterns. Consider reducing duplication with a parameterized test approach.


```typescript
describe('chaos-triggered operations', () => {
  beforeEach(() => {
    jest.spyOn(Math, 'random').mockReturnValue(0.1);
    service.setChaosProbability(0.2);
  });
  
  afterEach(() => {
    (Math.random as jest.Mock).mockRestore();
  });
  
  it.each([
    ['set', 'set', 'test-value'],
    ['delete', 'del', undefined]
  ])('should log warning and skip %s operation when chaos is triggered', async (operationName, methodName, value) => {
    const loggerSpy = jest.spyOn(Logger.prototype, 'warn');
    
    if (value !== undefined) {
      await service[methodName]('test-key', value);
    } else {
      await service[methodName]('test-key');
    }
    
    expect(cacheManager[methodName]).not.toHaveBeenCalled();
    expect(loggerSpy).toHaveBeenCalledWith(
      `[Chaos] Cache disabled for ${operationName} operation on key test-key`,
    );
  });
});
```


Also applies to: 261-272

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/cache/cache.service.spec.ts around lines 248 to 259 and 261 to 272, the
tests for chaos-triggered set and delete operations are duplicated. Refactor
these tests into a single parameterized test using jest's it.each to cover both
operations. Set up common mocks and chaos probability in a beforeEach block,
restore mocks in afterEach, and use the parameterized test to call the
appropriate method and verify the logger warning and that the cacheManager
method was not called, reducing code duplication and improving maintainability.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T07:57:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,112 @@
+name: Chaos Testing
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+    branches: [main, master]
+  workflow_dispatch:
+    inputs:
+      redis_failure_probability:
+        description: 'Redis failure probability (0-1)'
+        required: true
+        default: '0.3'
+        type: string
+
+jobs:
+  chaos-test:
+    runs-on: ubuntu-latest
+
+    services:
+      redis:
+        image: redis:7-alpine
+        ports:
+          - 6379:6379
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+      postgres:
+        image: postgres:17-alpine
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_USER: postgres
+          POSTGRES_DB: test
+        ports:
path: .github/workflows/chaos-test.yml
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Avoid committing plaintext credentials for the Postgres service**  
Hard-coding `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` exposes sensitive information in build logs and pull requests. Move these into repository or environment secrets and reference them via `${{ secrets.<NAME> }}`:

```diff
 services:
   postgres:
     image: postgres:17-alpine
     env:
-      POSTGRES_PASSWORD: postgres
-      POSTGRES_USER: postgres
-      POSTGRES_DB: test
+      POSTGRES_PASSWORD: ${{ secrets.CI_POSTGRES_PASSWORD }}
+      POSTGRES_USER:     ${{ secrets.CI_POSTGRES_USER }}
+      POSTGRES_DB:       ${{ secrets.CI_POSTGRES_DB }}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        env:
          POSTGRES_PASSWORD: ${{ secrets.CI_POSTGRES_PASSWORD }}
          POSTGRES_USER:     ${{ secrets.CI_POSTGRES_USER }}
          POSTGRES_DB:       ${{ secrets.CI_POSTGRES_DB }}
        ports:
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In .github/workflows/chaos-test.yml around lines 33 to 37, the Postgres service
environment variables for user, password, and database are hard-coded in
plaintext, which exposes sensitive credentials. Replace the hard-coded values
with references to repository or environment secrets using the syntax `${{
secrets.POSTGRES_USER }}`, `${{ secrets.POSTGRES_PASSWORD }}`, and `${{
secrets.POSTGRES_DB }}` to securely manage these credentials.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T08:20:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
+import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
+import { Role } from '@prisma/client';
+import { Roles } from '../auth/decorators/roles.decorator';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { CacheService } from '../cache/cache.service';
+
+export class ToggleRedisDto {
+  enabled: boolean;
+}
+
+export class SetChaosProbabilityDto {
+  probability: number;
+}
+
+@ApiTags('Chaos Testing')
+@Controller('chaos')
+@UseGuards(JwtAuthGuard, RolesGuard)
+@Roles(Role.ADMIN)
+export class ChaosController {
+  constructor(private readonly cacheService: CacheService) {}
path: src/chaos/chaos.controller.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider adding comprehensive JSDoc comments**

This controller handles potentially disruptive features for testing. Proper documentation would clarify the purpose and intended usage.


Add JSDoc comments to the controller class:

```diff
 @UseGuards(JwtAuthGuard, RolesGuard)
 @Roles(Role.ADMIN)
+/**
+ * Controller for managing chaos testing features related to Redis cache
+ * Allows toggling Redis on/off and setting failure probabilities
+ * NOTE: For testing environments only - can impact application stability
+ */
 export class ChaosController {
   constructor(private readonly cacheService: CacheService) {}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
@UseGuards(JwtAuthGuard, RolesGuard)
 @Roles(Role.ADMIN)
 /**
  * Controller for managing chaos testing features related to Redis cache
  * Allows toggling Redis on/off and setting failure probabilities
  * NOTE: For testing environments only - can impact application stability
  */
 export class ChaosController {
   constructor(private readonly cacheService: CacheService) {}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.controller.ts around lines 21 to 22, the ChaosController
class lacks JSDoc comments. Add comprehensive JSDoc comments above the class
declaration to describe the controller's purpose, its role in handling
potentially disruptive features for testing, and any important usage notes to
improve code clarity and maintainability.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T08:31:22Z
author_association: NONE
diff_hunk: @@ -0,0 +1,84 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+import { CacheService } from '../cache/cache.service';
+import { ChaosController } from './chaos.controller';
+
+describe('ChaosController', () => {
+  let controller: ChaosController;
+  let cacheService: {
+    toggleRedis: jest.Mock;
+    setChaosProbability: jest.Mock;
+    isRedisEnabled: boolean;
+    chaosProbability: number;
+  };
+
+  beforeEach(async () => {
+    cacheService = {
+      toggleRedis: jest.fn(),
+      setChaosProbability: jest.fn(),
+      isRedisEnabled: true,
+      chaosProbability: 0,
+    };
+
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [ChaosController],
+      providers: [{ provide: CacheService, useValue: cacheService }],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue({ canActivate: () => true })
+      .overrideGuard(RolesGuard)
+      .useValue({ canActivate: () => true })
+      .compile();
+
+    controller = module.get<ChaosController>(ChaosController);
+  });
+
+  describe('toggleRedis', () => {
+    it('should toggle Redis status to disabled', async () => {
+      const dto = { enabled: false };
+      const result = await controller.toggleRedis(dto);
+
+      expect(result).toEqual({ message: 'Redis cache disabled' });
+      expect(cacheService.toggleRedis).toHaveBeenCalledWith(false);
+    });
+
+    it('should toggle Redis status to enabled', async () => {
+      const dto = { enabled: true };
+      const result = await controller.toggleRedis(dto);
+
+      expect(result).toEqual({ message: 'Redis cache enabled' });
+      expect(cacheService.toggleRedis).toHaveBeenCalledWith(true);
+    });
+  });
+
+  describe('setProbability', () => {
+    it('should set chaos probability', async () => {
+      const dto = { probability: 0.5 };
+      const result = await controller.setProbability(dto);
+
+      expect(result).toEqual({ message: 'Set Redis chaos probability to 0.5' });
+      expect(cacheService.setChaosProbability).toHaveBeenCalledWith(0.5);
+    });
+
+    it('should reject invalid probability values', async () => {
+      const dto = { probability: 1.5 };
+      await expect(controller.setProbability(dto)).rejects.toThrow();
+    });
+  });
+
+  describe('getStatus', () => {
+    it('should return current chaos status', async () => {
+      cacheService.isRedisEnabled = false;
+      cacheService.chaosProbability = 0.3;
+
+      const result = await controller.getStatus();
+
+      expect(result).toEqual({
+        message: 'Chaos testing status',
+        redisEnabled: false,
+        chaosProbability: 0.3,
+      });
+    });
+  });
path: src/chaos/chaos.controller.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider testing error handling in getStatus method**

The current test only covers the happy path. Add a test for what happens when the cache service throws an error.


```typescript
it('should handle errors from cache service', async () => {
  // Mock the internal properties to throw when accessed
  Object.defineProperty(cacheService, 'isRedisEnabled', {
    get: () => { throw new Error('Test error'); }
  });
  
  // The controller should handle this gracefully
  await expect(controller.getStatus()).rejects.toThrow();
  
  // Reset the mock for other tests
  cacheService.isRedisEnabled = true;
});
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/chaos/chaos.controller.spec.ts around lines 70 to 83, add a new test case
for the getStatus method to cover error handling when the cacheService throws an
error. Mock the cacheService's isRedisEnabled property to throw an error when
accessed, then assert that controller.getStatus() rejects with an error.
Finally, reset the mock to avoid affecting other tests.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T08:51:52Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-19T02:48:06Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces chaos testing capabilities focused on Redis cache resilience. It adds a new ChaosModule with endpoints for toggling Redis and simulating failures, modifies CacheService to support runtime toggling and probabilistic failures, and provides automated GitHub Actions workflow and documentation for chaos testing. The Role enum source is unified to use Prisma's definition.

## Changes

| Files/Paths                                                                 | Change Summary                                                                                                                 |
|-----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
| .github/workflows/chaos-test.yml                                            | Added a GitHub Actions workflow for automated chaos testing, simulating Redis failures and verifying application resilience.   |
| README.md                                                                   | Added documentation for chaos testing features, endpoints, usage instructions, and implementation details.                     |
| package.json                                                                | Increased Jest branch coverage threshold from 70% to 75%.                                                                      |
| src/app.module.ts, src/chaos/chaos.module.ts                                | Imported and registered new ChaosModule in the application.                                                                    |
| src/chaos/chaos.controller.ts, src/chaos/chaos.controller.spec.ts           | Introduced ChaosController with endpoints for toggling Redis, setting chaos probability, and status; added comprehensive tests.|
| src/cache/cache.module.ts                                                   | Added ConfigService to RedisCacheModule providers; reordered imports.                                                          |
| src/cache/cache.service.ts, src/cache/cache.service.spec.ts                 | Enhanced CacheService to support chaos testing, runtime toggling, probabilistic failures, and added extensive tests.           |
| src/common/enums/role.enum.ts                                               | Deleted local Role enum; replaced usage with Prisma's Role enum throughout the codebase.                                       |
| src/auth/decorators/roles.decorator.ts, src/auth/decorators/roles.decorator.spec.ts, src/auth/guards/roles.guard.ts, src/auth/guards/roles.guard.spec.ts, src/users/entities/user.entity.ts, src/users/users.controller.ts, src/users/users.service.spec.ts | Changed Role enum import source from local to @prisma/client in decorators, guards, user entity, controller, and tests.        |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant ChaosController
    participant CacheService

    Client->>ChaosController: POST /chaos/toggle-redis {enabled}
    ChaosController->>CacheService: toggleRedis(enabled)
    CacheService-->>ChaosController: Confirmation

    Client->>ChaosController: POST /chaos/set-probability {probability}
    ChaosController->>CacheService: setChaosProbability(probability)
    CacheService-->>ChaosController: Confirmation

    Client->>ChaosController: GET /chaos/status
    ChaosController->>CacheService: Get status properties
    CacheService-->>ChaosController: Status data
    ChaosController-->>Client: Status response
```

```mermaid
sequenceDiagram
    participant App
    participant CacheService
    participant Redis

    App->>CacheService: get/set/del/getOrSet(key, ...)
    alt Redis enabled and no chaos
        CacheService->>Redis: Perform cache operation
        Redis-->>CacheService: Result
        CacheService-->>App: Result
    else Redis disabled or chaos triggered
        CacheService-->>App: Skip cache, log warning, fallback to direct logic
    end
```

## Possibly related PRs

- dhiazfathra/nestjs-starter#10: Integrates the foundational Redis caching infrastructure that the current chaos testing features extend and build upon, especially in the cache service logic.

## Suggested labels

`released`

## Poem

> In the warren of code where Redis hops,  
> Chaos now dances, and never stops.  
> With toggles and tests, we simulate fate,  
> To see if our cache will break or be great.  
> The rabbits rejoice—resilience is near!  
> 🐇✨

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> npm warn config production Use `--omit=dev` instead.
> npm error Exit handler never called!
> npm error This is an error with npm itself. Please report this error at:
> npm error   <https://github.com/npm/cli/issues>
> npm error A complete log of this run can be found in: /.npm/_logs/2025-05-19T08_47_39_136Z-debug-0.log
> 
> 
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details open="true">
<summary>🧪 Generate Unit Tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with Unit Tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit Unit Tests in branch `feat/add-chaos-testing-module-for-Redis-cache-with-failure-simulation`
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post Copyable Unit Tests in Comment

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=dhiazfathra/nestjs-starter&utm_content=24):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: dhiazfathra
created_at: 2025-05-19T02:48:10Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/dhiazfathra/pr-checks/3eb12417-b3c0-45ae-a670-b81df8e8d760?refs=8c227a20e7dbce34397fa09b47e1166a29db9eea&source=prChecksComment)



author_association: OWNER
user: login: codecov[bot]
created_at: 2025-05-19T02:50:04Z
body: ## [Codecov](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) Report
All modified and coverable lines are covered by tests :white_check_mark:

:white_check_mark: All tests successful. No failed tests found.

[![Impacted file tree graph](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24/graphs/tree.svg?width=650&height=150&src=pr&token=PYY1J4PRZU&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra)](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra)

```diff
@@            Coverage Diff            @@
##            master       #24   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           23        23           
  Lines          335       379   +44     
  Branches        21        28    +7     
=========================================
+ Hits           335       379   +44     
```

| [Flag](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) | `100.00% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files with missing lines](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) | Coverage Δ | |
|---|---|---|
| [src/auth/decorators/roles.decorator.ts](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?src=pr&el=tree&filepath=src%2Fauth%2Fdecorators%2Froles.decorator.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#diff-c3JjL2F1dGgvZGVjb3JhdG9ycy9yb2xlcy5kZWNvcmF0b3IudHM=) | `100.00% <ø> (ø)` | |
| [src/auth/guards/roles.guard.ts](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?src=pr&el=tree&filepath=src%2Fauth%2Fguards%2Froles.guard.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#diff-c3JjL2F1dGgvZ3VhcmRzL3JvbGVzLmd1YXJkLnRz) | `100.00% <ø> (ø)` | |
| [src/cache/cache.service.ts](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?src=pr&el=tree&filepath=src%2Fcache%2Fcache.service.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#diff-c3JjL2NhY2hlL2NhY2hlLnNlcnZpY2UudHM=) | `100.00% <100.00%> (ø)` | |
| [src/chaos/chaos.controller.ts](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?src=pr&el=tree&filepath=src%2Fchaos%2Fchaos.controller.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#diff-c3JjL2NoYW9zL2NoYW9zLmNvbnRyb2xsZXIudHM=) | `100.00% <100.00%> (ø)` | |
| [src/users/users.controller.ts](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?src=pr&el=tree&filepath=src%2Fusers%2Fusers.controller.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#diff-c3JjL3VzZXJzL3VzZXJzLmNvbnRyb2xsZXIudHM=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra). Last update [a6ff806...8c227a2](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/24?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra).

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-19T11:07:34Z
body: :tada: This PR is included in version 1.7.0 :tada:

The release is available on [GitHub release](https://github.com/dhiazfathra/nestjs-starter/releases/tag/v1.7.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE