diff --git a/server/package-lock.json b/server/package-lock.json index 5fd818aa..e677489c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -47,6 +47,7 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@testcontainers/mysql": "^10.16.0", + "@testcontainers/redis": "^10.16.0", "@types/bcrypt": "^5.0.2", "@types/cookie-parser": "^1.4.7", "@types/eventsource": "^1.1.15", @@ -2525,6 +2526,15 @@ "testcontainers": "^10.16.0" } }, + "node_modules/@testcontainers/redis": { + "version": "10.16.0", + "resolved": "https://registry.npmjs.org/@testcontainers/redis/-/redis-10.16.0.tgz", + "integrity": "sha512-+NJO1tfMXvUQiMfa9Y8qqwaFP6yV0BBg2cNA+iNz+Zt6kzTyxMapBiKCL8pWsCqWolz2mqZwuDtfcHBxdoCzAw==", + "dev": true, + "dependencies": { + "testcontainers": "^10.16.0" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", diff --git a/server/package.json b/server/package.json index d893308c..e997e9f3 100644 --- a/server/package.json +++ b/server/package.json @@ -60,6 +60,7 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@testcontainers/mysql": "^10.16.0", + "@testcontainers/redis": "^10.16.0", "@types/bcrypt": "^5.0.2", "@types/cookie-parser": "^1.4.7", "@types/eventsource": "^1.1.15", diff --git a/server/src/admin/service/admin.service.ts b/server/src/admin/service/admin.service.ts index e67521d7..169ce8de 100644 --- a/server/src/admin/service/admin.service.ts +++ b/server/src/admin/service/admin.service.ts @@ -41,7 +41,7 @@ export class AdminService { const sessionId = uuid.v4(); if (cookie) { - this.redisService.del(`auth:${cookie}`); + await this.redisService.del(`auth:${cookie}`); } let cursor = '0'; @@ -74,7 +74,7 @@ export class AdminService { } } while (cursor !== '0'); - this.redisService.set( + await this.redisService.set( `auth:${sessionId}`, admin.loginId, `EX`, @@ -86,7 +86,7 @@ export class AdminService { async logoutAdmin(request: Request, response: Response) { const sid = request.cookies['sessionId']; - this.redisService.del(`auth:${sid}`); + await this.redisService.del(`auth:${sid}`); response.clearCookie('sessionId'); } diff --git a/server/src/common/redis/redis.module.ts b/server/src/common/redis/redis.module.ts index fa962846..98ed36b0 100644 --- a/server/src/common/redis/redis.module.ts +++ b/server/src/common/redis/redis.module.ts @@ -2,7 +2,6 @@ import { Global, Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { RedisService } from './redis.service'; import Redis from 'ioredis'; -import Redis_Mock from 'ioredis-mock'; @Global() @Module({ @@ -12,10 +11,6 @@ import Redis_Mock from 'ioredis-mock'; provide: 'REDIS_CLIENT', inject: [ConfigService], useFactory: async (configService: ConfigService) => { - const envType = process.env.NODE_ENV; - if (envType === 'test') { - return new Redis_Mock(); - } return new Redis({ host: configService.get('REDIS_HOST'), port: configService.get('REDIS_PORT'), diff --git a/server/src/common/redis/redis.service.ts b/server/src/common/redis/redis.service.ts index a4288977..d654a38b 100644 --- a/server/src/common/redis/redis.service.ts +++ b/server/src/common/redis/redis.service.ts @@ -6,6 +6,10 @@ import { RedisKey } from 'ioredis/built/utils/RedisCommander'; export class RedisService { constructor(@Inject('REDIS_CLIENT') public readonly redisClient: Redis) {} + async disconnect(): Promise { + await this.redisClient.disconnect(); + } + async get(key: RedisKey): Promise { return this.redisClient.get(key); } @@ -116,6 +120,10 @@ export class RedisService { return this.redisClient.flushdb(); } + async flushall(): Promise { + this.redisClient.flushall(); + } + async sismember(key: string, member: string | number): Promise { return this.redisClient.sismember(key, member); } diff --git a/server/test/admin/e2e/logout.e2e-spec.ts b/server/test/admin/e2e/logout.e2e-spec.ts index bee7cb3f..b104fb28 100644 --- a/server/test/admin/e2e/logout.e2e-spec.ts +++ b/server/test/admin/e2e/logout.e2e-spec.ts @@ -8,7 +8,7 @@ describe('POST /api/admin/logout E2E Test', () => { beforeAll(async () => { app = global.testApp; const redisService = app.get(RedisService); - await redisService.sadd('auth:sid', 'test1234'); + await redisService.set('auth:sid', 'test1234'); }); it('관리자 로그인이 되어 있으면 로그아웃을 정상적으로 할 수 있다.', async () => { diff --git a/server/test/integration-test-global-setup.ts b/server/test/integration-test-global-setup.ts new file mode 100644 index 00000000..2df5a5f9 --- /dev/null +++ b/server/test/integration-test-global-setup.ts @@ -0,0 +1,38 @@ +import { MySqlContainer, StartedMySqlContainer } from '@testcontainers/mysql'; +import { RedisContainer, StartedRedisContainer } from '@testcontainers/redis'; +const globalAny: any = global; + +export default async () => { + console.log('Starting global setup...'); + await createMysqlContainer(); + await createRedisContainer(); + console.log('Global setup completed.'); +}; + +const createMysqlContainer = async () => { + console.log('Starting MySQL container...'); + const mysqlContainer: StartedMySqlContainer = await new MySqlContainer( + 'mysql:8.0.39', + ) + .withDatabase('denamu') + .start(); + globalAny.__MYSQL_CONTAINER__ = mysqlContainer; + + process.env.DB_TYPE = 'mysql'; + process.env.DB_HOST = mysqlContainer.getHost(); + process.env.DB_PORT = mysqlContainer.getPort().toString(); + process.env.DB_USERNAME = mysqlContainer.getUsername(); + process.env.DB_PASSWORD = mysqlContainer.getUserPassword(); + process.env.DB_DATABASE = mysqlContainer.getDatabase(); +}; + +const createRedisContainer = async () => { + console.log('Starting Redis container...'); + const redisContainer: StartedRedisContainer = await new RedisContainer( + 'redis:6.0.16-alpine', + ).start(); + globalAny.__REDIS_CONTAINER__ = redisContainer; + + process.env.REDIS_HOST = redisContainer.getHost(); + process.env.REDIS_PORT = redisContainer.getPort().toString(); +}; diff --git a/server/test/mysql-global-teardown.ts b/server/test/integration-test-global-teardown.ts similarity index 62% rename from server/test/mysql-global-teardown.ts rename to server/test/integration-test-global-teardown.ts index 77d54381..e38825f6 100644 --- a/server/test/mysql-global-teardown.ts +++ b/server/test/integration-test-global-teardown.ts @@ -1,17 +1,17 @@ const globalAny: any = global; export default async () => { - console.log('Stopping NestJS application...'); - if (globalAny.testApp) { - await globalAny.testApp.close(); - delete globalAny.testApp; - } - console.log('Stopping MySQL container...'); if (globalAny.__MYSQL_CONTAINER__) { await globalAny.__MYSQL_CONTAINER__.stop(); delete globalAny.__MYSQL_CONTAINER__; } + console.log('Stopping Redis container...'); + if (globalAny.__REDIS_CONTAINER__) { + await globalAny.__REDIS_CONTAINER__.stop(); + delete globalAny.__REDIS_CONTAINER__; + } + console.log('Global teardown completed.'); }; diff --git a/server/test/jest-e2e.json b/server/test/jest-e2e.json index 62327c68..5363eb7f 100644 --- a/server/test/jest-e2e.json +++ b/server/test/jest-e2e.json @@ -8,7 +8,7 @@ }, "coverageDirectory": "test/coverage", "setupFilesAfterEnv": ["./test/jest.setup.ts"], - "globalSetup": "./test/mysql-global-setup.ts", - "globalTeardown": "./test/mysql-global-teardown.ts", + "globalSetup": "./test/integration-test-global-setup.ts", + "globalTeardown": "./test/integration-test-global-teardown.ts", "maxWorkers": 1 } diff --git a/server/test/jest-integration.json b/server/test/jest-integration.json index 5f3bd1da..607698c4 100644 --- a/server/test/jest-integration.json +++ b/server/test/jest-integration.json @@ -8,7 +8,7 @@ }, "coverageDirectory": "test/coverage", "setupFilesAfterEnv": ["./test/jest.setup.ts"], - "globalSetup": "./test/mysql-global-setup.ts", - "globalTeardown": "./test/mysql-global-teardown.ts", + "globalSetup": "./test/integration-test-global-setup.ts", + "globalTeardown": "./test/integration-test-global-teardown.ts", "maxWorkers": 1 } \ No newline at end of file diff --git a/server/test/jest.setup.ts b/server/test/jest.setup.ts index de29f6ba..c745f6e0 100644 --- a/server/test/jest.setup.ts +++ b/server/test/jest.setup.ts @@ -1,4 +1,4 @@ -import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { AppModule } from '../src/app.module'; import { WinstonLoggerService } from '../src/common/logger/logger.service'; @@ -6,6 +6,7 @@ import { InternalExceptionsFilter } from '../src/common/filters/internal-excepti import { HttpExceptionsFilter } from '../src/common/filters/http-exception.filter'; import * as cookieParser from 'cookie-parser'; import { TestService } from '../src/common/test/test.service'; +import { RedisService } from '../src/common/redis/redis.service'; const globalAny: any = global; @@ -34,6 +35,10 @@ afterAll(async () => { const testService = globalAny.testApp.get(TestService); await testService.cleanDatabase(); + const redisService: RedisService = globalAny.testApp.get(RedisService); + await redisService.flushall(); + await redisService.disconnect(); + console.log('Closing NestJS application...'); if (globalAny.testApp) { await globalAny.testApp.close(); diff --git a/server/test/mysql-global-setup.ts b/server/test/mysql-global-setup.ts deleted file mode 100644 index 5740f2d2..00000000 --- a/server/test/mysql-global-setup.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { MySqlContainer, StartedMySqlContainer } from '@testcontainers/mysql'; -const globalAny: any = global; - -export default async () => { - console.log('Starting MySQL container...'); - const testContainer: StartedMySqlContainer = await new MySqlContainer( - 'mysql:8', - ) - .withDatabase('denamu') - .start(); - console.log('MySQL container started.'); - globalAny.__MYSQL_CONTAINER__ = testContainer; - - // 환경 변수 설정 - process.env.DB_TYPE = 'mysql'; - process.env.DB_HOST = testContainer.getHost(); - process.env.DB_PORT = testContainer.getPort().toString(); - process.env.DB_USERNAME = testContainer.getUsername(); - process.env.DB_PASSWORD = testContainer.getUserPassword(); - process.env.DB_DATABASE = testContainer.getDatabase(); - - console.log('Global setup completed.'); -}; diff --git a/server/test/rss/e2e/history/accept.e2e-spec.ts b/server/test/rss/e2e/history/accept.e2e-spec.ts index 6985ce7f..e675c051 100644 --- a/server/test/rss/e2e/history/accept.e2e-spec.ts +++ b/server/test/rss/e2e/history/accept.e2e-spec.ts @@ -17,7 +17,7 @@ describe('GET /api/rss/history/accept E2E Test', () => { } await Promise.all([ rssAcceptRepository.insert(rssAccepts), - redisService.sadd('auth:sid', 'test1234'), + redisService.set('auth:sid', 'test1234'), ]); }); diff --git a/server/test/rss/e2e/history/reject.e2e-spec.ts b/server/test/rss/e2e/history/reject.e2e-spec.ts index 30e537c1..3a39c2ec 100644 --- a/server/test/rss/e2e/history/reject.e2e-spec.ts +++ b/server/test/rss/e2e/history/reject.e2e-spec.ts @@ -17,7 +17,7 @@ describe('GET /api/rss/history/reject E2E Test', () => { } await Promise.all([ rssRejectRepository.insert(rssAccepts), - redisService.sadd('auth:sid', 'test1234'), + redisService.set('auth:sid', 'test1234'), ]); });