From b69ee2c968ce45d7145742048e463b40b30e54de Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 17:50:11 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EB=A7=A4=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order.service.spec.ts | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 BE/src/stock/order/stock-order.service.spec.ts diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts new file mode 100644 index 00000000..b5dc8c98 --- /dev/null +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -0,0 +1,125 @@ +import { Test } from '@nestjs/testing'; +import { BadRequestException } from '@nestjs/common'; +import { StockOrderService } from './stock-order.service'; +import { StockOrderRepository } from './stock-order.repository'; +import { StockPriceSocketService } from '../../stockSocket/stock-price-socket.service'; +import { UserStockRepository } from '../../asset/user-stock.repository'; +import { AssetRepository } from '../../asset/asset.repository'; +import { TradeType } from './enum/trade-type'; +import { StatusType } from './enum/status-type'; + +jest.mock('../../asset/asset.repository'); +jest.mock('./stock-order.repository'); + +describe('stock order test', () => { + let stockOrderService: StockOrderService; + let stockOrderRepository: StockOrderRepository; + let assetRepository: AssetRepository; + + beforeEach(async () => { + const mockStockOrderRepository = { + findBy: jest.fn(), + save: jest.fn(), + create: jest.fn(), + }; + const mockAssetRepository = { + findOneBy: jest.fn(), + }; + const mockStockPriceSocketService = { + subscribeByCode: jest.fn(), + }; + const mockUserStockRepository = {}; + + const module = await Test.createTestingModule({ + providers: [ + StockOrderService, + { provide: StockOrderRepository, useValue: mockStockOrderRepository }, + { provide: AssetRepository, useValue: mockAssetRepository }, + { + provide: StockPriceSocketService, + useValue: mockStockPriceSocketService, + }, + { provide: UserStockRepository, useValue: mockUserStockRepository }, + ], + }).compile(); + + stockOrderService = module.get(StockOrderService); + stockOrderRepository = module.get(StockOrderRepository); + assetRepository = module.get(AssetRepository); + }); + + it('충분한 자산을 가지고 특정 주식에 대해 매수를 요청할 경우, 요청이 DB에 정상적으로 등록된다.', async () => { + jest.spyOn(assetRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_balance: 0, + cash_balance: 1000, + total_asset: 1000, + total_profit: 0, + total_profit_rate: 0, + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([]); + + const createMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'create').mockImplementation(createMock); + + const saveMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'save').mockImplementation(saveMock); + + await stockOrderService.buy(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }); + + expect(createMock).toHaveBeenCalledWith({ + user_id: 1, + stock_code: '005930', + trade_type: TradeType.BUY, + amount: 1, + price: 1000, + status: StatusType.PENDING, + }); + expect(saveMock).toHaveBeenCalled(); + }); + + it('자산이 부족한 상태로 특정 주식에 대해 매수를 요청할 경우, BadRequest 예외가 발생한다.', async () => { + jest.spyOn(assetRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_balance: 0, + cash_balance: 1000, + total_asset: 1000, + total_profit: 0, + total_profit_rate: 0, + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.BUY, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }, + ]); + + const createMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'create').mockImplementation(createMock); + + const saveMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'save').mockImplementation(saveMock); + + await expect( + stockOrderService.buy(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }), + ).rejects.toThrow(BadRequestException); + }); +}); From acc7ae2c937171706ad5efbd9192b4ff653d833b Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 18:04:24 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EB=A7=A4=EB=8F=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order.service.spec.ts | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts index b5dc8c98..3f41eed7 100644 --- a/BE/src/stock/order/stock-order.service.spec.ts +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -15,6 +15,7 @@ describe('stock order test', () => { let stockOrderService: StockOrderService; let stockOrderRepository: StockOrderRepository; let assetRepository: AssetRepository; + let userStockRepository: UserStockRepository; beforeEach(async () => { const mockStockOrderRepository = { @@ -22,13 +23,9 @@ describe('stock order test', () => { save: jest.fn(), create: jest.fn(), }; - const mockAssetRepository = { - findOneBy: jest.fn(), - }; - const mockStockPriceSocketService = { - subscribeByCode: jest.fn(), - }; - const mockUserStockRepository = {}; + const mockAssetRepository = { findOneBy: jest.fn() }; + const mockStockPriceSocketService = { subscribeByCode: jest.fn() }; + const mockUserStockRepository = { findOneBy: jest.fn() }; const module = await Test.createTestingModule({ providers: [ @@ -46,6 +43,7 @@ describe('stock order test', () => { stockOrderService = module.get(StockOrderService); stockOrderRepository = module.get(StockOrderRepository); assetRepository = module.get(AssetRepository); + userStockRepository = module.get(UserStockRepository); }); it('충분한 자산을 가지고 특정 주식에 대해 매수를 요청할 경우, 요청이 DB에 정상적으로 등록된다.', async () => { @@ -108,14 +106,75 @@ describe('stock order test', () => { }, ]); + await expect( + stockOrderService.buy(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }), + ).rejects.toThrow(BadRequestException); + }); + + it('충분한 주식을 가지고 특정 주식에 대해 매도를 요청할 경우, 요청이 DB에 정상적으로 등록된다.', async () => { + jest.spyOn(userStockRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + quantity: 1, + avg_price: 1000, + last_updated: new Date(), + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([]); + const createMock = jest.fn(); jest.spyOn(stockOrderRepository, 'create').mockImplementation(createMock); const saveMock = jest.fn(); jest.spyOn(stockOrderRepository, 'save').mockImplementation(saveMock); + await stockOrderService.sell(1, { + stock_code: '005930', + price: 1000, + amount: 1, + }); + + expect(createMock).toHaveBeenCalledWith({ + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + }); + expect(saveMock).toHaveBeenCalled(); + }); + + it('주식이 부족한 상태로 특정 주식에 대해 매도를 요청할 경우, BadRequest 예외가 발생한다.', async () => { + jest.spyOn(userStockRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + quantity: 1, + avg_price: 1000, + last_updated: new Date(), + }); + + jest.spyOn(stockOrderRepository, 'findBy').mockResolvedValue([ + { + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }, + ]); + await expect( - stockOrderService.buy(1, { + stockOrderService.sell(1, { stock_code: '005930', price: 1000, amount: 1, From a8336aef18ad679c317acc1a5b8c9fdd0e9b46fd Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 18:12:51 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order.service.spec.ts | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts index 3f41eed7..e0be9cd2 100644 --- a/BE/src/stock/order/stock-order.service.spec.ts +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -1,5 +1,9 @@ import { Test } from '@nestjs/testing'; -import { BadRequestException } from '@nestjs/common'; +import { + BadRequestException, + ConflictException, + ForbiddenException, +} from '@nestjs/common'; import { StockOrderService } from './stock-order.service'; import { StockOrderRepository } from './stock-order.repository'; import { StockPriceSocketService } from '../../stockSocket/stock-price-socket.service'; @@ -22,9 +26,15 @@ describe('stock order test', () => { findBy: jest.fn(), save: jest.fn(), create: jest.fn(), + findOneBy: jest.fn(), + remove: jest.fn(), + existsBy: jest.fn(), }; const mockAssetRepository = { findOneBy: jest.fn() }; - const mockStockPriceSocketService = { subscribeByCode: jest.fn() }; + const mockStockPriceSocketService = { + subscribeByCode: jest.fn(), + unsubscribeByCode: jest.fn(), + }; const mockUserStockRepository = { findOneBy: jest.fn() }; const module = await Test.createTestingModule({ @@ -181,4 +191,64 @@ describe('stock order test', () => { }), ).rejects.toThrow(BadRequestException); }); + + it('사용자 본인의 미체결된 주문에 대해 취소를 요청할 경우, 해당 주문이 DB에서 삭제된다.', async () => { + jest.spyOn(stockOrderRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }); + + const removeMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'remove').mockImplementation(removeMock); + + await stockOrderService.cancel(1, 1); + + expect(removeMock).toHaveBeenCalled(); + }); + + it('사용자 본인의 주문이 아닌 주문에 대해 취소를 요청할 경우, Forbidden 예외가 발생한다.', async () => { + jest.spyOn(stockOrderRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 2, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.PENDING, + created_at: new Date(), + }); + + const removeMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'remove').mockImplementation(removeMock); + + await expect(stockOrderService.cancel(1, 1)).rejects.toThrow( + ForbiddenException, + ); + }); + + it('이미 체결된 주문에 대해 취소를 요청할 경우, Conflict 예외가 발생한다.', async () => { + jest.spyOn(stockOrderRepository, 'findOneBy').mockResolvedValue({ + id: 1, + user_id: 1, + stock_code: '005930', + trade_type: TradeType.SELL, + amount: 1, + price: 1000, + status: StatusType.COMPLETE, + created_at: new Date(), + }); + + const removeMock = jest.fn(); + jest.spyOn(stockOrderRepository, 'remove').mockImplementation(removeMock); + + await expect(stockOrderService.cancel(1, 1)).rejects.toThrow( + ConflictException, + ); + }); }); From f20dea2179ba421bffa851507fd8cce66e54a320 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Mon, 2 Dec 2024 19:15:39 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EB=AA=A8=ED=82=B9=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/order/stock-order.service.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/BE/src/stock/order/stock-order.service.spec.ts b/BE/src/stock/order/stock-order.service.spec.ts index e0be9cd2..78d0b2b6 100644 --- a/BE/src/stock/order/stock-order.service.spec.ts +++ b/BE/src/stock/order/stock-order.service.spec.ts @@ -12,9 +12,6 @@ import { AssetRepository } from '../../asset/asset.repository'; import { TradeType } from './enum/trade-type'; import { StatusType } from './enum/status-type'; -jest.mock('../../asset/asset.repository'); -jest.mock('./stock-order.repository'); - describe('stock order test', () => { let stockOrderService: StockOrderService; let stockOrderRepository: StockOrderRepository;