Skip to content

Commit

Permalink
feat: renamePortfolio procedure
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Oct 23, 2020
1 parent c141879 commit 9df368a
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 2 deletions.
16 changes: 15 additions & 1 deletion src/api/entities/NumberedPortfolio.ts
@@ -1,7 +1,7 @@
import BigNumber from 'bignumber.js';

import { Portfolio } from '~/api/entities';
import { deletePortfolio } from '~/api/procedures';
import { deletePortfolio, modifyNamePortfolio, ModifyNamePortfolioParams } from '~/api/procedures';
import { Context, TransactionQueue } from '~/base';

export interface UniqueIdentifiers {
Expand Down Expand Up @@ -49,4 +49,18 @@ export class NumberedPortfolio extends Portfolio {
} = this;
return deletePortfolio.prepare({ did, id }, this.context);
}

/**
* Rename portfolio
*/
public async modifyName(
args: ModifyNamePortfolioParams
): Promise<TransactionQueue<NumberedPortfolio>> {
const {
id,
owner: { did },
} = this;
const { name } = args;
return modifyNamePortfolio.prepare({ did, id, name }, this.context);
}
}
21 changes: 20 additions & 1 deletion src/api/entities/__tests__/NumberedPortfolio.ts
Expand Up @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { Entity, Identity, NumberedPortfolio } from '~/api/entities';
import { deletePortfolio } from '~/api/procedures';
import { deletePortfolio, modifyNamePortfolio } from '~/api/procedures';
import { Context, TransactionQueue } from '~/base';
import { dsMockUtils } from '~/testUtils/mocks';

Expand Down Expand Up @@ -70,4 +70,23 @@ describe('Numberedortfolio class', () => {
expect(queue).toBe(expectedQueue);
});
});

describe('method: modifyName', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const id = new BigNumber(1);
const did = 'someDid';
const name = 'newName';
const numberedPortfolio = new NumberedPortfolio({ id, did }, context);
const expectedQueue = ('someQueue' as unknown) as TransactionQueue<NumberedPortfolio>;

sinon
.stub(modifyNamePortfolio, 'prepare')
.withArgs({ id, did, name }, context)
.resolves(expectedQueue);

const queue = await numberedPortfolio.modifyName({ name });

expect(queue).toBe(expectedQueue);
});
});
});
170 changes: 170 additions & 0 deletions src/api/procedures/__tests__/modifyNamePortfolio.ts
@@ -0,0 +1,170 @@
import { Bytes, u64 } from '@polkadot/types';
import BigNumber from 'bignumber.js';
import { IdentityId } from 'polymesh-types/types';
import sinon from 'sinon';

import { Params, prepareModifyNamePortfolio } from '~/api/procedures/modifyNamePortfolio';
import { Context } from '~/base';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { NumberedPortfolio } from '~/types';
import { tuple } from '~/types/utils';
import * as utilsModule from '~/utils';

jest.mock(
'~/api/entities/NumberedPortfolio',
require('~/testUtils/mocks/entities').mockNumberedPortfolioModule(
'~/api/entities/NumberedPortfolio'
)
);

