From 0c260e70cbd763e22b81dc3014586c13a8e511b2 Mon Sep 17 00:00:00 2001 From: Toms Date: Thu, 10 Oct 2024 00:13:37 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20make=20venueId=20optiona?= =?UTF-8?q?l=20for=20createInstruction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settlements.controller.spec.ts | 24 +++++++++ src/settlements/settlements.controller.ts | 42 ++++++++++++++- src/settlements/settlements.service.spec.ts | 52 ++++++++++++------- src/settlements/settlements.service.ts | 16 ++++-- src/test-utils/mocks.ts | 1 + 5 files changed, 112 insertions(+), 23 deletions(-) diff --git a/src/settlements/settlements.controller.spec.ts b/src/settlements/settlements.controller.spec.ts index e76a2f70..ea8ec517 100644 --- a/src/settlements/settlements.controller.spec.ts +++ b/src/settlements/settlements.controller.spec.ts @@ -9,10 +9,12 @@ import { Nft, TransferError, } from '@polymeshassociation/polymesh-sdk/types'; +import { when } from 'jest-when'; import { PaginatedResultsModel } from '~/common/models/paginated-results.model'; import { LegType } from '~/common/types'; import { createPortfolioIdentifierModel } from '~/portfolios/portfolios.util'; +import { CreateInstructionDto } from '~/settlements/dto/create-instruction.dto'; import { SettlementsController } from '~/settlements/settlements.controller'; import { SettlementsService } from '~/settlements/settlements.service'; import { processedTxResult, testValues } from '~/test-utils/consts'; @@ -322,4 +324,26 @@ describe('SettlementsController', () => { expect(result).toEqual(processedTxResult); }); }); + + describe('addInstruction', () => { + it('should create an instruction and return the data returned by the service', async () => { + const mockInstruction = new MockInstruction(); + + when(mockInstruction.getLegs).calledWith().mockResolvedValue({ data: [] }); + + const mockData = { + ...txResult, + result: mockInstruction, + }; + mockSettlementsService.createInstruction.mockResolvedValue(mockData); + + const result = await controller.addInstruction({} as CreateInstructionDto); + + expect(result).toEqual({ + ...processedTxResult, + instruction: mockInstruction, // in jest the @FromEntity decorator is not applied + legs: [], + }); + }); + }); }); diff --git a/src/settlements/settlements.controller.ts b/src/settlements/settlements.controller.ts index d5089ea4..31c26e5a 100644 --- a/src/settlements/settlements.controller.ts +++ b/src/settlements/settlements.controller.ts @@ -7,6 +7,7 @@ import { ApiQuery, ApiTags, } from '@nestjs/swagger'; +import { Instruction } from '@polymeshassociation/polymesh-sdk/types'; import { ApiArrayResponse } from '~/common/decorators/swagger'; import { IdParamsDto } from '~/common/dto/id-params.dto'; @@ -15,19 +16,21 @@ import { TransactionBaseDto } from '~/common/dto/transaction-base-dto'; import { PaginatedResultsModel } from '~/common/models/paginated-results.model'; import { ResultsModel } from '~/common/models/results.model'; import { TransactionQueueModel } from '~/common/models/transaction-queue.model'; -import { handleServiceResult, TransactionResponseModel } from '~/common/utils'; +import { handleServiceResult, TransactionResolver, TransactionResponseModel } from '~/common/utils'; import { PortfolioDto } from '~/portfolios/dto/portfolio.dto'; import { AffirmAsMediatorDto } from '~/settlements/dto/affirm-as-mediator.dto'; import { AffirmInstructionDto } from '~/settlements/dto/affirm-instruction.dto'; +import { CreateInstructionDto } from '~/settlements/dto/create-instruction.dto'; import { ExecuteInstructionDto } from '~/settlements/dto/execute-instruction.dto'; import { LegIdParamsDto } from '~/settlements/dto/leg-id-params.dto'; import { LegValidationParamsDto } from '~/settlements/dto/leg-validation-params.dto'; +import { CreatedInstructionModel } from '~/settlements/models/created-instruction.model'; import { InstructionModel } from '~/settlements/models/instruction.model'; import { InstructionAffirmationModel } from '~/settlements/models/instruction-affirmation.model'; import { OffChainAffirmationModel } from '~/settlements/models/off-chain-affirmation.model'; import { TransferBreakdownModel } from '~/settlements/models/transfer-breakdown.model'; import { SettlementsService } from '~/settlements/settlements.service'; -import { createInstructionModel } from '~/settlements/settlements.util'; +import { createInstructionModel, legsToLegModel } from '~/settlements/settlements.util'; @ApiTags('settlements') @Controller() @@ -384,4 +387,39 @@ export class SettlementsController { const result = await this.settlementsService.executeInstruction(id, body); return handleServiceResult(result); } + + @ApiTags('instructions') + @ApiOperation({ + summary: 'Create a new Instruction', + }) + @ApiOkResponse({ + description: 'The ID of the newly created Instruction', + type: CreatedInstructionModel, + }) + @Post('instructions/create') + public async addInstruction( + @Body() createInstructionDto: CreateInstructionDto + ): Promise { + const serviceResult = await this.settlementsService.createInstruction( + undefined, + createInstructionDto + ); + + const resolver: TransactionResolver = async ({ + result: instruction, + transactions, + details, + }) => { + const { data: legs } = await instruction.getLegs(); + + return new CreatedInstructionModel({ + instruction, + details, + transactions, + legs: legsToLegModel(legs), + }); + }; + + return handleServiceResult(serviceResult, resolver); + } } diff --git a/src/settlements/settlements.service.spec.ts b/src/settlements/settlements.service.spec.ts index d6d52395..93e311f7 100644 --- a/src/settlements/settlements.service.spec.ts +++ b/src/settlements/settlements.service.spec.ts @@ -19,6 +19,7 @@ import { POLYMESH_API } from '~/polymesh/polymesh.consts'; import { PolymeshModule } from '~/polymesh/polymesh.module'; import { PolymeshService } from '~/polymesh/polymesh.service'; import { PortfolioDto } from '~/portfolios/dto/portfolio.dto'; +import { CreateInstructionDto } from '~/settlements/dto/create-instruction.dto'; import { LegDto } from '~/settlements/dto/leg.dto'; import { OffChainAffirmationReceiptDto } from '~/settlements/dto/offchain-affirmation-receipt.dto'; import { OffChainLegDto } from '~/settlements/dto/offchain-leg.dto'; @@ -165,7 +166,7 @@ describe('SettlementsService', () => { describe('createInstruction', () => { it('should run an addInstruction procedure and return the queue data', async () => { const mockVenue = new MockVenue(); - + const venueId = new BigNumber(123); const transaction = { blockHash: '0x1', txHash: '0x2', @@ -182,6 +183,7 @@ describe('SettlementsService', () => { const findVenueSpy = jest.spyOn(service, 'findVenue'); // eslint-disable-next-line @typescript-eslint/no-explicit-any findVenueSpy.mockResolvedValue(mockVenue as any); + mockPolymeshApi.settlements.addInstruction.mockResolvedValue(mockTransaction); const onChainLeg = { type: LegType.onChain, @@ -208,30 +210,44 @@ describe('SettlementsService', () => { ...params, }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result = await service.createInstruction(new BigNumber(123), body as any); + let result = await service.createInstruction(venueId, body as CreateInstructionDto); + const expectedLegs = [ + { + from: 'fromDid', + to: { identity: 'toDid', id: new BigNumber(1) }, + amount: new BigNumber(100), + asset: 'FAKE_TICKER', + }, + { + from: '0x01', + to: '0x02', + offChainAmount: new BigNumber(100), + asset: 'OFF_CHAIN_TICKER', + }, + ]; expect(result).toEqual({ result: mockInstruction, transactions: [mockTransaction], }); expect(mockTransactionsService.submit).toHaveBeenCalledWith( - mockVenue.addInstruction, + mockPolymeshApi.settlements.addInstruction, { - legs: [ - { - from: 'fromDid', - to: { identity: 'toDid', id: new BigNumber(1) }, - amount: new BigNumber(100), - asset: 'FAKE_TICKER', - }, - { - from: '0x01', - to: '0x02', - offChainAmount: new BigNumber(100), - asset: 'OFF_CHAIN_TICKER', - }, - ], + legs: expectedLegs, + venueId, + }, + expect.objectContaining({ signer }) + ); + + result = await service.createInstruction(undefined, body as CreateInstructionDto); + expect(result).toEqual({ + result: mockInstruction, + transactions: [mockTransaction], + }); + expect(mockTransactionsService.submit).toHaveBeenCalledWith( + mockPolymeshApi.settlements.addInstruction, + { + legs: expectedLegs, }, expect.objectContaining({ signer }) ); diff --git a/src/settlements/settlements.service.ts b/src/settlements/settlements.service.ts index 4451d5e4..ed5866df 100644 --- a/src/settlements/settlements.service.ts +++ b/src/settlements/settlements.service.ts @@ -57,18 +57,28 @@ export class SettlementsService { } public async createInstruction( - venueId: BigNumber, + venueId: BigNumber | undefined, createInstructionDto: CreateInstructionDto ): ServiceReturn { const { options, args } = extractTxOptions(createInstructionDto); - const venue = await this.findVenue(venueId); + + const { + polymeshService: { + polymeshApi: { settlements }, + }, + } = this; + + if (venueId) { + await this.findVenue(venueId); // Check if venue exists + } const params = { ...args, legs: args.legs.map(leg => leg.toLeg()), + venueId, }; - return this.transactionsService.submit(venue.addInstruction, params, options); + return this.transactionsService.submit(settlements.addInstruction, params, options); } public async findVenuesByOwner(did: string): Promise { diff --git a/src/test-utils/mocks.ts b/src/test-utils/mocks.ts index 39e3920f..c54edf1b 100644 --- a/src/test-utils/mocks.ts +++ b/src/test-utils/mocks.ts @@ -120,6 +120,7 @@ export class MockPolymesh { getInstruction: jest.fn(), getVenue: jest.fn(), createVenue: jest.fn(), + addInstruction: jest.fn(), }; public claims = {