Skip to content

Commit

Permalink
Merge pull request boostcampwm-2024#230 from boostcampwm-2024/feature…
Browse files Browse the repository at this point in the history
…-be-#160

[boostcampwm-2024#160] UserDBInterceptor 테스트 코드 작성
  • Loading branch information
mintaek22 authored Dec 3, 2024
2 parents 1b37e9d + 7fca794 commit 9e2ab30
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 47 deletions.
3 changes: 2 additions & 1 deletion BE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
"testEnvironment": "node",
"setupFiles": ["<rootDir>/jest.setup.js"]
}
}
12 changes: 12 additions & 0 deletions BE/src/common/exception/custom-exception.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ export class DataLimitExceedException extends HttpException {
);
}
}

export class ConnectionLimitExceedException extends HttpException {
constructor() {
super(
{
status: HttpStatus.TOO_MANY_REQUESTS,
message: 'Too many users right now! Please try again soon.',
},
HttpStatus.TOO_MANY_REQUESTS,
);
}
}
9 changes: 0 additions & 9 deletions BE/src/config/query-database/admin-query-db.moudle.ts

This file was deleted.

21 changes: 8 additions & 13 deletions BE/src/interceptors/user-db-connection.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {
CallHandler,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { createConnection } from 'mysql2/promise';
import { ConfigService } from '@nestjs/config';
import { catchError, finalize, Observable, tap } from 'rxjs';
import { createReadStream } from 'fs';
import { DataLimitExceedException } from '../common/exception/custom-exception';
import {
ConnectionLimitExceedException,
DataLimitExceedException,
} from '../common/exception/custom-exception';

@Injectable()
export class UserDBConnectionInterceptor implements NestInterceptor {
Expand All @@ -34,17 +35,11 @@ export class UserDBConnectionInterceptor implements NestInterceptor {
return createReadStream(path);
},
});
} catch (error) {
console.error('커넥션 제한으로 인한 에러', error);
if (error.errno == 1040) {
throw new HttpException(
{
status: HttpStatus.TOO_MANY_REQUESTS,
message: 'Too many users right now! Please try again soon.',
},
HttpStatus.TOO_MANY_REQUESTS,
);
} catch (err) {
if (err.errno == 1040) {
throw new ConnectionLimitExceedException();
}
throw err;
}

await request.dbConnection.query('set profiling = 1');
Expand Down
24 changes: 0 additions & 24 deletions BE/test/app.e2e-spec.ts

This file was deleted.

215 changes: 215 additions & 0 deletions BE/test/interceptor/user-db-connection.interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { UserDBConnectionInterceptor } from '../../src/interceptors/user-db-connection.interceptor';
import {
BadGatewayException,
CallHandler,
ExecutionContext,
} from '@nestjs/common';
import { MySqlContainer } from '@testcontainers/mysql';
import { ConfigService } from '@nestjs/config';
import { lastValueFrom, of, throwError } from 'rxjs';
import { mock } from 'jest-mock-extended';
import {
ConnectionLimitExceedException,
DataLimitExceedException,
} from '../../src/common/exception/custom-exception';
import { StartedTestContainer } from 'testcontainers';
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
import { Test, TestingModule } from '@nestjs/testing';

let interceptor: UserDBConnectionInterceptor;
let dbContainer: StartedTestContainer;
const mockContext = mock<ExecutionContext>();
const mockConfigService = mock<ConfigService>();
const mockCallHandler = mock<CallHandler>();

const TEST_SESSION_ID = 'db12345678';
const TEST_REQUEST = {
sessionID: TEST_SESSION_ID,
dbConnection: null,
};

beforeAll(async () => {
dbContainer = await new MySqlContainer()
.withUsername(TEST_SESSION_ID.substring(0, 10))
.withUserPassword(TEST_SESSION_ID)
.withDatabase(TEST_SESSION_ID)
.withExposedPorts(3306)
.withCommand(['--max_connections=1'])
.start();
});

afterAll(async () => {
await dbContainer.stop();
});

beforeEach(async () => {
// Mock ConfigService
mockConfigService.get.mockImplementation((key: string) => {
const config = {
QUERY_DB_HOST: dbContainer.getHost(),
QUERY_DB_PORT: dbContainer.getMappedPort(3306),
};
return config[key];
});

//Mock Context
setupMockContext();
const module: TestingModule = await Test.createTestingModule({
providers: [
UserDBConnectionInterceptor,
{ provide: ConfigService, useValue: mockConfigService },
],
}).compile();

interceptor = module.get<UserDBConnectionInterceptor>(
UserDBConnectionInterceptor,
);
});

afterEach(async () => {
if (TEST_REQUEST.dbConnection) {
await TEST_REQUEST.dbConnection.close();
TEST_REQUEST.dbConnection = null;
}
});

const setupMockContext = () => {
const mockHttpArgumentsHost = mock<HttpArgumentsHost>();
mockHttpArgumentsHost.getRequest.mockReturnValue(TEST_REQUEST);
mockContext.switchToHttp.mockReturnValue(mockHttpArgumentsHost);
};

describe('UserDBConnectionInterceptor - 요청 처리', () => {
beforeEach(() => {
mockCallHandler.handle.mockReturnValue(of('test response'));
});

it('요청이 오면 DB Connection을 생성한다.', async () => {
//given&when
await interceptor.intercept(mockContext, mockCallHandler);

//then
expect(TEST_REQUEST.dbConnection).toBeDefined();
expect(mockCallHandler.handle).toHaveBeenCalled();
});

it('DB Connection 제한을 초과하면 에러를 반환한다.', async () => {
//given&when
await interceptor.intercept(mockContext, mockCallHandler);

//then
await expect(
interceptor.intercept(mockContext, mockCallHandler),
).rejects.toThrowError(ConnectionLimitExceedException);
});
});

describe('UserDBConnectionInterceptor - 응답 처리', () => {
it('성공적인 응답에 대해 commit이 호출된다.', async () => {
//given
mockCallHandler.handle.mockReturnValue(of('test response'));

//when
const observable = await interceptor.intercept(
mockContext,
mockCallHandler,
);
const commitSpy = jest.spyOn(TEST_REQUEST.dbConnection, 'commit');

//then
await expect(lastValueFrom(observable)).resolves.toBe('test response');
expect(commitSpy).toHaveBeenCalled();
});

it('성공적인 응답에 대해 커넥션이 종료된다.', async () => {
//given
mockCallHandler.handle.mockReturnValue(of('test response'));

//when
const observable = await interceptor.intercept(
mockContext,
mockCallHandler,
);
const endSpy = jest.spyOn(TEST_REQUEST.dbConnection, 'end');

//then
await expect(lastValueFrom(observable)).resolves.toBe('test response');
expect(endSpy).toHaveBeenCalled();
});

it('용량 초과 에러에 대해 rollback이 호출된다.', async () => {
//given
mockCallHandler.handle.mockImplementation(() =>
throwError(() => new DataLimitExceedException()),
);

//when
const observable = await interceptor.intercept(
mockContext,
mockCallHandler,
);
const rollbackSpy = jest.spyOn(TEST_REQUEST.dbConnection, 'rollback');

//then
await expect(lastValueFrom(observable)).rejects.toThrow(
DataLimitExceedException,
);
expect(rollbackSpy).toHaveBeenCalled();
});

it('용량 초과 에러에 대해 커넥션이 종료된다.', async () => {
//given
mockCallHandler.handle.mockImplementation(() =>
throwError(() => new DataLimitExceedException()),
);

//when
const observable = await interceptor.intercept(
mockContext,
mockCallHandler,
);
const endSpy = jest.spyOn(TEST_REQUEST.dbConnection, 'end');

//then
await expect(lastValueFrom(observable)).rejects.toThrow(
DataLimitExceedException,
);
expect(endSpy).toHaveBeenCalled();
});

it('일반 에러에 대해 rollback이 호출되지 않는다.', async () => {
//given
mockCallHandler.handle.mockImplementation(() =>
throwError(() => new Error()),
);

//when
const observable = await interceptor.intercept(
mockContext,
mockCallHandler,
);
const rollbackSpy = jest.spyOn(TEST_REQUEST.dbConnection, 'rollback');

//then
await expect(lastValueFrom(observable)).rejects.toThrow(Error());
expect(rollbackSpy).not.toHaveBeenCalled();
});

it('일반 에러에 대해 커넥션이 종료된다.', async () => {
//given
mockCallHandler.handle.mockImplementation(() =>
throwError(() => new Error()),
);

//when
const observable = await interceptor.intercept(
mockContext,
mockCallHandler,
);
const endSpy = jest.spyOn(TEST_REQUEST.dbConnection, 'end');

//then
await expect(lastValueFrom(observable)).rejects.toThrow(Error());
expect(endSpy).toHaveBeenCalled();
});
});
1 change: 1 addition & 0 deletions BE/test/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jest.setTimeout(100000);

0 comments on commit 9e2ab30

Please sign in to comment.