Skip to content

Commit

Permalink
feat: trusted claim issuer entity
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Jul 17, 2020
1 parent 27a14a8 commit f2817fa
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 15 deletions.
20 changes: 14 additions & 6 deletions src/api/entities/SecurityToken/Compliance/TrustedClaimIssuers.ts
@@ -1,12 +1,12 @@
import { IdentityId } from 'polymesh-types/types';

import { Identity } from '~/api/entities';
import { setTokenTrustedClaimIssuers, SetTokenTrustedClaimIssuersParams } from '~/api/procedures';
import { Namespace, TransactionQueue } from '~/base';
import { SubCallback, UnsubCallback } from '~/types';
import { identityIdToString, stringToTicker } from '~/utils';

import { SecurityToken } from '../';
import { TrustedClaimIssuer } from '../../TrustedClaimIssuer';

/**
* Handles all Security Token Default Trusted Claim Issuers related functionality
Expand All @@ -32,11 +32,13 @@ export class TrustedClaimIssuers extends Namespace<SecurityToken> {
*
* @note can be subscribed to
*/
public get(): Promise<Identity[]>;
public get(callback: SubCallback<Identity[]>): Promise<UnsubCallback>;
public get(): Promise<TrustedClaimIssuer[]>;
public get(callback: SubCallback<TrustedClaimIssuer[]>): Promise<UnsubCallback>;

