Skip to content

Commit cb0039b

Browse files
authored
Refactored MosaicService unit test (#510)
* Fixed #509 * Added mosaicHttp unit tests * Refactored mosaicHttp to use abstract caller * Add error trapping unit test * removed unused error catcher
1 parent 847c798 commit cb0039b

File tree

3 files changed

+259
-90
lines changed

3 files changed

+259
-90
lines changed

src/infrastructure/MosaicHttp.ts

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { from as observableFrom, Observable, throwError } from 'rxjs';
18-
import { catchError, map, mergeMap } from 'rxjs/operators';
19-
import { MosaicRoutesApi } from 'symbol-openapi-typescript-node-client';
17+
import { Observable, throwError } from 'rxjs';
18+
import { catchError, mergeMap } from 'rxjs/operators';
19+
import { MosaicRoutesApi, MosaicIds, AccountIds, MosaicInfoDTO, MosaicDTO } from 'symbol-openapi-typescript-node-client';
2020
import { Address } from '../model/account/Address';
2121
import { PublicAccount } from '../model/account/PublicAccount';
2222
import { MosaicFlags } from '../model/mosaic/MosaicFlags';
@@ -63,21 +63,9 @@ export class MosaicHttp extends Http implements MosaicRepository {
6363
*/
6464
public getMosaic(mosaicId: MosaicId): Observable<MosaicInfo> {
6565
return this.networkTypeObservable.pipe(
66-
mergeMap((networkType) => observableFrom(
67-
this.mosaicRoutesApi.getMosaic(mosaicId.toHex())).pipe(
68-
map(({body}) => new MosaicInfo(
69-
new MosaicId(body.mosaic.id),
70-
UInt64.fromNumericString(body.mosaic.supply),
71-
UInt64.fromNumericString(body.mosaic.startHeight),
72-
PublicAccount.createFromPublicKey(body.mosaic.ownerPublicKey, networkType),
73-
body.mosaic.revision,
74-
new MosaicFlags(body.mosaic.flags),
75-
body.mosaic.divisibility,
76-
UInt64.fromNumericString(body.mosaic.duration),
77-
)),
78-
catchError((error) => throwError(this.errorHandling(error))),
79-
)),
80-
);
66+
mergeMap((networkType) =>
67+
this.call(this.mosaicRoutesApi.getMosaic(mosaicId.toHex()), (body) => this.toMosaicInfo(body, networkType))),
68+
);
8169
}
8270

8371
/**
@@ -86,27 +74,11 @@ export class MosaicHttp extends Http implements MosaicRepository {
8674
* @returns Observable<MosaicInfo[]>
8775
*/
8876
public getMosaics(mosaicIds: MosaicId[]): Observable<MosaicInfo[]> {
89-
const mosaicIdsBody = {
90-
mosaicIds: mosaicIds.map((id) => id.toHex()),
91-
};
77+
const ids = new MosaicIds();
78+
ids.mosaicIds = mosaicIds.map((id) => id.toHex());
9279
return this.networkTypeObservable.pipe(
93-
mergeMap((networkType) => observableFrom(
94-
this.mosaicRoutesApi.getMosaics(mosaicIdsBody)).pipe(
95-
map(({body}) => body.map((mosaicInfoDTO) => {
96-
return new MosaicInfo(
97-
new MosaicId(mosaicInfoDTO.mosaic.id),
98-
UInt64.fromNumericString(mosaicInfoDTO.mosaic.supply),
99-
UInt64.fromNumericString(mosaicInfoDTO.mosaic.startHeight),
100-
PublicAccount.createFromPublicKey(mosaicInfoDTO.mosaic.ownerPublicKey, networkType),
101-
mosaicInfoDTO.mosaic.revision,
102-
new MosaicFlags(mosaicInfoDTO.mosaic.flags),
103-
mosaicInfoDTO.mosaic.divisibility,
104-
UInt64.fromNumericString(mosaicInfoDTO.mosaic.duration),
105-
);
106-
})),
107-
catchError((error) => throwError(this.errorHandling(error))),
108-
),
109-
),
80+
mergeMap((networkType) =>
81+
this.call(this.mosaicRoutesApi.getMosaics(ids), (body) => body.map((b) => this.toMosaicInfo(b, networkType)))),
11082
);
11183
}
11284

@@ -117,20 +89,9 @@ export class MosaicHttp extends Http implements MosaicRepository {
11789
*/
11890
public getMosaicsFromAccount(address: Address): Observable<MosaicInfo[]> {
11991
return this.networkTypeObservable.pipe(
120-
mergeMap((networkType) => observableFrom(
121-
this.mosaicRoutesApi.getMosaicsFromAccount(address.plain())).pipe(
122-
map(({body}) => body.mosaics.map((mosaicInfo) =>
123-
new MosaicInfo(
124-
new MosaicId(mosaicInfo.id),
125-
UInt64.fromNumericString(mosaicInfo.supply),
126-
UInt64.fromNumericString(mosaicInfo.startHeight),
127-
PublicAccount.createFromPublicKey(mosaicInfo.ownerPublicKey, networkType),
128-
mosaicInfo.revision,
129-
new MosaicFlags(mosaicInfo.flags),
130-
mosaicInfo.divisibility,
131-
UInt64.fromNumericString(mosaicInfo.duration)))),
132-
catchError((error) => throwError(this.errorHandling(error))),
133-
)),
92+
mergeMap((networkType) =>
93+
this.call(this.mosaicRoutesApi.getMosaicsFromAccount(address.plain()),
94+
(body) => body.mosaics.map((b) => this.toMosaicInfoFromMosaicDto(b, networkType)))),
13495
);
13596
}
13697

@@ -140,27 +101,50 @@ export class MosaicHttp extends Http implements MosaicRepository {
140101
* @param addresses Array of addresses
141102
*/
142103
public getMosaicsFromAccounts(addresses: Address[]): Observable<MosaicInfo[]> {
143-
const accountIdsBody = {
144-
addresses: addresses.map((address) => address.plain()),
145-
};
104+
const accountIds = new AccountIds();
105+
accountIds.addresses = addresses.map((address) => address.plain());
146106
return this.networkTypeObservable.pipe(
147-
mergeMap((networkType) => observableFrom(
148-
this.mosaicRoutesApi.getMosaicsFromAccounts(accountIdsBody)).pipe(
149-
map(({body}) => body.mosaics.map((mosaicInfoDTO) => {
150-
return new MosaicInfo(
151-
new MosaicId(mosaicInfoDTO.id),
152-
UInt64.fromNumericString(mosaicInfoDTO.supply),
153-
UInt64.fromNumericString(mosaicInfoDTO.startHeight),
154-
PublicAccount.createFromPublicKey(mosaicInfoDTO.ownerPublicKey, networkType),
155-
mosaicInfoDTO.revision,
156-
new MosaicFlags(mosaicInfoDTO.flags),
157-
mosaicInfoDTO.divisibility,
158-
UInt64.fromNumericString(mosaicInfoDTO.duration),
159-
);
160-
})),
161-
catchError((error) => throwError(this.errorHandling(error))),
162-
),
163-
),
107+
mergeMap((networkType) =>
108+
this.call(this.mosaicRoutesApi.getMosaicsFromAccounts(accountIds),
109+
(body) => body.mosaics.map((b) => this.toMosaicInfoFromMosaicDto(b, networkType)))),
110+
);
111+
}
112+
113+
/**
114+
* Maps MosaicInfoDTO to MosaicInfo
115+
*
116+
* @param mosaicInfo the dto object.
117+
* @returns the model object
118+
*/
119+
private toMosaicInfo(mosaicInfo: MosaicInfoDTO, networkType: NetworkType): MosaicInfo {
120+
return new MosaicInfo(
121+
new MosaicId(mosaicInfo.mosaic.id),
122+
UInt64.fromNumericString(mosaicInfo.mosaic.supply),
123+
UInt64.fromNumericString(mosaicInfo.mosaic.startHeight),
124+
PublicAccount.createFromPublicKey(mosaicInfo.mosaic.ownerPublicKey, networkType),
125+
mosaicInfo.mosaic.revision,
126+
new MosaicFlags(mosaicInfo.mosaic.flags),
127+
mosaicInfo.mosaic.divisibility,
128+
UInt64.fromNumericString(mosaicInfo.mosaic.duration),
129+
);
130+
}
131+
132+
/**
133+
* Maps MosaicDTO to MosaicInfo
134+
*
135+
* @param mosaicInfo the dto object.
136+
* @returns the model object
137+
*/
138+
private toMosaicInfoFromMosaicDto(mosaicInfo: MosaicDTO, networkType: NetworkType): MosaicInfo {
139+
return new MosaicInfo(
140+
new MosaicId(mosaicInfo.id),
141+
UInt64.fromNumericString(mosaicInfo.supply),
142+
UInt64.fromNumericString(mosaicInfo.startHeight),
143+
PublicAccount.createFromPublicKey(mosaicInfo.ownerPublicKey, networkType),
144+
mosaicInfo.revision,
145+
new MosaicFlags(mosaicInfo.flags),
146+
mosaicInfo.divisibility,
147+
UInt64.fromNumericString(mosaicInfo.duration),
164148
);
165149
}
166150
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright 2020 NEM
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { expect } from 'chai';
17+
import * as http from 'http';
18+
import {
19+
Mosaic,
20+
MosaicRoutesApi,
21+
MosaicInfoDTO,
22+
MosaicDTO,
23+
MosaicsInfoDTO,
24+
MosaicIds,
25+
} from 'symbol-openapi-typescript-node-client';
26+
import { instance, mock, reset, when, deepEqual } from 'ts-mockito';
27+
import { DtoMapping } from '../../src/core/utils/DtoMapping';
28+
import { MosaicRepository } from '../../src/infrastructure/MosaicRepository';
29+
import { MosaicHttp } from '../../src/infrastructure/MosaicHttp';
30+
import { NetworkType } from '../../src/model/network/NetworkType';
31+
import { MosaicId } from '../../src/model/mosaic/MosaicId';
32+
import { MosaicInfo } from '../../src/model/mosaic/MosaicInfo';
33+
import { PublicAccount } from '../../src/model/account/PublicAccount';
34+
import { AccountIds } from 'symbol-openapi-typescript-node-client';
35+
import { assert } from 'chai';
36+
37+
describe('MosaicHttp', () => {
38+
39+
const publicAccount =
40+
PublicAccount.createFromPublicKey('9801508C58666C746F471538E43002B85B1CD542F9874B2861183919BA8787B6', NetworkType.MIJIN_TEST);
41+
const address = publicAccount.address;
42+
const mosaicId = new MosaicId('941299B2B7E1291C');
43+
const mosaic = new Mosaic();
44+
mosaic.amount = '777';
45+
mosaic.id = mosaicId.toHex();
46+
47+
const mosaicDto = new MosaicDTO();
48+
mosaicDto.divisibility = 6;
49+
mosaicDto.duration = '10';
50+
mosaicDto.flags = 1;
51+
mosaicDto.id = mosaicId.toHex();
52+
mosaicDto.ownerAddress = address.encoded();
53+
mosaicDto.ownerPublicKey = publicAccount.publicKey;
54+
mosaicDto.revision = 0;
55+
mosaicDto.startHeight = '1';
56+
mosaicDto.supply = '100';
57+
58+
const mosaicInfoDto = new MosaicInfoDTO();
59+
const mosaicsInfoDto = new MosaicsInfoDTO();
60+
mosaicInfoDto.mosaic = mosaicDto;
61+
mosaicsInfoDto.mosaics = [mosaicDto];
62+
63+
const url = 'http://someHost';
64+
const response: http.IncomingMessage = mock();
65+
const mosaicRoutesApi: MosaicRoutesApi = mock();
66+
const mosaicRepository: MosaicRepository = DtoMapping.assign(new MosaicHttp(url, NetworkType.MIJIN_TEST),
67+
{mosaicRoutesApi: instance(mosaicRoutesApi)});
68+
69+
before(() => {
70+
reset(response);
71+
reset(mosaicRoutesApi);
72+
});
73+
74+
function assertMosaicInfo(mosaicInfo: MosaicInfo) {
75+
expect(mosaicInfo).to.be.not.null;
76+
expect(mosaicInfo.divisibility).to.be.equals(6);
77+
expect(mosaicInfo.duration.toString()).to.be.equals(mosaicDto.duration);
78+
expect(mosaicInfo.flags.getValue()).to.be.equals(mosaicDto.flags);
79+
expect(mosaicInfo.height.toString()).to.be.equals(mosaicDto.startHeight);
80+
expect(mosaicInfo.supply.toString()).to.be.equals(mosaicDto.supply);
81+
expect(mosaicInfo.owner.publicKey).to.be.equals(mosaicDto.ownerPublicKey);
82+
expect(mosaicInfo.owner.address.encoded()).to.be.equals(mosaicDto.ownerAddress);
83+
expect(mosaicInfo.revision).to.be.equals(mosaicDto.revision);
84+
expect(mosaicInfo.id.toHex()).to.be.equals(mosaicId.toHex());
85+
}
86+
87+
it('getMosaic', async () => {
88+
when(mosaicRoutesApi.getMosaic(mosaicId.toHex())).thenReturn(Promise.resolve({response, body: mosaicInfoDto}));
89+
const mosaicInfo = await mosaicRepository.getMosaic(mosaicId).toPromise();
90+
assertMosaicInfo(mosaicInfo);
91+
});
92+
93+
it('getMosaics', async () => {
94+
const mosaicIds = new MosaicIds();
95+
mosaicIds.mosaicIds = [mosaicId.toHex()];
96+
when(mosaicRoutesApi.getMosaics(deepEqual(mosaicIds))).thenReturn(Promise.resolve({response, body: [mosaicInfoDto]}));
97+
const mosaicInfos = await mosaicRepository.getMosaics([mosaicId]).toPromise();
98+
assertMosaicInfo(mosaicInfos[0]);
99+
});
100+
101+
it('getMosaicsFromAccount', async () => {
102+
when(mosaicRoutesApi.getMosaicsFromAccount(address.plain())).thenReturn(Promise.resolve({response, body: mosaicsInfoDto}));
103+
const mosaicsInfo = await mosaicRepository.getMosaicsFromAccount(address).toPromise();
104+
assertMosaicInfo(mosaicsInfo[0]);
105+
});
106+
107+
it('getMosaicsFromAccounts', async () => {
108+
const accountIds = new AccountIds();
109+
accountIds.addresses = [address.plain()];
110+
when(mosaicRoutesApi.getMosaicsFromAccounts(deepEqual(accountIds)))
111+
.thenReturn(Promise.resolve({response, body: mosaicsInfoDto}));
112+
const mosaicsInfo = await mosaicRepository.getMosaicsFromAccounts([address]).toPromise();
113+
assertMosaicInfo(mosaicsInfo[0]);
114+
});
115+
116+
it('getMosaic - Error', async () => {
117+
when(mosaicRoutesApi.getMosaic(mosaicId.toHex())).thenThrow(new Error('Mocked Error'));
118+
await mosaicRepository.getMosaic(mosaicId).toPromise().catch((error) =>
119+
expect(error).not.to.be.undefined);
120+
});
121+
122+
it('getMosaics - Error', async () => {
123+
const mosaicIds = new MosaicIds();
124+
mosaicIds.mosaicIds = [mosaicId.toHex()];
125+
when(mosaicRoutesApi.getMosaics(deepEqual(mosaicIds))).thenThrow(new Error('Mocked Error'));
126+
await mosaicRepository.getMosaics([mosaicId]).toPromise().catch((error) =>
127+
expect(error).not.to.be.undefined);
128+
});
129+
130+
it('getMosaicsFromAccount - Error', async () => {
131+
when(mosaicRoutesApi.getMosaicsFromAccount(address.plain())).thenThrow(new Error('Mocked Error'));
132+
await mosaicRepository.getMosaicsFromAccount(address).toPromise().catch((error) =>
133+
expect(error).not.to.be.undefined);
134+
});
135+
136+
it('getMosaicsFromAccounts - Error', async () => {
137+
const accountIds = new AccountIds();
138+
accountIds.addresses = [address.plain()];
139+
when(mosaicRoutesApi.getMosaicsFromAccounts(deepEqual(accountIds))).thenThrow(new Error('Mocked Error'));
140+
await mosaicRepository.getMosaicsFromAccounts([address]).toPromise().catch((error) =>
141+
expect(error).not.to.be.undefined);
142+
});
143+
});

0 commit comments

Comments
 (0)