Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/local register #7

Merged
merged 30 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f66279f
feat: 全域驗證器默認設定
a20688392 Jun 2, 2023
cfbfb42
feat: 使用者資料表初始化
a20688392 Jun 2, 2023
20d10f6
feat: 使用者註冊驗證
a20688392 Jun 2, 2023
539f8d6
feat: 使用者註冊資料儲存
a20688392 Jun 2, 2023
154dcce
feat: 使用者註冊密碼加密
a20688392 Jun 2, 2023
598f153
feat: 使用者註冊 swagger demo
a20688392 Jun 2, 2023
dcc4c9e
feat: 使用者註冊加上回應狀態
a20688392 Jun 2, 2023
2a24498
test: auth service unit test
a20688392 Jun 3, 2023
75afc68
test: auth controller 單元測試-dto資料驗證
a20688392 Jun 4, 2023
dd188b1
test: auth controller 單元測試-創建本地使用者201回應
a20688392 Jun 4, 2023
ac250a9
test: auth controller 單元測試-創建本地使用者資料驗證失敗406回應
a20688392 Jun 4, 2023
561c7c5
test: auth controller 單元測試-providers 資料庫命名錯誤修正
a20688392 Jun 4, 2023
147bdd4
test: auth controller 單元測試-創建本地使用者資料重覆失敗409回應
a20688392 Jun 4, 2023
6acb3ea
test: auth service 單元測試加上測試後復原資料庫
a20688392 Jun 4, 2023
ffd8019
test: 將測試資料庫改成 sqlite
a20688392 Jun 4, 2023
af03407
test: 將 afterEach 搬移至最後面並只需要一次
a20688392 Jun 6, 2023
d6efa65
fix: 將非此次測試的刪除
a20688392 Jun 6, 2023
24c0968
fix: 修正狀態碼 406->400
a20688392 Jun 6, 2023
3a422bf
refactor: 將 createAt 改成 createdAt
a20688392 Jun 6, 2023
7a8ebd3
fix: 範例訊息移除 name 的重複衝突
a20688392 Jun 6, 2023
ca5cdce
test: usersService 單元測試-創建使用者
a20688392 Jun 6, 2023
4097153
refactor: 將 try catch 移除
a20688392 Jun 7, 2023
1718f03
refactor: 更改為使用 hashSync
a20688392 Jun 7, 2023
b9f0972
refactor: 使用單數 user
a20688392 Jun 8, 2023
f33bcc6
fix: auth controller 單元測試-重新撰寫是否會正確驗證傳入的 payload
a20688392 Jun 8, 2023
039491a
refactor: 將 DTO 驗證搬移出去
a20688392 Jun 8, 2023
29f3558
refactor: 將自定義的 validationPipe 獨立出來
a20688392 Jun 8, 2023
e572da2
refactor: 應使用 pips 命名資料夾
a20688392 Jun 10, 2023
8abcc3b
refactor: 刪除多餘命名
a20688392 Jun 10, 2023
72289d5
refactor: 驗證 dto 使用自訂 validation-pipe
a20688392 Jun 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test": "jest --verbose",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
Expand All @@ -39,6 +39,7 @@
"@nestjs/platform-express": "^9.0.0",
"@nestjs/swagger": "^6.1.4",
"@nestjs/typeorm": "^9.0.1",
"bcrypt": "^5.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.0.3",
Expand Down Expand Up @@ -71,6 +72,7 @@
"lint-staged": "^13.1.0",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"sqlite3": "^5.1.6",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
Expand All @@ -93,6 +95,9 @@
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
"testEnvironment": "node",
"moduleNameMapper": {
"src/(.*)$": "<rootDir>/$1"
}
}
}
4 changes: 4 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import { TypeOrmModule } from "@nestjs/typeorm";

import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { AuthModule } from "./auth/auth.module";
import { dataSourceOptions } from "./config/data-source";
import { validate } from "./config/env.validation";
import { UserModule } from "./user/user.module";

@Module({
imports: [
ConfigModule.forRoot({
validate,
}),
TypeOrmModule.forRoot(dataSourceOptions),
UserModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
79 changes: 79 additions & 0 deletions src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ConflictException } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken, TypeOrmModule } from "@nestjs/typeorm";
import { dataSourceJest } from "src/config/data-source";
import { UserEntity } from "src/user/entities/user.entity";
import { CreateUserRespose } from "src/user/resposes/create-user-respose";
import { UserService } from "src/user/user.service";
import { Repository } from "typeorm";

import { CreateUserDto } from "../user/dto/create-user.dto";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";

describe("AuthController", () => {
let authController: AuthController;
let authService: AuthService;
let userRepository: Repository<UserEntity>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forRoot(dataSourceJest)],
controllers: [AuthController],
providers: [
AuthService,
UserService,
{
provide: getRepositoryToken(UserEntity),
useValue: UserEntity, // 使用測試資料庫的 Repository
},
],
}).compile();

authController = module.get<AuthController>(AuthController);
authService = module.get<AuthService>(AuthService);
userRepository = module.get<Repository<UserEntity>>(
getRepositoryToken(UserEntity),
);
});
describe("create", () => {
it("應該會創建一個使用者,並返回 201 狀態碼", async () => {
const createUserDto: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account",
password: "Password@123",
};
const expectedResponse: CreateUserRespose = {
statusCode: 201,
message: "創建成功",
};
jest.spyOn(authService, "register").mockResolvedValue(expectedResponse);
const result = await authController.register(createUserDto);
expect(result).toEqual(expectedResponse);
});
it("應該會發生資料使用者重覆,並返回 409 狀態碼", async () => {
const createUserDto1: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account1",
password: "Password@123",
};
try {
await authService.register(createUserDto1);
await authService.register(createUserDto1);
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.response).toEqual({
statusCode: 409,
message: ["email 已被註冊。", "account 已被註冊。"],
error: "Conflict",
});
}
});
});
afterEach(async () => {
if (userRepository && userRepository.clear) {
await userRepository.clear();
}
});
});
40 changes: 40 additions & 0 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Body, Controller, Post } from "@nestjs/common";
import {
ApiBadRequestResponse,
ApiConflictResponse,
ApiCreatedResponse,
ApiOperation,
ApiTags,
} from "@nestjs/swagger";
import { CreateUserDto } from "src/user/dto/create-user.dto";
import { CreateUserBadrequestError } from "src/user/exceptions/create-user-badrequest-error.exception";
import { CreateUserConflictError } from "src/user/exceptions/create-user-conflict-error.exception";
import { CreateUserRespose } from "src/user/resposes/create-user-respose";

import { AuthService } from "./auth.service";

@ApiTags("Auth")
@Controller("auth")
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post("register")
@ApiOperation({
summary: "使用者註冊",
description: "會檢查是否重複過的資料",
})
@ApiCreatedResponse({
description: "使用者創建成功",
type: CreateUserRespose,
})
@ApiConflictResponse({
description: "使用者資料重覆",
type: CreateUserConflictError,
})
@ApiBadRequestResponse({
description: "使用者格式不符",
type: CreateUserBadrequestError,
})
register(@Body() userDto: CreateUserDto) {
return this.authService.register(userDto);
}
}
14 changes: 14 additions & 0 deletions src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UserEntity } from "src/user/entities/user.entity";
import { UserModule } from "src/user/user.module";

import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";