// eslint-disable-next-line require-jsdoc
public async get(callback?: SubCallback<Identity[]>): Promise<Identity[] | UnsubCallback> {
public async get(
callback?: SubCallback<TrustedClaimIssuer[]>
): Promise<TrustedClaimIssuer[] | UnsubCallback> {
const {
context: {
polymeshApi: {
Expand All @@ -49,8 +51,14 @@ export class TrustedClaimIssuers extends Namespace<SecurityToken> {

const rawTicker = stringToTicker(ticker, context);

const assembleResult = (issuers: IdentityId[]): Identity[] =>
issuers.map(claimIssuer => new Identity({ did: identityIdToString(claimIssuer) }, context));
const assembleResult = (issuers: IdentityId[]): TrustedClaimIssuer[] =>
issuers.map(
claimIssuer =>
new TrustedClaimIssuer(
{ claimIssuerDid: identityIdToString(claimIssuer), ticker },
context
)
);

if (callback) {
return complianceManager.trustedClaimIssuer(rawTicker, issuers => {
Expand Down
Expand Up @@ -2,7 +2,7 @@ import { IdentityId, Ticker } from 'polymesh-types/types';
import sinon from 'sinon';

import { SecurityToken } from '~/api/entities';
import { Identity } from '~/api/entities/Identity';
import { TrustedClaimIssuer } from '~/api/entities/TrustedClaimIssuer';
import { setTokenTrustedClaimIssuers } from '~/api/procedures';
import { Namespace, TransactionQueue } from '~/base';
import { Context } from '~/context';
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('TrustedClaimIssuers class', () => {
let context: Context;
let token: SecurityToken;
let expectedDids: string[];
let expectedIdentities: Identity[];
let expectedTrustedClaimIssuers: TrustedClaimIssuer[];
let claimIssuers: IdentityId[];

let trustedClaimIssuerStub: sinon.SinonStub;
Expand All @@ -84,11 +84,13 @@ describe('TrustedClaimIssuers class', () => {

expectedDids = ['someDid', 'otherDid', 'yetAnotherDid'];

expectedIdentities = [];
expectedTrustedClaimIssuers = [];
claimIssuers = [];

expectedDids.forEach(did => {
expectedIdentities.push(new Identity({ did }, context));
expectedTrustedClaimIssuers.push(
new TrustedClaimIssuer({ claimIssuerDid: did, ticker }, context)
);
claimIssuers.push(dsMockUtils.createMockIdentityId(did));
});

Expand All @@ -110,7 +112,7 @@ describe('TrustedClaimIssuers class', () => {

const result = await trustedClaimIssuers.get();

expect(result).toEqual(expectedIdentities);
expect(result).toEqual(expectedTrustedClaimIssuers);
});

test('should allow subscription', async () => {
Expand All @@ -126,7 +128,7 @@ describe('TrustedClaimIssuers class', () => {
const result = await trustedClaimIssuers.get(callback);

expect(result).toBe(unsubCallback);
sinon.assert.calledWithExactly(callback, expectedIdentities);
sinon.assert.calledWithExactly(callback, expectedTrustedClaimIssuers);
});
});
});
12 changes: 9 additions & 3 deletions src/api/entities/SecurityToken/__tests__/index.ts
Expand Up @@ -332,19 +332,25 @@ describe('SecurityToken class', () => {
test('should return the event identifier object of the token creation', async () => {
const ticker = 'SOMETICKER';
const blockId = 1234;
const blockDatetime = new Date('4/14/2020');
const eventIdx = 1;
const variables = {
moduleId: 'asset',
eventId: 'AssetCreated',
eventArg1: utilsModule.padString(ticker, MAX_TICKER_LENGTH),
};
const fakeResult = { blockNumber: blockId, eventIndex: eventIdx };
const fakeResult = { blockNumber: blockId, blockDatetime, eventIndex: eventIdx };
const context = dsMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);

dsMockUtils.createApolloQueryStub(eventByIndexedArgs(variables), {
// eslint-disable-next-line @typescript-eslint/camelcase
eventByIndexedArgs: { block_id: blockId, event_idx: eventIdx },
/* eslint-disable @typescript-eslint/camelcase */
eventByIndexedArgs: {
block_id: blockId,
block: { datetime: blockDatetime },
event_idx: eventIdx,
},
/* eslint-enable @typescript-eslint/camelcase */
});

const result = await securityToken.createdAt();
Expand Down
1 change: 1 addition & 0 deletions src/api/entities/SecurityToken/index.ts
Expand Up @@ -283,6 +283,7 @@ export class SecurityToken extends Entity<UniqueIdentifiers> {
/* eslint-disable @typescript-eslint/no-non-null-assertion */
return {
blockNumber: result.data.eventByIndexedArgs.block_id!,
blockDatetime: result.data.eventByIndexedArgs.block!.datetime!,
eventIndex: result.data.eventByIndexedArgs.event_idx!,
};
/* eslint-enabled @typescript-eslint/no-non-null-assertion */
Expand Down
100 changes: 100 additions & 0 deletions src/api/entities/TrustedClaimIssuer.ts
@@ -0,0 +1,100 @@
import { ApolloQueryResult } from 'apollo-client';

import { Identity } from '~/api/entities/Identity';
import { Entity, PolymeshError } from '~/base';
import { Context } from '~/context';
import { eventByIndexedArgs } from '~/middleware/queries';
import { Query } from '~/middleware/types';
import { Ensured, ErrorCode, EventIdentifier } from '~/types';
import { padString } from '~/utils';
import { MAX_TICKER_LENGTH } from '~/utils/constants';

export interface UniqueIdentifiers {
claimIssuerDid: string;
}

export interface Params {
ticker: string;
}

/**
* Represents a trusted claim issuer for a specific token in the Polymesh blockchain
*/
export class TrustedClaimIssuer extends Entity<UniqueIdentifiers> {
/**
* @hidden
* Check if a value is of type [[UniqueIdentifiers]]
*/
public static isUniqueIdentifiers(identifier: unknown): identifier is UniqueIdentifiers {
const { claimIssuerDid } = identifier as UniqueIdentifiers;

return typeof claimIssuerDid === 'string';
}

/**
* identity of the trusted claim issuer
*/
public identity: Identity;

/**
* ticker of the Security Token
*/
public ticker: string;

/**
* @hidden
*/
public constructor(args: UniqueIdentifiers & Params, context: Context) {
const { ticker, ...identifiers } = args;

super(identifiers, context);

const { claimIssuerDid } = identifiers;

this.identity = new Identity({ did: claimIssuerDid }, context);
this.ticker = ticker;
}

/**
* Retrieve the identifier data (block number and event index) of the event that was emitted when the token was created
*
* @note this data is harvested from the chain and stored in a database, so there is a possibility that the data is not ready by the time it is requested. In that case, `null` is returned
*/
public async addedAt(): Promise<EventIdentifier | null> {
const {
context: { middlewareApi },
ticker,
identity,
} = this;

let result: ApolloQueryResult<Ensured<Query, 'eventByIndexedArgs'>>;
try {
result = await middlewareApi.query<Ensured<Query, 'eventByIndexedArgs'>>(
eventByIndexedArgs({
moduleId: 'complianceManager',
eventId: 'TrustedDefaultClaimIssuerAdded',
eventArg1: padString(ticker, MAX_TICKER_LENGTH),
eventArg2: identity.did,
})
);
} catch (e) {
throw new PolymeshError({
code: ErrorCode.MiddlewareError,
message: `Error in middleware query: ${e.message}`,
});
}

if (result.data.eventByIndexedArgs) {
// TODO remove null check once types fixed
/* eslint-disable @typescript-eslint/no-non-null-assertion */
return {
blockNumber: result.data.eventByIndexedArgs.block_id!,
blockDatetime: result.data.eventByIndexedArgs.block!.datetime!,
eventIndex: result.data.eventByIndexedArgs.event_idx!,
};
/* eslint-enabled @typescript-eslint/no-non-null-assertion */
}

return null;
}
}
104 changes: 104 additions & 0 deletions src/api/entities/__tests__/TrustedClaimIssuer.ts
@@ -0,0 +1,104 @@
import { Entity } from '~/base';
import { Context } from '~/context';
import { eventByIndexedArgs } from '~/middleware/queries';
import { dsMockUtils } from '~/testUtils/mocks';
import * as utilsModule from '~/utils';
import { MAX_TICKER_LENGTH } from '~/utils/constants';

import { Identity } from '../Identity';
import { TrustedClaimIssuer } from '../TrustedClaimIssuer';

describe('TrustedClaimIssuer class', () => {
let context: Context;

beforeAll(() => {
dsMockUtils.initMocks();
});

beforeEach(() => {
context = dsMockUtils.getContextInstance();
});

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

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

test('should extend entity', () => {
expect(TrustedClaimIssuer.prototype instanceof Entity).toBe(true);
});

describe('constructor', () => {
test('should assign ticker and identity to instance', () => {
const did = 'someDid';
const ticker = 'SOMETICKER';
const identity = new Identity({ did }, context);
const trustedClaimIssuer = new TrustedClaimIssuer({ claimIssuerDid: did, ticker }, context);

expect(trustedClaimIssuer.ticker).toBe(ticker);
expect(trustedClaimIssuer.identity).toEqual(identity);
});
});

describe('method: isUniqueIdentifiers', () => {
test('should return true if the object conforms to the interface', () => {
expect(TrustedClaimIssuer.isUniqueIdentifiers({ claimIssuerDid: 'someDid' })).toBe(true);
expect(TrustedClaimIssuer.isUniqueIdentifiers({})).toBe(false);
expect(TrustedClaimIssuer.isUniqueIdentifiers({ claimIssuerDid: 1 })).toBe(false);
});
});

describe('method: addedAt', () => {
const claimIssuerDid = 'someDid';
const ticker = 'SOMETICKER';
const variables = {
moduleId: 'complianceManager',
eventId: 'TrustedDefaultClaimIssuerAdded',
eventArg1: utilsModule.padString(ticker, MAX_TICKER_LENGTH),
eventArg2: claimIssuerDid,
};

test('should return the event identifier object of the trusted claim issuer creation', async () => {
const blockId = 1234;
const blockDatetime = new Date('4/14/2020');
const eventIdx = 1;
const fakeResult = { blockNumber: blockId, blockDatetime, eventIndex: eventIdx };
const trustedClaimIssuer = new TrustedClaimIssuer({ claimIssuerDid, ticker }, context);

dsMockUtils.createApolloQueryStub(eventByIndexedArgs(variables), {
/* eslint-disable @typescript-eslint/camelcase */
eventByIndexedArgs: {
block_id: blockId,
block: { datetime: blockDatetime },
event_idx: eventIdx,
},
/* eslint-enable @typescript-eslint/camelcase */
});

const result = await trustedClaimIssuer.addedAt();

expect(result).toEqual(fakeResult);
});

test('should return null if the query result is empty', async () => {
const trustedClaimIssuer = new TrustedClaimIssuer({ claimIssuerDid, ticker }, context);

dsMockUtils.createApolloQueryStub(eventByIndexedArgs(variables), {});
const result = await trustedClaimIssuer.addedAt();
expect(result).toBeNull();
});

test('should throw if the middleware query fails', async () => {
const trustedClaimIssuer = new TrustedClaimIssuer({ claimIssuerDid, ticker }, context);

dsMockUtils.throwOnMiddlewareQuery();

return expect(trustedClaimIssuer.addedAt()).rejects.toThrow(
'Error in middleware query: Error'
);
});
});
});
3 changes: 3 additions & 0 deletions src/middleware/queries.ts
Expand Up @@ -75,6 +75,9 @@ export function eventByIndexedArgs(
block_id
event_idx
extrinsic_idx
block {
datetime
}
}
}
`;
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Expand Up @@ -330,6 +330,7 @@ export interface UiKeyring {

export interface EventIdentifier {
blockNumber: number;
blockDatetime: Date;
eventIndex: number;
}

Expand Down

0 comments on commit f2817fa

Please sign in to comment.