Skip to content

Commit

Permalink
Refactor #204: mock repository와 controller로 분리하기 (#205)
Browse files Browse the repository at this point in the history
* feat(#191): playwright 설치하기

* feat(#191): playwright로 간단히 테스트해보기

* refactor(#191): mock 변경 사항 반영 및 불필요한 resolver 제거 및 resolver 함수명 통일하기

* refactor(#191): mock history 불필요한 함수 제거 및 DTO 적용하기

* refactor(#191): mock lotus 제목 검색 로직 추가하기

* refactor(#191): mocking 시에 페이지네이션 max 값도 계산하기

* refactor(#191): user에 대한 MockRepository 적용하기

* refactor(#191): 테스트를 위한 data-testid값을 부여

* fix(#191): lotus 삭제 시 오류 수정하기

* refactor(#191): app 레이어로 테스트 코드 이동 및 e2e 테스트 코드 작성완료

* refactor(#204): mock 코드를 controller와 repository로 분리하기

* refactor(#204): lotus 생성 mocking 시에 유저 레포지토리로 유저 데이터 관리하도록 변경하기

* refactor(#204): mocking 시에 findLotusDetail을 repository에서 controller로 변경하기

* fix(#204): fe ci 오류 해결하기
  • Loading branch information
naarang authored Dec 5, 2024
1 parent 9d8d4ab commit 7430c43
Show file tree
Hide file tree
Showing 13 changed files with 563 additions and 625 deletions.
27 changes: 2 additions & 25 deletions apps/frontend/src/app/mock/MockRepository/MockRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type Identifiable<T> = T & { id: string };

export class MockRepository<T> {
private _autoId = 0;
private memory: Identifiable<T>[] = [];
protected memory: Identifiable<T>[] = [];

private isMatch(owner: Partial<T>, target: Partial<T>): boolean {
for (const key in target) {
Expand All @@ -16,29 +16,11 @@ export class MockRepository<T> {
return true;
}

private isPartialMatch(owner: Partial<T>, target: Partial<T>): boolean {
for (const key in target) {
if (!Object.prototype.hasOwnProperty.call(owner, key)) return false;

const ownerValue = owner[key as keyof T];
const targetValue = target[key as keyof T];

if (typeof ownerValue === 'boolean' && ownerValue !== targetValue) {
return false;
}

if (typeof targetValue === 'string' && !(ownerValue as string)?.includes(targetValue)) {
return false;
}
}
return true;
}

private generateId() {
return String(this._autoId++);
}

private paginate(items: Identifiable<T>[], page: number, size: number) {
protected paginate(items: Identifiable<T>[], page: number, size: number) {
const start = (page - 1) * size;
const end = start + size;
const data = items.slice(start, end);
Expand Down Expand Up @@ -88,9 +70,4 @@ export class MockRepository<T> {

return data;
}

async search({ query, page = 1, size = 10 }: { query?: Partial<Identifiable<T>>; page?: number; size?: number }) {
const filtered = query ? this.memory.filter((item) => this.isPartialMatch(item, query)) : this.memory;
return this.paginate(filtered, page, size);
}
}
File renamed without changes.
93 changes: 93 additions & 0 deletions apps/frontend/src/app/mock/controller/historyController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { DefaultBodyType, HttpResponse, PathParams, StrictRequest } from 'msw';
import { historyRepository } from '@/app/mock/repository/historyRepository';

// Lotus History 목록 조회
export const getHistoryList = async ({
request,
params
}: {
request: StrictRequest<DefaultBodyType>;
params: PathParams;
}) => {
const { lotusId } = params;

const url = new URL(request.url);
const page = Number(url.searchParams.get('page')) || 1;

if (!lotusId) {
return new HttpResponse('Bad Request', {
status: 400,
headers: {
'Content-Type': 'text/plain'
}
});
}
const { data, maxPage: max } = await historyRepository.findMany({ page });
const list = data.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());

return HttpResponse.json({
list,
page: {
current: page,
max
}
});
};

// 코드 실행
interface PostCodeRunBody {
input?: string;
execFileName: string;
}

export const postCodeRun = async ({ request }: { request: StrictRequest<DefaultBodyType> }) => {
const body = (await request.json()) as PostCodeRunBody;

if (!body?.execFileName)
return new HttpResponse('Bad Request', {
status: 400,
headers: {
'Content-Type': 'text/plain'
}
});

const newHistory = await historyRepository.create({
filename: body.execFileName,
date: new Date().toISOString(),
status: 'PENDING',
input: body?.input ?? '',
output: ''
});

setTimeout(() => {
historyRepository.update(
{ id: newHistory.id },
{
status: 'SUCCESS',
output: `입력한 값: ${newHistory.input} `
}
);
}, 2000);

return HttpResponse.json({
status: newHistory.status
});
};

// 해당 히스토리 정보
export const getHistory = async ({ params }: { params: PathParams }) => {
const { lotusId, historyId } = params;

if (!lotusId || !historyId) {
return new HttpResponse('Bad Request', {
status: 400,
headers: {
'Content-Type': 'text/plain'
}
});
}

const history = await historyRepository.findOne({ id: historyId as string });

return HttpResponse.json(history);
};
129 changes: 129 additions & 0 deletions apps/frontend/src/app/mock/controller/lotusController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { DefaultBodyType, HttpResponse, PathParams, StrictRequest } from 'msw';
import { lotusMockFileData, lotusRepository } from '@/app/mock/repository/lotusRepository';
import { userRepository } from '@/app/mock/repository/userRepository';
import { LotusDto } from '@/feature/lotus';

const MOCK_UUID = 'mock-uuid';

// 사용자의 Lotus 목록 조회
export const getUserLotusList = async ({ request }: { request: StrictRequest<DefaultBodyType> }) => {
const authorization = request.headers.get('Authorization');

const [type, token] = authorization?.split(' ') || [];

if (token !== MOCK_UUID || type !== 'Bearer') {
return new HttpResponse('Unauthorized: Invalid or missing token', {
status: 401,
headers: {
'Content-Type': 'text/plain'
}
});
}

const url = new URL(request.url);
const page = Number(url.searchParams.get('page')) || 1;
const size = Number(url.searchParams.get('size')) || 5;

const { data: lotuses, maxPage: max } = await lotusRepository.findMany({ page, size });

return HttpResponse.json({
lotuses,
page: {
current: page,
max
}
});
};

// public lotus 목록 조회
export const getPublicLotusList = async ({ request }: { request: StrictRequest<DefaultBodyType> }) => {
const url = new URL(request.url);
const page = Number(url.searchParams.get('page')) || 1;
const size = Number(url.searchParams.get('size')) || 5;
const search = url.searchParams.get('search') || '';

const { data: lotuses, maxPage: max } = await lotusRepository.search({
query: { title: search, isPublic: true },
page,
size
});

return HttpResponse.json({
lotuses,
page: {
current: page,
max
}
});
};

// public lotus 상세 조회
export const getLotusDetail = async ({ params }: { params: Record<string, string> }) => {
const lotusId = params.lotusId;

const lotus = await lotusRepository.findOne({ id: lotusId });

return HttpResponse.json({ ...lotus, ...lotusMockFileData });
};

type CreateLotusDto = {
title: string;
isPublic: false;
tags: string[];
gistUuid: string;
};

//lotus 생성
export const postCreateLotus = async ({ request }: { request: StrictRequest<DefaultBodyType> }) => {
const body = (await request.json()) as CreateLotusDto;

const author = await userRepository.findOne({ id: '0' });

const lotus = await lotusRepository.create({
...body,
date: new Date().toISOString(),
author,
logo: '/image/exampleImage.jpeg',
link: 'https://devblog.com/articles/1000000001',
isPublic: false,
gistUrl: ''
});

return HttpResponse.json(lotus);
};

// lotus 수정
export const patchLotus = async ({
params,
request
}: {
params: PathParams;
request: StrictRequest<DefaultBodyType>;
}) => {
const { id } = params;

const body = (await request.json()) as Partial<LotusDto>;

if (!id || typeof id !== 'string') return HttpResponse.json({ message: 'id is required' });

const lotus = await lotusRepository.findOne({ id });

const updatedLotus = await lotusRepository.update(lotus, body);

return HttpResponse.json(updatedLotus);
};

// lotus 삭제
export const deleteLotus = async ({ params }: { params: PathParams }) => {
const { id } = params;

if (!id || typeof id !== 'string') return HttpResponse.json({ message: 'id is required' });

const lotus = await lotusRepository.findOne({
id
});

await lotusRepository.delete(lotus);

return HttpResponse.json(lotus);
};
Original file line number Diff line number Diff line change
@@ -1,25 +1,7 @@
import { DefaultBodyType, HttpResponse, StrictRequest } from 'msw';
import { MockRepository } from './MockRepository';
import { MOCK_CODE, MOCK_UUID, userRepository } from '@/app/mock/repository/userRepository';
import { UserDto } from '@/feature/user';

const MOCK_CODE = 'mock-code';
const MOCK_UUID = 'mock-uuid';

const userList = new MockRepository<Omit<UserDto, 'id'>>();

const insertUser = () => {
const userMock: UserDto = {
id: '1',
nickname: 'mockUser',
profile: '/image/exampleImage.jpeg',
gistUrl: 'https://github.com'
};

userList.create(userMock);
};

insertUser();

// github 사용자 기본 정보 조회 api
export const getUserInfo = async ({ request }: { request: StrictRequest<DefaultBodyType> }) => {
const authorization = request.headers.get('Authorization');
Expand All @@ -35,13 +17,12 @@ export const getUserInfo = async ({ request }: { request: StrictRequest<DefaultB
});
}

const user = await userList.findOne({ id: '0' });
const user = await userRepository.findOne({ id: '0' });

return HttpResponse.json(user);
};

// 사용자 프로필 수정 api - 일단 닉네임만 수정되도록

export const patchUserInfo = async ({ request }: { request: StrictRequest<DefaultBodyType> }) => {
const authorization = request.headers.get('Authorization');

Expand All @@ -57,9 +38,9 @@ export const patchUserInfo = async ({ request }: { request: StrictRequest<Defaul
try {
const body = (await request.json()) as Partial<UserDto>;

const user = await userList.findOne({ id: '0' });
const user = await userRepository.findOne({ id: '0' });

const updatedUser = await userList.update(user, body);
const updatedUser = await userRepository.update(user, body);

return HttpResponse.json(updatedUser);
} catch (error) {
Expand Down
19 changes: 11 additions & 8 deletions apps/frontend/src/app/mock/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { http } from 'msw';
import { getGistDetail, getUserGistList } from './gistResolvers';
import { getHistory, getHistoryList, getTagList, postCodeRun, postTag } from './historyResolvers';
import { getGistDetail, getUserGistList } from './controller/gistController';
import { getHistory, getHistoryList, postCodeRun } from './controller/historyController';
import {
deleteLotus,
getLotusDetail,
getPublicLotusList,
getUserLotusList,
patchLotus,
postCreateLotus
} from './lotusResolvers';
import { getLogin, getUserInfo, patchUserInfo } from './userResolvers';
} from './controller/lotusController';
import { getLogin, getUserInfo, patchUserInfo } from './controller/userController';

const apiUrl = (path: string) => `${import.meta.env.VITE_API_URL}${path}`;

Expand All @@ -19,6 +19,7 @@ export const handlers = [
http.patch(apiUrl(`/api/user`), patchUserInfo),
http.get(apiUrl(`/api/user/login/callback`), getLogin),
http.get(apiUrl(`/api/user/lotus`), getUserLotusList),
// gist
http.get(apiUrl(`/api/user/gist`), getUserGistList),
http.get(apiUrl(`/api/user/gist/:gistId`), getGistDetail),
// lotus
Expand All @@ -27,11 +28,13 @@ export const handlers = [
http.post(apiUrl(`/api/lotus`), postCreateLotus),
http.patch(apiUrl(`/api/lotus/:id`), patchLotus),
http.delete(apiUrl(`/api/lotus/:id`), deleteLotus),
http.get(apiUrl(`/api/lotus`), getPublicLotusList),
http.get(apiUrl(`/api/lotus/:lotusId`), getLotusDetail),
http.post(apiUrl(`/api/lotus`), postCreateLotus),
http.patch(apiUrl(`/api/lotus/:id`), patchLotus),
http.delete(apiUrl(`/api/lotus/:id`), deleteLotus),
// history
http.get(apiUrl(`/api/lotus/:lotusId/history`), getHistoryList),
http.post(apiUrl(`/api/lotus/:lotusId/history`), postCodeRun),
http.get(apiUrl(`/api/lotus/:lotusId/history/:historyId`), getHistory),
// tag
http.get(apiUrl(`/api/tag`), getTagList),
http.post(apiUrl(`/api/tag`), postTag)
http.get(apiUrl(`/api/lotus/:lotusId/history/:historyId`), getHistory)
];
Loading

0 comments on commit 7430c43

Please sign in to comment.