Skip to content

Commit

Permalink
Merge pull request #229 from Quickchive/test-#226/post-login
Browse files Browse the repository at this point in the history
[Test] e2e test 용 유틸 객체/의존성 추가 및 login e2e test
  • Loading branch information
stae1102 authored Jan 20, 2024
2 parents 1e31678 + ecd0aa8 commit 4ced4f1
Show file tree
Hide file tree
Showing 10 changed files with 1,570 additions and 27 deletions.
1,381 changes: 1,358 additions & 23 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test": "jest --detectOpenHandles --forceExit",
"test:watch": "cross-env NODE_ENV=test jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:e2e": "jest --config ./test/jest-e2e.json --detectOpenHandles --forceExit",
"typeorm": "typeorm-ts-node-commonjs",
"migration:generate": "npm run typeorm -- -d src/database/ormconfig.ts migration:generate",
"migration:create": "npm run typeorm -- migration:create",
Expand Down Expand Up @@ -67,9 +67,11 @@
"winston-daily-rotate-file": "^4.7.1"
},
"devDependencies": {
"@faker-js/faker": "^8.3.1",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.1",
"@nestjs/testing": "^9.1.2",
"@testcontainers/postgresql": "^10.6.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.6",
"@types/node": "^18.0.6",
Expand Down
9 changes: 9 additions & 0 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Controller, Get } from '@nestjs/common';

@Controller()
export class AppController {
@Get()
healthCheck() {
return { data: true };
}
}
3 changes: 2 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { BatchModule } from './batch/batch.module';
import { SummaryModule } from './summary/summary.module';
import { TypeOrmConfigService } from './database/typerom-config.service';
import { OpenaiModule } from './openai/openai.module';
import { AppController } from './app.controller';

@Module({
imports: [
Expand Down Expand Up @@ -101,7 +102,7 @@ import { OpenaiModule } from './openai/openai.module';
}),
OpenaiModule,
],
controllers: [],
controllers: [AppController],
providers: [],
})
export class AppModule {}
135 changes: 135 additions & 0 deletions test/auth/post-login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { getBuilder } from '../common/application-builder';
import { CACHE_MANAGER, HttpStatus, INestApplication } from '@nestjs/common';
import { cacheManagerMock } from '../mock/cache-manager.mock';
import { User } from '../../src/users/entities/user.entity';
import { DataSource, Repository } from 'typeorm';
import { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
import * as request from 'supertest';
import { Seeder } from '../seeder/seeder.interface';
import { LoginBodyDto } from '../../src/auth/dtos/login.dto';
import { UserSeeder } from '../seeder/user.seeder';
import * as bcrypt from 'bcrypt';

jest.setTimeout(30_000);
describe('[POST] /api/auth/login', () => {
// Application
let app: INestApplication;
let container: StartedPostgreSqlContainer; // TODO 결합도 낮추기
let dataSource: DataSource;

// Repository
let userRepository: Repository<User>;

// Seeder
const userSeeder: Seeder<User> = new UserSeeder();

// Stub
let userStub: User;

beforeAll(async () => {
const {
builder,
container: _container,
dataSource: _dataSource,
} = await getBuilder();
container = _container;
dataSource = _dataSource;

const module = await builder
.overrideProvider(DataSource)
.useValue(dataSource)
.overrideProvider(CACHE_MANAGER)
.useValue(cacheManagerMock)
.compile();

app = module.createNestApplication();
await app.init();

userRepository = dataSource.getRepository(User);
});

beforeEach(async () => {
await dataSource.synchronize(true);
});

afterAll(async () => {
await app.close();
await container.stop();
});

it('testHealthCheck', async () => {
const { status, body } = await request(app.getHttpServer()).get('');

expect(status).toBe(HttpStatus.OK);
expect(body.data).toBe(true);
});

/**
* 1. 로그인에 성공한다.
* 2. 유저가 존재하지 않아 실패한다.
* 3. 비밀번호가 일치하지 않아 실패한다.
*/

describe('로그인에 성공한다.', () => {
beforeEach(async () => {
userStub = userSeeder.generateOne();
const hashedPassword = await bcrypt.hash(userStub.password, 10);
await userRepository.save({ ...userStub, password: hashedPassword });
});

it('access token 및 refresh token과 함께 201을 반환한다.', async () => {
const loginBodyDto: LoginBodyDto = {
email: userStub.email,
password: userStub.password,
auto_login: true,
};

const { status, body } = await request(app.getHttpServer())
.post('/auth/login')
.send(loginBodyDto);

expect(status).toBe(HttpStatus.CREATED);
expect(typeof body.access_token).toBe('string');
expect(typeof body.refresh_token).toBe('string');
});
});

describe('유저가 존재하지 않아 실패한다.', () => {
it('404 예외를 던진다.', async () => {
const loginBodyDto: LoginBodyDto = {
email: userStub.email,
password: userStub.password,
auto_login: true,
};

const { status, body } = await request(app.getHttpServer())
.post('/auth/login')
.send(loginBodyDto);

expect(status).toBe(HttpStatus.NOT_FOUND);
expect(body.message).toBe('User Not Found');
});
});

describe('비밀번호가 일치하지 않아 실패한다.', () => {
beforeEach(async () => {
userStub = userSeeder.generateOne();
await userRepository.save(userStub);
});

it('400 예외를 던진다.', async () => {
const loginBodyDto: LoginBodyDto = {
email: userStub.email,
password: userStub.password + 'fail',
auto_login: true,
};

const { status, body } = await request(app.getHttpServer())
.post('/auth/login')
.send(loginBodyDto);

expect(status).toBe(HttpStatus.BAD_REQUEST);
expect(body.message).toBe('Wrong Password');
});
});
});
32 changes: 32 additions & 0 deletions test/common/application-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Test } from '@nestjs/testing';
import { AppModule } from '../../src/app.module';
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import { DataSource, DataSourceOptions } from 'typeorm';

export const getBuilder = async () => {
const container = await new PostgreSqlContainer()
.withDatabase('quickchive_test')
.withReuse()
.start();

const dbConfigOption: DataSourceOptions = {
type: 'postgres',
host: container.getHost(),
port: container.getMappedPort(5432),
username: container.getUsername(),
password: container.getPassword(),
database: container.getDatabase(),
entities: [__dirname + '/../../src/**/*.entity{.ts,.js}'],
synchronize: true,
};

const dataSource = await new DataSource(dbConfigOption).initialize();

return {
builder: Test.createTestingModule({
imports: [AppModule],
}),
container,
dataSource,
};
};
2 changes: 1 addition & 1 deletion test/jest-e2e.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"testRegex": ".*spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
Expand Down
3 changes: 3 additions & 0 deletions test/mock/cache-manager.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const cacheManagerMock = {
set: jest.fn(),
};
3 changes: 3 additions & 0 deletions test/seeder/seeder.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Seeder<T> {
generateOne(options?: { [K in keyof T]: any }): T;
}
23 changes: 23 additions & 0 deletions test/seeder/user.seeder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Seeder } from './seeder.interface';
import { User, UserRole } from '../../src/users/entities/user.entity';
import { faker } from '@faker-js/faker';

export class UserSeeder implements Seeder<User> {
private readonly userStub = {
name: faker.person.lastName(),
email: faker.internet.email(),
profileImage: faker.internet.url(),
password:
'1' +
faker.internet.password({
length: 10,
// pattern: /^(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/,
}),
role: UserRole.Client,
verified: true,
};

generateOne(options?: { [K in keyof User]: any }): User {
return { ...this.userStub, ...options } as User;
}
}

0 comments on commit 4ced4f1

Please sign in to comment.