mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
Initial API tests
Signed-off-by: James Taylor <jamest@uk.ibm.com>
This commit is contained in:
parent
6477333743
commit
862080773e
6 changed files with 826 additions and 25 deletions
|
|
@ -2934,6 +2934,31 @@
|
|||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"fengari": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz",
|
||||
"integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readline-sync": "^1.4.9",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"dependencies": {
|
||||
"sprintf-js": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fengari-interop": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.2.tgz",
|
||||
"integrity": "sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ==",
|
||||
"dev": true
|
||||
},
|
||||
"file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
|
@ -3366,6 +3391,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ioredis-mock": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ioredis-mock/-/ioredis-mock-5.6.0.tgz",
|
||||
"integrity": "sha512-Ow+tyKdijg/gA2gSEv7lq8dLp6bO7FnwDXbJ9as37NF23XNRGMLzBc7ITaqMydfrbTodWnLcE2lKEaBs7SBpyA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fengari": "^0.1.4",
|
||||
"fengari-interop": "^0.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
|
@ -4299,6 +4336,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jest-mock-extended": {
|
||||
"version": "2.0.2-beta2",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.2-beta2.tgz",
|
||||
"integrity": "sha512-56zcpgRPs3YxQP0ejcaaNFxUinPyRxQCbuk7GGORZqEbAFuQVXWAAtru2tI1N4qcLBoDWEJ/hwUxwbEGY5hdyw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ts-essentials": "^7.0.3"
|
||||
}
|
||||
},
|
||||
"jest-pnp-resolver": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
||||
|
|
@ -5246,6 +5292,12 @@
|
|||
"word-wrap": "^1.2.3"
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"p-each-series": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
|
||||
|
|
@ -5636,6 +5688,12 @@
|
|||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"readline-sync": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
|
||||
"integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==",
|
||||
"dev": true
|
||||
},
|
||||
"redis-commands": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||
|
|
@ -6198,6 +6256,15 @@
|
|||
"integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==",
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"tmpl": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
|
||||
|
|
@ -6258,6 +6325,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ts-essentials": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz",
|
||||
"integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "27.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.4.tgz",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@
|
|||
"eslint": "^7.29.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"ioredis-mock": "^5.6.0",
|
||||
"jest": "^27.0.6",
|
||||
"jest-mock-extended": "^2.0.2-beta2",
|
||||
"pino-pretty": "^5.0.2",
|
||||
"prettier": "^2.3.1",
|
||||
"rimraf": "^3.0.2",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,47 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { Contract, Network, Transaction, WalletStore } from 'fabric-network';
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
import * as fabricProtos from 'fabric-protos';
|
||||
|
||||
const mockAsset1 = {
|
||||
ID: 'asset1',
|
||||
Color: 'blue',
|
||||
Size: 5,
|
||||
Owner: 'Tomoko',
|
||||
AppraisedValue: 300,
|
||||
};
|
||||
const mockAsset1Buffer = Buffer.from(JSON.stringify(mockAsset1));
|
||||
|
||||
const mockAsset2 = {
|
||||
ID: 'asset2',
|
||||
Color: 'red',
|
||||
Size: 5,
|
||||
Owner: 'Brad',
|
||||
AppraisedValue: 400,
|
||||
};
|
||||
|
||||
const mockAllAssetsBuffer = Buffer.from(
|
||||
JSON.stringify([mockAsset1, mockAsset2])
|
||||
);
|
||||
|
||||
const mockBlockchainInfoProto = fabricProtos.common.BlockchainInfo.create();
|
||||
mockBlockchainInfoProto.height = 42;
|
||||
const mockBlockchainInfoBuffer = Buffer.from(
|
||||
fabricProtos.common.BlockchainInfo.encode(mockBlockchainInfoProto).finish()
|
||||
);
|
||||
|
||||
const processedTransactionProto =
|
||||
fabricProtos.protos.ProcessedTransaction.create();
|
||||
processedTransactionProto.validationCode =
|
||||
fabricProtos.protos.TxValidationCode.VALID;
|
||||
const processedTransactionBuffer = Buffer.from(
|
||||
fabricProtos.protos.ProcessedTransaction.encode(
|
||||
processedTransactionProto
|
||||
).finish()
|
||||
);
|
||||
|
||||
type FabricNetworkModule = jest.Mocked<typeof import('fabric-network')>;
|
||||
|
||||
|
|
@ -14,24 +54,124 @@ const {
|
|||
Wallets,
|
||||
}: FabricNetworkModule = jest.createMockFromModule('fabric-network');
|
||||
|
||||
const mockWalletStore = mock<WalletStore>();
|
||||
mocked(Wallets.newInMemoryWallet).mockResolvedValue(
|
||||
new Wallet({
|
||||
get: jest.fn(),
|
||||
list: jest.fn(),
|
||||
put: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
})
|
||||
new Wallet(mockWalletStore)
|
||||
);
|
||||
|
||||
mocked(Gateway.prototype.getNetwork).mockResolvedValue({
|
||||
getGateway: jest.fn(),
|
||||
getContract: jest.fn(),
|
||||
getChannel: jest.fn(),
|
||||
addCommitListener: jest.fn(),
|
||||
removeCommitListener: jest.fn(),
|
||||
addBlockListener: jest.fn(),
|
||||
removeBlockListener: jest.fn(),
|
||||
});
|
||||
const mockAssetExistsTransaction = mock<Transaction>();
|
||||
mockAssetExistsTransaction.evaluate
|
||||
.calledWith('asset1')
|
||||
.mockResolvedValue(Buffer.from('true'));
|
||||
mockAssetExistsTransaction.evaluate
|
||||
.calledWith('asset3')
|
||||
.mockResolvedValue(Buffer.from('false'));
|
||||
|
||||
const mockReadAssetTransaction = mock<Transaction>();
|
||||
mockReadAssetTransaction.evaluate
|
||||
.calledWith('asset1')
|
||||
.mockResolvedValue(mockAsset1Buffer);
|
||||
mockReadAssetTransaction.evaluate
|
||||
.calledWith('asset3')
|
||||
.mockRejectedValue(new Error('the asset asset3 does not exist'));
|
||||
|
||||
const mockCreateAssetTransaction = mock<Transaction>();
|
||||
mockCreateAssetTransaction.getTransactionId.mockReturnValue('txn1');
|
||||
mockCreateAssetTransaction.submit
|
||||
.calledWith('asset1')
|
||||
.mockRejectedValue(
|
||||
new Error(
|
||||
'No valid responses from any peers. Errors:\n peer=peer0.org1.example.com:7051, status=500, message=the asset asset1 already exists\n peer=peer0.org2.example.com:9051, status=500, message=the asset asset3 already exists'
|
||||
)
|
||||
);
|
||||
|
||||
// NOTE: only the second mocked GetAllAssets with return no assets
|
||||
// TODO find a better alternative so that test order does not matter
|
||||
const mockGetAllAssetsTransaction = mock<Transaction>();
|
||||
mockGetAllAssetsTransaction.evaluate
|
||||
.mockResolvedValueOnce(Buffer.from(''))
|
||||
.mockResolvedValueOnce(mockAllAssetsBuffer);
|
||||
|
||||
const mockUpdateAssetTransaction = mock<Transaction>();
|
||||
mockUpdateAssetTransaction.getTransactionId.mockReturnValue('txn1');
|
||||
mockUpdateAssetTransaction.submit
|
||||
.calledWith('asset3')
|
||||
.mockRejectedValue(
|
||||
new Error(
|
||||
'No valid responses from any peers. Errors:\n peer=peer0.org1.example.com:7051, status=500, message=the asset asset3 does not exist\n peer=peer0.org2.example.com:9051, status=500, message=the asset asset3 does not exist'
|
||||
)
|
||||
);
|
||||
|
||||
const mockTransferAssetTransaction = mock<Transaction>();
|
||||
mockTransferAssetTransaction.getTransactionId.mockReturnValue('txn1');
|
||||
mockTransferAssetTransaction.submit
|
||||
.calledWith('asset3')
|
||||
.mockRejectedValue(
|
||||
new Error(
|
||||
'No valid responses from any peers. Errors:\n peer=peer0.org1.example.com:7051, status=500, message=the asset asset3 does not exist\n peer=peer0.org2.example.com:9051, status=500, message=the asset asset3 does not exist'
|
||||
)
|
||||
);
|
||||
|
||||
const mockDeleteAssetTransaction = mock<Transaction>();
|
||||
mockDeleteAssetTransaction.getTransactionId.mockReturnValue('txn1');
|
||||
mockDeleteAssetTransaction.submit
|
||||
.calledWith('asset3')
|
||||
.mockRejectedValue(
|
||||
new Error(
|
||||
'No valid responses from any peers. Errors:\n peer=peer0.org1.example.com:7051, status=500, message=the asset asset3 does not exist\n peer=peer0.org2.example.com:9051, status=500, message=the asset asset3 does not exist'
|
||||
)
|
||||
);
|
||||
|
||||
const mockBasicContract = mock<Contract>();
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('AssetExists')
|
||||
.mockReturnValue(mockAssetExistsTransaction);
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('ReadAsset')
|
||||
.mockReturnValue(mockReadAssetTransaction);
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('CreateAsset')
|
||||
.mockReturnValue(mockCreateAssetTransaction);
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('GetAllAssets')
|
||||
.mockReturnValue(mockGetAllAssetsTransaction);
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('UpdateAsset')
|
||||
.mockReturnValue(mockUpdateAssetTransaction);
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('TransferAsset')
|
||||
.mockReturnValue(mockTransferAssetTransaction);
|
||||
mockBasicContract.createTransaction
|
||||
.calledWith('DeleteAsset')
|
||||
.mockReturnValue(mockDeleteAssetTransaction);
|
||||
|
||||
const mockGetTransactionByIDTransaction = mock<Transaction>();
|
||||
mockGetTransactionByIDTransaction.evaluate
|
||||
.calledWith('mychannel', 'txn1')
|
||||
.mockResolvedValue(processedTransactionBuffer);
|
||||
mockGetTransactionByIDTransaction.evaluate
|
||||
.calledWith('mychannel', 'txn3')
|
||||
.mockRejectedValue(
|
||||
new Error(
|
||||
'Failed to get transaction with id txn3, error Entry not found in index'
|
||||
)
|
||||
);
|
||||
|
||||
const mockSystemContract = mock<Contract>();
|
||||
mockSystemContract.evaluateTransaction
|
||||
.calledWith('GetChainInfo')
|
||||
.mockResolvedValue(mockBlockchainInfoBuffer);
|
||||
mockSystemContract.createTransaction
|
||||
.calledWith('GetTransactionByID')
|
||||
.mockReturnValue(mockGetTransactionByIDTransaction);
|
||||
|
||||
const mockNetwork = mock<Network>();
|
||||
mockNetwork.getContract.calledWith('basic').mockReturnValue(mockBasicContract);
|
||||
mockNetwork.getContract.calledWith('qscc').mockReturnValue(mockSystemContract);
|
||||
|
||||
mocked(Gateway.prototype.getNetwork).mockResolvedValue(mockNetwork);
|
||||
|
||||
// TODO remove this and use simpler mocks in fabric spec tests
|
||||
const getMockedNetwork = (getContract = jest.fn()) => {
|
||||
return mocked(Gateway.prototype.getNetwork).mockResolvedValue({
|
||||
getGateway: jest.fn(),
|
||||
|
|
@ -47,6 +187,7 @@ const getMockedNetwork = (getContract = jest.fn()) => {
|
|||
export {
|
||||
DefaultEventHandlerStrategies,
|
||||
DefaultQueryHandlerStrategies,
|
||||
Contract,
|
||||
Gateway,
|
||||
Wallets,
|
||||
getMockedNetwork,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
jest.mock('fabric-network');
|
||||
jest.mock('ioredis', () => require('ioredis-mock/jest'));
|
||||
|
||||
import { createServer } from '../server';
|
||||
import { Application } from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
jest.mock('fabric-network');
|
||||
jest.mock('ioredis');
|
||||
|
||||
// TODO add tests for server errors
|
||||
// TODO implement 405 Method Not Allowed where appropriate and add tests
|
||||
describe('Asset Transfer Besic REST API', () => {
|
||||
let app: Application;
|
||||
|
||||
|
|
@ -16,15 +18,593 @@ describe('Asset Transfer Besic REST API', () => {
|
|||
app = await createServer();
|
||||
});
|
||||
|
||||
describe('GET /ready', () => {
|
||||
it('should respond with success json', async () => {
|
||||
describe('/ready', () => {
|
||||
it('GET should respond with 200 OK json', async () => {
|
||||
const response = await request(app).get('/ready');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body.status).toEqual('OK');
|
||||
expect(response.body).toEqual({
|
||||
status: 'OK',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/live', () => {
|
||||
it('GET should respond with 200 OK json', async () => {
|
||||
const response = await request(app).get('/live');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'OK',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/api/assets', () => {
|
||||
it('GET should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/assets')
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should respond with an empty json array when there are no assets', async () => {
|
||||
// NOTE: only the first mocked GetAllAssets with return no assets
|
||||
// TODO find a better alternative so that test order does not matter
|
||||
const response = await request(app)
|
||||
.get('/api/assets')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual([]);
|
||||
});
|
||||
|
||||
it('GET should respond with json array of assets', async () => {
|
||||
// NOTE: only the second mocked GetAllAssets with return no assets
|
||||
// TODO find a better alternative so that test order does not matter
|
||||
const response = await request(app)
|
||||
.get('/api/assets')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual([
|
||||
{
|
||||
ID: 'asset1',
|
||||
Color: 'blue',
|
||||
Size: 5,
|
||||
Owner: 'Tomoko',
|
||||
AppraisedValue: 300,
|
||||
},
|
||||
{
|
||||
ID: 'asset2',
|
||||
Color: 'red',
|
||||
Size: 5,
|
||||
Owner: 'Brad',
|
||||
AppraisedValue: 400,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('POST should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/assets')
|
||||
.send({
|
||||
ID: 'asset6',
|
||||
Color: 'white',
|
||||
Size: 15,
|
||||
Owner: 'Michel',
|
||||
AppraisedValue: 800,
|
||||
})
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('POST should respond with 400 bad request json for invalid asset json', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/assets')
|
||||
.send({
|
||||
identifier: 'asset3',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Bad Request',
|
||||
reason: 'VALIDATION_ERROR',
|
||||
errors: [
|
||||
{
|
||||
location: 'body',
|
||||
msg: 'must be a string',
|
||||
param: 'id',
|
||||
},
|
||||
],
|
||||
message: 'Invalid request body',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('POST should respond with 202 accepted json', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/assets')
|
||||
.send({
|
||||
id: 'asset3',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(202);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Accepted',
|
||||
transactionId: 'txn1',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('POST should respond with 409 conflict json when asset already exists', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/assets')
|
||||
.send({
|
||||
id: 'asset1',
|
||||
color: 'blue',
|
||||
size: 5,
|
||||
owner: 'Tomoko',
|
||||
appraisedValue: 300,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(409);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Conflict',
|
||||
reason: 'ASSET_EXISTS',
|
||||
message: 'the asset asset1 already exists',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/api/assets/:id', () => {
|
||||
it('OPTIONS should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.options('/api/assets/asset1')
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('OPTIONS should respond with 404 not found json without the allow header when there is no asset with the specified ID', async () => {
|
||||
const response = await request(app)
|
||||
.options('/api/assets/asset3')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(404);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.header).not.toHaveProperty('allow');
|
||||
expect(response.body).toEqual({
|
||||
status: 'Not Found',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('OPTIONS should respond with 200 OK json with the allow header', async () => {
|
||||
const response = await request(app)
|
||||
.options('/api/assets/asset1')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.header).toHaveProperty(
|
||||
'allow',
|
||||
'DELETE,GET,OPTIONS,PATCH,PUT'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'OK',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/assets/asset1')
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should respond with 404 not found json when there is no asset with the specified ID', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/assets/asset3')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(404);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Not Found',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should respond with the asset json when the asset exists', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/assets/asset1')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
ID: 'asset1',
|
||||
Color: 'blue',
|
||||
Size: 5,
|
||||
Owner: 'Tomoko',
|
||||
AppraisedValue: 300,
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/assets/asset1')
|
||||
.send({
|
||||
id: 'asset3',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT should respond with 404 not found json when there is no asset with the specified ID', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/assets/asset3')
|
||||
.send({
|
||||
id: 'asset3',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(404);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Not Found',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT should respond with 400 bad request json when IDs do not match', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/assets/asset1')
|
||||
.send({
|
||||
id: 'asset2',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Bad Request',
|
||||
reason: 'ASSET_ID_MISMATCH',
|
||||
message: 'Asset IDs must match',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT should respond with 400 bad request json for invalid asset json', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/assets/asset1')
|
||||
.send({
|
||||
identifier: 'asset1',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Bad Request',
|
||||
reason: 'VALIDATION_ERROR',
|
||||
errors: [
|
||||
{
|
||||
location: 'body',
|
||||
msg: 'must be a string',
|
||||
param: 'id',
|
||||
},
|
||||
],
|
||||
message: 'Invalid request body',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT should respond with 202 accepted json', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/assets/asset1')
|
||||
.send({
|
||||
id: 'asset1',
|
||||
color: 'red',
|
||||
size: 5,
|
||||
owner: 'Brad',
|
||||
appraisedValue: 400,
|
||||
})
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(202);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Accepted',
|
||||
transactionId: 'txn1',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PATCH should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.patch('/api/assets/asset1')
|
||||
.send([{ op: 'replace', path: '/owner', value: 'Ashleigh' }])
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PATCH should respond with 404 not found json when there is no asset with the specified ID', async () => {
|
||||
const response = await request(app)
|
||||
.patch('/api/assets/asset3')
|
||||
.send([{ op: 'replace', path: '/owner', value: 'Ashleigh' }])
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(404);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Not Found',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PATCH should respond with 400 bad request json for invalid patch op/path', async () => {
|
||||
const response = await request(app)
|
||||
.patch('/api/assets/asset1')
|
||||
.send([{ op: 'replace', path: '/color', value: 'orange' }])
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Bad Request',
|
||||
reason: 'VALIDATION_ERROR',
|
||||
errors: [
|
||||
{
|
||||
location: 'body',
|
||||
msg: "path must be '/owner'",
|
||||
param: '[0].path',
|
||||
value: '/color',
|
||||
},
|
||||
],
|
||||
message: 'Invalid request body',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('PATCH should respond with 202 accepted json', async () => {
|
||||
const response = await request(app)
|
||||
.patch('/api/assets/asset1')
|
||||
.send([{ op: 'replace', path: '/owner', value: 'Ashleigh' }])
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(202);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Accepted',
|
||||
transactionId: 'txn1',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.delete('/api/assets/asset1')
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE should respond with 404 not found json when there is no asset with the specified ID', async () => {
|
||||
const response = await request(app)
|
||||
.delete('/api/assets/asset3')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(404);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Not Found',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE should respond with 202 accepted json', async () => {
|
||||
const response = await request(app)
|
||||
.delete('/api/assets/asset1')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(202);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Accepted',
|
||||
transactionId: 'txn1',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/api/transactions/:id', () => {
|
||||
it('GET should respond with 401 unauthorized json when an invalid API key is specified', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/transactions/txn1')
|
||||
.set('X-Api-Key', 'NOTTHERIGHTAPIKEY');
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
reason: 'NO_VALID_APIKEY',
|
||||
status: 'Unauthorized',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should respond with 404 not found json when there is no transaction with the specified ID', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/transactions/txn3')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(404);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Not Found',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should respond with json details for the specified transaction ID', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/transactions/txn1')
|
||||
.set('X-Api-Key', 'ORG1MOCKAPIKEY');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.header).toHaveProperty(
|
||||
'content-type',
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(response.body).toEqual({
|
||||
status: 'OK',
|
||||
progress: 'DONE',
|
||||
validationCode: 'VALID',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ assetsRouter.get('/', async (req: Request, res: Response) => {
|
|||
try {
|
||||
const contract: Contract = getContractForOrg(req).contract;
|
||||
const data = await evatuateTransaction(contract, 'GetAllAssets');
|
||||
const assets = JSON.parse(data.toString());
|
||||
let assets = [];
|
||||
if (data.length > 0) {
|
||||
assets = JSON.parse(data.toString());
|
||||
}
|
||||
|
||||
return res.status(OK).json(assets);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
TransactionError,
|
||||
TransactionNotFoundError,
|
||||
} from './errors';
|
||||
import fabproto6 from 'fabric-protos';
|
||||
import protos from 'fabric-protos';
|
||||
|
||||
export const getNetwork = async (gateway: Gateway): Promise<Network> => {
|
||||
const network = await gateway.getNetwork(config.channelName);
|
||||
|
|
@ -169,7 +169,9 @@ export const evatuateTransaction = async (
|
|||
const txnId = txn.getTransactionId();
|
||||
|
||||
try {
|
||||
return await txn.evaluate(...transactionArgs);
|
||||
const payload = await txn.evaluate(...transactionArgs);
|
||||
logger.debug({ payload }, 'Evaluate transaction response received');
|
||||
return payload;
|
||||
} catch (err) {
|
||||
throw handleError(txnId, err);
|
||||
}
|
||||
|
|
@ -338,7 +340,7 @@ export const getChainInfo = async (qscc: Contract): Promise<boolean> => {
|
|||
'GetChainInfo',
|
||||
config.channelName
|
||||
);
|
||||
const info = fabproto6.common.BlockchainInfo.decode(data);
|
||||
const info = protos.common.BlockchainInfo.decode(data);
|
||||
const blockHeight = info.height.toString();
|
||||
logger.info('Current block height: %s', blockHeight);
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue