diff --git a/.gitignore b/.gitignore index 3dca77e8..81c25d39 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,4 @@ lerna-debug.log* # IDE - VSCode .vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json apps/backend/db.sqlite diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e69de29b diff --git a/apps/backend/src/edge/edge.controller.spec.ts b/apps/backend/src/edge/edge.controller.spec.ts index ac8f8412..7f4b370a 100644 --- a/apps/backend/src/edge/edge.controller.spec.ts +++ b/apps/backend/src/edge/edge.controller.spec.ts @@ -1,9 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EdgeController } from './edge.controller'; import { EdgeService } from './edge.service'; +import { CreateEdgeDto } from './dtos/createEdge.dto'; +import { EdgeResponseMessage } from './edge.controller'; +import { EdgeNotFoundException } from '../exception/edge.exception'; +import { Edge } from './edge.entity'; +import { Node } from '../node/node.entity'; describe('EdgeController', () => { let controller: EdgeController; + let edgeService: EdgeService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -11,15 +17,104 @@ describe('EdgeController', () => { providers: [ { provide: EdgeService, - useValue: {}, + useValue: { + createEdge: jest.fn(), + deleteEdge: jest.fn(), + findEdges: jest.fn(), + }, }, ], }).compile(); controller = module.get(EdgeController); + edgeService = module.get(EdgeService); }); it('컨트롤러 클래스가 정상적으로 인스턴스화된다.', () => { expect(controller).toBeDefined(); }); + + describe('createEdge', () => { + it('엣지가 성공적으로 만들어진다', async () => { + const dto: CreateEdgeDto = { fromNode: 1, toNode: 3 }; + const expectedResponse = { + message: EdgeResponseMessage.EDGE_CREATED, + }; + + jest.spyOn(edgeService, 'createEdge').mockResolvedValue(undefined); + const result = await controller.createEdge(dto); + + expect(edgeService.createEdge).toHaveBeenCalledWith(dto); + expect(result).toEqual(expectedResponse); + }); + }); + + describe('deleteEdge', () => { + it('id에 해당하는 엣지를 찾아 삭제한다.', async () => { + const id = 2; + const expectedResponse = { + message: EdgeResponseMessage.EDGE_DELETED, + }; + + const result = await controller.deleteEdge(id); + + expect(edgeService.deleteEdge).toHaveBeenCalledWith(id); + expect(result).toEqual(expectedResponse); + }); + + it('id에 해당하는 엣지가 존재하지 않으면 NodeNotFoundException을 throw한다.', async () => { + jest + .spyOn(edgeService, 'deleteEdge') + .mockRejectedValue(new EdgeNotFoundException()); + + await expect(controller.deleteEdge(1)).rejects.toThrow( + EdgeNotFoundException, + ); + }); + }); + + describe('findEdges', () => { + it('모든 엣지 목록을 반환한다.', async () => { + const node3 = { + id: 3, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const node4 = { + id: 4, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const node5 = { + id: 5, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + + const expectedEdges = [ + { id: 1, fromNode: node3, toNode: node5 }, + { id: 2, fromNode: node3, toNode: node4 }, + ] as Edge[]; + node3.outgoingEdges = []; + + jest.spyOn(edgeService, 'findEdges').mockResolvedValue(expectedEdges); + + await expect(controller.findEdges()).resolves.toEqual({ + message: EdgeResponseMessage.EDGE_ALL_RETURNED, + edges: expectedEdges, + }); + }); + }); }); diff --git a/apps/backend/src/edge/edge.controller.ts b/apps/backend/src/edge/edge.controller.ts index d845a6c3..4ce7b75f 100644 --- a/apps/backend/src/edge/edge.controller.ts +++ b/apps/backend/src/edge/edge.controller.ts @@ -33,11 +33,11 @@ export class EdgeController { }) @Get('/') @HttpCode(HttpStatus.OK) - async getNodes() { - const nodes = await this.edgeService.findEdges(); + async findEdges() { + const edges = await this.edgeService.findEdges(); return { message: EdgeResponseMessage.EDGE_ALL_RETURNED, - nodes: nodes, + edges: edges, }; } @@ -56,7 +56,7 @@ export class EdgeController { @ApiOperation({ summary: '엣지를 삭제합니다.' }) @Delete('/:id') @HttpCode(HttpStatus.OK) - async deleteNode( + async deleteEdge( @Param('id', ParseIntPipe) id: number, ): Promise<{ message: string }> { await this.edgeService.deleteEdge(id); diff --git a/apps/backend/src/edge/edge.entity.ts b/apps/backend/src/edge/edge.entity.ts index d5049f91..50541f37 100644 --- a/apps/backend/src/edge/edge.entity.ts +++ b/apps/backend/src/edge/edge.entity.ts @@ -2,7 +2,7 @@ import { Entity, PrimaryGeneratedColumn, - Column, + // Column, ManyToOne, JoinColumn, } from 'typeorm'; @@ -21,9 +21,9 @@ export class Edge { @JoinColumn({ name: 'to_node_id' }) toNode: Node; - @Column({ nullable: true }) - type: string; + // @Column({ nullable: true }) + // type: string; - @Column({ nullable: true }) - color: string; + // @Column({ nullable: true }) + // color: string; } diff --git a/apps/backend/src/edge/edge.service.spec.ts b/apps/backend/src/edge/edge.service.spec.ts index 62ef670c..a08bed60 100644 --- a/apps/backend/src/edge/edge.service.spec.ts +++ b/apps/backend/src/edge/edge.service.spec.ts @@ -2,9 +2,15 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EdgeService } from './edge.service'; import { EdgeRepository } from './edge.repository'; import { NodeRepository } from '../node/node.repository'; +import { CreateEdgeDto } from './dtos/createEdge.dto'; +import { Edge } from './edge.entity'; +import { Node } from '../node/node.entity'; +import { EdgeNotFoundException } from '../exception/edge.exception'; describe('EdgeService', () => { let service: EdgeService; + let edgeRepository: jest.Mocked; + let nodeRepository: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -12,19 +18,175 @@ describe('EdgeService', () => { EdgeService, { provide: EdgeRepository, - useValue: {}, + useValue: { + create: jest.fn(), + save: jest.fn(), + delete: jest.fn(), + findOneBy: jest.fn(), + find: jest.fn(), + }, }, { provide: NodeRepository, - useValue: {}, + useValue: { + save: jest.fn(), + findOneBy: jest.fn(), + }, }, ], }).compile(); service = module.get(EdgeService); + edgeRepository = module.get(EdgeRepository); + nodeRepository = module.get(NodeRepository); }); it('서비스 클래스가 정상적으로 인스턴스화된다.', () => { expect(service).toBeDefined(); }); + + describe('createEdge', () => { + it('새로운 엣지를 만들어 노드와 노드를 연결하는 연결한다.', async () => { + const dto: CreateEdgeDto = { fromNode: 3, toNode: 5 }; + const fromNode = { + id: 3, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const toNode = { + id: 5, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const edge = { + id: 1, + fromNode: fromNode, + toNode: toNode, + } as Edge; + + jest + .spyOn(nodeRepository, 'findOneBy') + .mockResolvedValueOnce(fromNode) // 첫 번째 호출: fromNode + .mockResolvedValueOnce(toNode); // 두 번째 호출: toNode + jest.spyOn(edgeRepository, 'save').mockResolvedValue(edge); + + const result = await service.createEdge(dto); + + expect(result).toEqual(edge); + expect(edgeRepository.save).toHaveBeenCalledTimes(1); + expect(nodeRepository.findOneBy).toHaveBeenCalledTimes(2); + }); + }); + + describe('deleteEdge', () => { + it('엣지를 성공적으로 삭제한다.', async () => { + jest + .spyOn(edgeRepository, 'delete') + .mockResolvedValue({ affected: true } as any); + jest.spyOn(edgeRepository, 'findOneBy').mockResolvedValue(new Edge()); + + await service.deleteEdge(1); + + expect(edgeRepository.delete).toHaveBeenCalledWith(1); + }); + + it('삭제할 엣지가 존재하지 않으면 EdgeNotFoundException을 throw한다.', async () => { + jest + .spyOn(edgeRepository, 'delete') + .mockResolvedValue({ affected: false } as any); + await expect(service.deleteEdge(1)).rejects.toThrow( + EdgeNotFoundException, + ); + }); + }); + + describe('findEdges', () => { + it('존재하는 모든 엣지를 반환한다.', async () => { + const node3 = { + id: 3, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const node4 = { + id: 4, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const node5 = { + id: 5, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + const node7 = { + id: 7, + x: 0, + y: 0, + title: 'Node Title', + page: null, + outgoingEdges: [], + incomingEdges: [], + } as Node; + + const expectedEdgeList = [ + { + id: 1, + fromNode: node3, + toNode: node5, + } as Edge, + { + id: 2, + fromNode: node3, + toNode: node4, + } as Edge, + { + id: 3, + fromNode: node3, + toNode: node7, + } as Edge, + ]; + + jest.spyOn(edgeRepository, 'find').mockResolvedValue(expectedEdgeList); + const result = await service.findEdges(); + expect(result).toEqual(expectedEdgeList); + expect(edgeRepository.find).toHaveBeenCalledTimes(1); + expect(edgeRepository.find).toHaveBeenCalledWith({ + relations: ['fromNode', 'toNode'], + select: { + id: true, + fromNode: { + id: true, + }, + toNode: { + id: true, + }, + }, + }); + }); + + it('엣지가 없을 경우, 빈 배열을 던진다.', async () => { + jest.spyOn(edgeRepository, 'find').mockResolvedValue([]); + const result = await service.findEdges(); + expect(result).toEqual([]); + }); + }); });