Skip to content

Commit

Permalink
feat: finish modifyToken procedure and tests with 100% coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Mar 16, 2020
1 parent 39c720c commit 25fb4f2
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 124 deletions.
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -66,7 +66,6 @@
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2",
"websocket": "^1.0.31"
},
"config": {
Expand Down
27 changes: 26 additions & 1 deletion src/api/entities/SecurityToken/__tests__/index.ts
@@ -1,6 +1,8 @@
import { Balance } from '@polkadot/types/interfaces';
import sinon from 'sinon';

import { Entity } from '~/base';
import { modifyToken } from '~/api/procedures';
import { Entity, TransactionQueue } from '~/base';
import { polkadotMockUtils } from '~/testUtils/mocks';
import { balanceToBigNumber } from '~/utils';

Expand Down Expand Up @@ -72,4 +74,27 @@ describe('SecurityToken class', () => {
expect(details.owner.did).toBe(owner);
});
});

describe('method: modify', () => {
test('should prepare the procedure with the correct arguments and context, and return the resulting transaction queue', async () => {
const ticker = 'TEST';
const context = polkadotMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);

const args = {
makeDivisible: true,
};

const expectedQueue = ('someQueue' as unknown) as TransactionQueue<SecurityToken>;

sinon
.stub(modifyToken, 'prepare')
.withArgs({ ticker, ...args }, context)
.resolves(expectedQueue);

const queue = await securityToken.modify(args);

expect(queue).toBe(expectedQueue);
});
});
});
13 changes: 3 additions & 10 deletions src/api/entities/SecurityToken/index.ts
Expand Up @@ -49,18 +49,11 @@ export class SecurityToken extends Entity<UniqueIdentifiers> {
/**
* Modify some properties of the Security Token
*
* @param args.makeDivisible - makes an indivisible token divisible. Only called by the token owner
* @param args.makeDivisible - makes an indivisible token divisible
*/
public async modify(
args: Omit<ModifyTokenParams, 'ticker'>
): Promise<TransactionQueue<SecurityToken>> {
// pedir details() de security token y obtener el owner y si ya es divisible.
/*
VALIDAR: si es divisible tirar error:
VALIDAR: si el usuario no es el owner, tirar error: You must be the owner of the token to make it divisible
*/
public modify(args: ModifyTokenParams): Promise<TransactionQueue<SecurityToken>> {
const { ticker } = this;
return modifyToken.prepare({ ...args, ticker }, this.context);
return modifyToken.prepare({ ticker, ...args }, this.context);
}

/**
Expand Down
139 changes: 139 additions & 0 deletions src/api/procedures/__tests__/modifyToken.ts
@@ -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);
});
});
2 changes: 1 addition & 1 deletion src/api/procedures/index.ts
@@ -1,2 +1,2 @@
export { reserveTicker, ReserveTickerParams } from './reserveTicker';
export { modifyToken, ModifyTokenParams } from '/modifyToken';
export { modifyToken, ModifyTokenParams } from './modifyToken';
106 changes: 29 additions & 77 deletions src/api/procedures/modifyToken.ts
@@ -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);

0 comments on commit 25fb4f2

Please sign in to comment.