describe('modifyNamePortfolio procedure', () => {
const id = new BigNumber(1);
const did = 'someDid';
const identityId = dsMockUtils.createMockIdentityId(did);
const rawPortfolioNumber = dsMockUtils.createMockU64(id.toNumber());
let mockContext: Mocked<Context>;
let stringToIdentityIdStub: sinon.SinonStub<[string, Context], IdentityId>;
let numberToU64Stub: sinon.SinonStub<[number | BigNumber, Context], u64>;
let bytesToStringStub: sinon.SinonStub<[Bytes], string>;
let stringToBytesStub: sinon.SinonStub<[string, Context], Bytes>;

beforeAll(() => {
dsMockUtils.initMocks();
procedureMockUtils.initMocks();
entityMockUtils.initMocks();
stringToIdentityIdStub = sinon.stub(utilsModule, 'stringToIdentityId');
numberToU64Stub = sinon.stub(utilsModule, 'numberToU64');
bytesToStringStub = sinon.stub(utilsModule, 'bytesToString');
stringToBytesStub = sinon.stub(utilsModule, 'stringToBytes');
});

beforeEach(() => {
mockContext = dsMockUtils.getContextInstance();
stringToIdentityIdStub.withArgs(did, mockContext).returns(identityId);
numberToU64Stub.withArgs(id, mockContext).returns(rawPortfolioNumber);
entityMockUtils.configureMocks({
numberedPortfolioOptions: {
isOwned: true,
},
});
});

afterEach(() => {
entityMockUtils.reset();
procedureMockUtils.reset();
dsMockUtils.reset();
});

afterAll(() => {
entityMockUtils.cleanup();
procedureMockUtils.cleanup();
dsMockUtils.cleanup();
});

test('should throw an error if the portfolio does not exist', async () => {
entityMockUtils.configureMocks({
numberedPortfolioOptions: {
isOwned: false,
},
});
dsMockUtils.createQueryStub('portfolio', 'portfolios').returns(dsMockUtils.createMockBytes());
dsMockUtils.createQueryStub('portfolio', 'portfolios', {
entries: [],
});

const proc = procedureMockUtils.getInstance<Params, NumberedPortfolio>(mockContext);

return expect(
prepareModifyNamePortfolio.call(proc, {
id,
did,
name: 'newName',
})
).rejects.toThrow('You are not the owner of this Portfolio');
});

test('should throw an error if new name is the same name currently in the pportfolio', async () => {
const newName = 'newName';
const rawNewName = dsMockUtils.createMockBytes(newName);

bytesToStringStub.withArgs(rawNewName).returns(newName);
dsMockUtils.createQueryStub('portfolio', 'portfolios').returns(rawNewName);
dsMockUtils.createQueryStub('portfolio', 'portfolios', {
entries: [],
});

const proc = procedureMockUtils.getInstance<Params, NumberedPortfolio>(mockContext);

return expect(
prepareModifyNamePortfolio.call(proc, {
id,
did,
name: newName,
})
).rejects.toThrow('New name is the same as current name');
});

test('should throw an error if there is a portfolio with the new name', async () => {
const portfolioName = 'portfolioName';
const rawPortfolioName = dsMockUtils.createMockBytes(portfolioName);
const entriePortfolioName = 'someName';
const rawEntriePortfolioName = dsMockUtils.createMockBytes(entriePortfolioName);
const portfolioNames = [
{
name: entriePortfolioName,
},
];
bytesToStringStub.withArgs(rawPortfolioName).returns(portfolioName);
bytesToStringStub.withArgs(rawEntriePortfolioName).returns(entriePortfolioName);

const rawPortfolios = portfolioNames.map(({ name }) =>
tuple(dsMockUtils.createMockBytes(name))
);
const portfolioEntries = rawPortfolios.map(([name]) => tuple([], name));

dsMockUtils.createQueryStub('portfolio', 'portfolios').returns(rawPortfolioName);
dsMockUtils.createQueryStub('portfolio', 'portfolios', {
entries: [portfolioEntries[0]],
});

const proc = procedureMockUtils.getInstance<Params, NumberedPortfolio>(mockContext);

return expect(
prepareModifyNamePortfolio.call(proc, {
id,
did,
name: portfolioNames[0].name,
})
).rejects.toThrow('A portfolio with that name already exists');
});

test('should add a rename portfolio transaction to the queue', async () => {
const portfolioName = 'portfolioName';
const rawPortfolioName = dsMockUtils.createMockBytes(portfolioName);
const newName = 'newName';
const rawNewName = dsMockUtils.createMockBytes(newName);

bytesToStringStub.withArgs(rawPortfolioName).returns(portfolioName);
stringToBytesStub.returns(rawNewName);

dsMockUtils.createQueryStub('portfolio', 'portfolios').returns(rawPortfolioName);
dsMockUtils.createQueryStub('portfolio', 'portfolios', {
entries: [],
});

const transaction = dsMockUtils.createTxStub('portfolio', 'renamePortfolio');
const proc = procedureMockUtils.getInstance<Params, NumberedPortfolio>(mockContext);

const result = await prepareModifyNamePortfolio.call(proc, {
id,
did,
name: newName,
});

const addTransactionStub = procedureMockUtils.getAddTransactionStub();

sinon.assert.calledWith(addTransactionStub, transaction, {}, rawPortfolioNumber, rawNewName);
expect(result.id).toBe(id);
});
});
1 change: 1 addition & 0 deletions src/api/procedures/index.ts
Expand Up @@ -40,3 +40,4 @@ export { transferTokenOwnership, TransferTokenOwnershipParams } from './transfer
// export { voteOnProposal, VoteOnProposalParams } from './voteOnProposal';
export { removePrimaryIssuanceAgent } from './removePrimaryIssuanceAgent';
export { deletePortfolio } from './deletePortfolio';
export { modifyNamePortfolio, ModifyNamePortfolioParams } from './modifyNamePortfolio';
80 changes: 80 additions & 0 deletions src/api/procedures/modifyNamePortfolio.ts
@@ -0,0 +1,80 @@
import BigNumber from 'bignumber.js';

import { NumberedPortfolio } from '~/api/entities';
import { PolymeshError, Procedure } from '~/base';
import { ErrorCode } from '~/types';
import { bytesToString, numberToU64, stringToBytes, stringToIdentityId } from '~/utils';

export type ModifyNamePortfolioParams = { name: string };

/**
* @hidden
*/
export type Params = { did: string; id: BigNumber } & ModifyNamePortfolioParams;

/**
* @hidden
*/
export async function prepareModifyNamePortfolio(
this: Procedure<Params, NumberedPortfolio>,
args: Params
): Promise<NumberedPortfolio> {
const {
context: {
polymeshApi: {
query: { portfolio: queryPortfolio },
tx: { portfolio },
},
},
context,
} = this;

const { did, id, name: newName } = args;

const numberedPortfolio = new NumberedPortfolio({ did, id }, context);
const identityId = stringToIdentityId(did, context);
const rawPortfolioNumber = numberToU64(id, context);

const [isOwned, rawPortfolioName, rawPortfolios] = await Promise.all([
numberedPortfolio.isOwned(),
queryPortfolio.portfolios(identityId, rawPortfolioNumber),
queryPortfolio.portfolios.entries(identityId),
]);

if (!isOwned) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'You are not the owner of this Portfolio',
});
}

if (bytesToString(rawPortfolioName) === newName) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'New name is the same as current name',
});
}

const portfolioNames = rawPortfolios.map(([, name]) => bytesToString(name));

if (portfolioNames.includes(newName)) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'A portfolio with that name already exists',
});
}

this.addTransaction(
portfolio.renamePortfolio,
{},
rawPortfolioNumber,
stringToBytes(newName, context)
);

return new NumberedPortfolio({ did, id }, context);
}

/**
* @hidden
*/
export const modifyNamePortfolio = new Procedure(prepareModifyNamePortfolio);

0 comments on commit 9df368a

Please sign in to comment.