@Module({
imports: [UserModule, TypeOrmModule.forFeature([UserEntity])],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
129 changes: 129 additions & 0 deletions src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { ConflictException, HttpStatus } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken, TypeOrmModule } from "@nestjs/typeorm";
import { validate } from "class-validator";
import { dataSourceJest } from "src/config/data-source";
import { CreateUserDto } from "src/user/dto/create-user.dto";
import { UserEntity } from "src/user/entities/user.entity";
import { UserService } from "src/user/user.service";
import { Repository } from "typeorm";

import { AuthService } from "./auth.service";

describe("AuthService", () => {
let authService: AuthService;
let userRepository: Repository<UserEntity>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forRoot(dataSourceJest)],
providers: [
AuthService,
UserService,
{
provide: getRepositoryToken(UserEntity),
useValue: UserEntity, // 使用測試資料庫的 Repository
},
],
}).compile();

authService = module.get<AuthService>(AuthService);
userRepository = module.get<Repository<UserEntity>>(
getRepositoryToken(UserEntity),
);
});

describe("createUser - Data", () => {
it("應該會創建 一個使用者", async () => {
const test_data: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account1",
password: "Password@123",
};
const user = await authService.register(test_data);

expect(user).toBeDefined();
expect(user.statusCode).toEqual(HttpStatus.CREATED);
expect(user.message).toEqual("創建成功");
});
it("應該會發生 email、account 已被註冊衝突", async () => {
const createUserDto1: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account",
password: "Password@123",
};
try {
await authService.register(createUserDto1);
await authService.register(createUserDto1);
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.response).toEqual({
statusCode: 409,
message: ["email 已被註冊。", "account 已被註冊。"],
error: "Conflict",
});
}
});
it("應該會發生 email 已被註冊衝突", async () => {
const test_data1: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account1",
password: "Password@123",
};
const test_data2: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account2",
password: "Password@123",
};
const errors = await validate(test_data1);
expect(errors.length).toBe(0);
try {
await authService.register(test_data1);
await authService.register(test_data2);
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.response).toEqual({
statusCode: 409,
message: ["email 已被註冊。"],
error: "Conflict",
});
}
});
it("應該會發生 account 已被註冊衝突", async () => {
const test_data1: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account",
password: "Password@123",
};
const test_data2: CreateUserDto = {
email: "[email protected]",
name: "displayname",
account: "account",
password: "Password@123",
};
const errors = await validate(test_data1);
expect(errors.length).toBe(0);
try {
await authService.register(test_data1);
await authService.register(test_data2);
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.response).toEqual({
statusCode: 409,
message: ["account 已被註冊。"],
error: "Conflict",
});
}
});
});
afterEach(async () => {
if (userRepository && userRepository.clear) {
await userRepository.clear();
}
});
});
32 changes: 32 additions & 0 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ConflictException, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { CreateUserDto } from "src/user/dto/create-user.dto";
import { UserEntity } from "src/user/entities/user.entity";
import { UserService } from "src/user/user.service";
import { Repository } from "typeorm";

@Injectable()
export class AuthService {
constructor(
private userService: UserService,
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
) {}
async register(userDto: CreateUserDto) {
const existingUser = await this.userRepository.findOne({
where: [{ email: userDto.email }, { account: userDto.account }],
});

if (existingUser) {
const keys = ["email", "account"];
const conflictedAttributes = [];
keys.forEach(key => {
if (existingUser[key] === userDto[key]) {
conflictedAttributes.push(key + " 已被註冊。");
}
});
throw new ConflictException(conflictedAttributes);
}
return this.userService.create(userDto);
}
}
8 changes: 8 additions & 0 deletions src/config/data-source.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as dotenv from "dotenv";
import { UserEntity } from "src/user/entities/user.entity";
import { DataSource, DataSourceOptions } from "typeorm";

dotenv.config();
Expand All @@ -19,5 +20,12 @@ export const dataSourceOptions: DataSourceOptions = {
logging: false,
};

export const dataSourceJest: DataSourceOptions = {
type: "sqlite",
database: ":memory:",
entities: [UserEntity],
synchronize: true,
};

const dataSource = new DataSource(dataSourceOptions);
export default dataSource;
Loading