Initial API tests

Signed-off-by: James Taylor <jamest@uk.ibm.com>
This commit is contained in:
James Taylor 2021-08-02 14:09:11 +01:00
parent 6477333743
commit 862080773e
6 changed files with 826 additions and 25 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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,

View file

@ -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),
});
});
});
});

View file

@ -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) {

View file

@ -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;