generated from PolymeshAssociation/typescript-boilerplate
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: finish modifyToken procedure and tests with 100% coverage
- Loading branch information
1 parent
39c720c
commit 25fb4f2
Showing
11 changed files
with
235 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { Ticker } from 'polymesh-types/types'; | ||
import sinon from 'sinon'; | ||
|
||
import { SecurityToken } from '~/api/entities'; | ||
import { ModifyTokenInternalParams, prepareModifyToken } from '~/api/procedures/modifyToken'; | ||
import { Context } from '~/context'; | ||
import { entityMockUtils, polkadotMockUtils, procedureMockUtils } from '~/testUtils/mocks'; | ||
import { Mocked } from '~/testUtils/types'; | ||
import * as utilsModule from '~/utils'; | ||
|
||
jest.mock( | ||
'~/api/entities/SecurityToken', | ||
require('~/testUtils/mocks/entities').mockSecurityTokenModule('~/api/entities/SecurityToken') | ||
); | ||
|
||
describe('modifyToken procedure', () => { | ||
let mockContext: Mocked<Context>; | ||
let stringToTickerStub: sinon.SinonStub<[string, Context], Ticker>; | ||
let ticker: string; | ||
let rawTicker: Ticker; | ||
let procedureResult: SecurityToken; | ||
|
||
beforeAll(() => { | ||
polkadotMockUtils.initMocks(); | ||
procedureMockUtils.initMocks(); | ||
entityMockUtils.initMocks({ identityOptions: { did: 'someOtherDid' } }); | ||
stringToTickerStub = sinon.stub(utilsModule, 'stringToTicker'); | ||
ticker = 'someTicker'; | ||
rawTicker = polkadotMockUtils.createMockTicker(ticker); | ||
procedureResult = entityMockUtils.getSecurityTokenInstance(); | ||
}); | ||
|
||
let addTransactionStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
addTransactionStub = procedureMockUtils.getAddTransactionStub().returns([procedureResult]); | ||
mockContext = polkadotMockUtils.getContextInstance(); | ||
stringToTickerStub.withArgs(ticker, mockContext).returns(rawTicker); | ||
}); | ||
|
||
afterEach(() => { | ||
entityMockUtils.reset(); | ||
procedureMockUtils.reset(); | ||
polkadotMockUtils.reset(); | ||
}); | ||
|
||
afterAll(() => { | ||
entityMockUtils.cleanup(); | ||
procedureMockUtils.cleanup(); | ||
polkadotMockUtils.cleanup(); | ||
}); | ||
|
||
test('should throw an error if the user is not set any argument to modify the security token', () => { | ||
const proc = procedureMockUtils.getInstance<ModifyTokenInternalParams, SecurityToken>(); | ||
proc.context = mockContext; | ||
|
||
return expect( | ||
prepareModifyToken.call(proc, ({} as unknown) as ModifyTokenInternalParams) | ||
).rejects.toThrow('You should set at least one argument to perform this action'); | ||
}); | ||
|
||
test('should throw an error if the user is not the owner of the token', () => { | ||
entityMockUtils.getSecurityTokenDetailsStub().resolves({ | ||
owner: entityMockUtils.getIdentityInstance(), | ||
isDivisible: false, | ||
}); | ||
|
||
const proc = procedureMockUtils.getInstance<ModifyTokenInternalParams, SecurityToken>(); | ||
proc.context = mockContext; | ||
|
||
return expect( | ||
prepareModifyToken.call(proc, { | ||
ticker, | ||
makeDivisible: true, | ||
}) | ||
).rejects.toThrow('You must be the owner of the token to modify any property of it'); | ||
}); | ||
|
||
test('should throw an error if makeDivisible is set to true and the security token is already divisible', () => { | ||
entityMockUtils.initMocks({ identityOptions: { did: 'someDid' } }); | ||
|
||
entityMockUtils.getSecurityTokenDetailsStub().resolves({ | ||
owner: entityMockUtils.getIdentityInstance(), | ||
isDivisible: true, | ||
}); | ||
|
||
const proc = procedureMockUtils.getInstance<ModifyTokenInternalParams, SecurityToken>(); | ||
proc.context = mockContext; | ||
|
||
return expect( | ||
prepareModifyToken.call(proc, { | ||
ticker, | ||
makeDivisible: true, | ||
}) | ||
).rejects.toThrow('The Security Token is already divisible'); | ||
}); | ||
|
||
test('should throw an error if makeDivisible is set to false', () => { | ||
entityMockUtils.initMocks({ identityOptions: { did: 'someDid' } }); | ||
|
||
entityMockUtils.getSecurityTokenDetailsStub().resolves({ | ||
owner: entityMockUtils.getIdentityInstance(), | ||
isDivisible: false, | ||
}); | ||
|
||
const proc = procedureMockUtils.getInstance<ModifyTokenInternalParams, SecurityToken>(); | ||
proc.context = mockContext; | ||
|
||
return expect( | ||
prepareModifyToken.call(proc, { | ||
ticker, | ||
makeDivisible: false, | ||
}) | ||
).rejects.toThrow('You can not make the token indivisible'); | ||
}); | ||
|
||
test('should add a make divisble transaction to the queue', async () => { | ||
entityMockUtils.initMocks({ identityOptions: { did: 'someDid' } }); | ||
|
||
entityMockUtils.getSecurityTokenDetailsStub().resolves({ | ||
owner: entityMockUtils.getIdentityInstance(), | ||
isDivisible: false, | ||
}); | ||
|
||
const proc = procedureMockUtils.getInstance<ModifyTokenInternalParams, SecurityToken>(); | ||
proc.context = mockContext; | ||
|
||
const transaction = polkadotMockUtils.createTxStub('asset', 'makeDivisible'); | ||
|
||
const result = await prepareModifyToken.call(proc, { | ||
ticker, | ||
makeDivisible: true, | ||
}); | ||
|
||
sinon.assert.calledWith(addTransactionStub, transaction, sinon.match({}), rawTicker); | ||
|
||
expect(result.ticker).toBe(procedureResult.ticker); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export { reserveTicker, ReserveTickerParams } from './reserveTicker'; | ||
export { modifyToken, ModifyTokenParams } from '/modifyToken'; | ||
export { modifyToken, ModifyTokenParams } from './modifyToken'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,114 +1,66 @@ | ||
import { TxTags } from '@polymathnetwork/polkadot/api/types'; | ||
import { SecurityToken, Ticker } from '@polymathnetwork/polkadot/types/interfaces'; | ||
import { ISubmittableResult } from '@polymathnetwork/polkadot/types/types'; | ||
import { SecurityToken } from '~/api/entities'; | ||
import { PolymeshError, Procedure } from '~/base'; | ||
import { ErrorCode } from '~/types'; | ||
import { stringToTicker } from '~/utils'; | ||
|
||
import { TickerReservation } from '~/api/entities'; | ||
import { PolymeshError, PostTransactionValue, Procedure } from '~/base'; | ||
import { Context } from '~/context'; | ||
import { ErrorCode, TickerReservationStatus } from '~/types'; | ||
import { balanceToBigNumber, findEventRecord, stringToTicker, tickerToString } from '~/utils'; | ||
export type ModifyTokenParams = | ||
| { makeDivisible?: boolean; name: string } | ||
| { makeDivisible: boolean; name?: string }; | ||
|
||
export interface ModifyTokenParams { | ||
ticker: string; | ||
makeDivisible?: boolean; | ||
} | ||
export type ModifyTokenInternalParams = { ticker: string } & ModifyTokenParams; | ||
|
||
/** | ||
* @hidden | ||
*/ | ||
export async function prepareModifyToken( | ||
this: Procedure<ModifyTokenParams, SecurityToken>, | ||
args: ModifyTokenParams | ||
): Promise<PostTransactionValue<TickerReservation>> { | ||
this: Procedure<ModifyTokenInternalParams, SecurityToken>, | ||
args: ModifyTokenInternalParams | ||
): Promise<SecurityToken> { | ||
const { | ||
context: { | ||
polymeshApi: { query, tx }, | ||
polymeshApi: { tx }, | ||
}, | ||
context, | ||
} = this; | ||
const { ticker, makeDivisible } = args; | ||
|
||
if (!ticker && !makeDivisible) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: 'You should set at least one argument to perform this action', | ||
}); | ||
} | ||
|
||
const rawTicker = stringToTicker(ticker, context); | ||
|
||
const reservation = new TickerReservation({ ticker }, context); | ||
const securityToken = new SecurityToken({ ticker }, context); | ||
|
||
const [ | ||
rawFee, | ||
balance, | ||
{ max_ticker_length: rawMaxTickerLength }, | ||
{ ownerDid, expiryDate, status }, | ||
] = await Promise.all([ | ||
query.asset.tickerRegistrationFee(), | ||
context.accountBalance(), | ||
query.asset.tickerConfig(), | ||
reservation.details(), | ||
]); | ||
const { isDivisible, owner } = await securityToken.details(); | ||
|
||
if (status === TickerReservationStatus.TokenCreated) { | ||
if (owner.did !== context.currentIdentity?.did) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: `A Security Token with ticker "${ticker} already exists`, | ||
message: 'You must be the owner of the token to modify any property of it', | ||
}); | ||
} | ||
|
||
if (status === TickerReservationStatus.Reserved) { | ||
if (!extendPeriod) { | ||
const isPermanent = expiryDate === null; | ||
|
||
if (makeDivisible) { | ||
if (isDivisible) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: `Ticker "${ticker}" already reserved. The current reservation will ${ | ||
!isPermanent ? '' : 'not ' | ||
}expire${!isPermanent ? ` at ${expiryDate}` : ''}`, | ||
}); | ||
} else if (ownerDid !== context.currentIdentity?.did) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: 'You must be the owner of the ticker to extend its reservation period', | ||
message: 'The Security Token is already divisible', | ||
}); | ||
} | ||
} | ||
|
||
if (!extendPeriod) { | ||
const maxTickerLength = rawMaxTickerLength.toNumber(); | ||
|
||
if (ticker.length > maxTickerLength) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: `Ticker length cannot exceed ${maxTickerLength}`, | ||
}); | ||
} | ||
this.addTransaction(tx.asset.makeDivisible, {}, rawTicker); | ||
} else { | ||
if (status === TickerReservationStatus.Free) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: 'Ticker not reserved or the reservation has expired', | ||
}); | ||
} | ||
} | ||
|
||
const fee = balanceToBigNumber(rawFee); | ||
|
||
if (balance.lt(fee)) { | ||
throw new PolymeshError({ | ||
code: ErrorCode.ValidationError, | ||
message: `Not enough POLY balance to pay for ticker ${ | ||
extendPeriod ? 'period extension' : 'reservation' | ||
}`, | ||
message: 'You can not make the token indivisible', | ||
}); | ||
} | ||
|
||
const [newReservation] = this.addTransaction( | ||
tx.asset.registerTicker, | ||
{ | ||
tag: TxTags.asset.RegisterTicker, | ||
fee, | ||
resolvers: [createTickerReservationResolver(context)], | ||
}, | ||
rawTicker | ||
); | ||
|
||
return newReservation; | ||
return securityToken; | ||
} | ||
|
||
export const modifyToken = new Procedure(prepareModifyToken); |
Oops, something went wrong.