add js cc for the private data

Signed-off-by: abdou.chakhkhar <abdelmoula.chakhkhar@uit.ac.ma>
This commit is contained in:
abdou.chakhkhar 2022-04-29 15:25:02 +00:00
parent f2fbfa410f
commit 304e84ec96
8 changed files with 870 additions and 0 deletions

View file

@ -0,0 +1,5 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
module.exports = {
env: {
node: true,
mocha: true,
es6: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: "eslint:recommended",
rules: {
indent: ['error', 4],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-tabs': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed'],
'no-constant-condition': ["error", { "checkLoops": false }]
}
};

View file

@ -0,0 +1,15 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Report cache used by istanbul
.nyc_output
# Dependency directories
node_modules/
jspm_packages/
package-lock.json

View file

@ -0,0 +1,35 @@
[
{
"name": "assetCollection",
"policy": "OR('Org1MSP.member', 'Org2MSP.member')",
"requiredPeerCount": 1,
"maxPeerCount": 1,
"blockToLive":1000000,
"memberOnlyRead": true,
"memberOnlyWrite": true
},
{
"name": "Org1MSPPrivateCollection",
"policy": "OR('Org1MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 1,
"blockToLive":3,
"memberOnlyRead": true,
"memberOnlyWrite": false,
"endorsementPolicy": {
"signaturePolicy": "OR('Org1MSP.member')"
}
},
{
"name": "Org2MSPPrivateCollection",
"policy": "OR('Org2MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 1,
"blockToLive":3,
"memberOnlyRead": true,
"memberOnlyWrite": false,
"endorsementPolicy": {
"signaturePolicy": "OR('Org2MSP.member')"
}
}
]

View file

@ -0,0 +1,12 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const PrivateAssetTransfer = require('./lib/PrivateAssetTransfer');
module.exports.PrivateAssetTransfer = PrivateAssetTransfer;
module.exports.contracts = [PrivateAssetTransfer];

View file

@ -0,0 +1,443 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
// Deterministic JSON.stringify()
const stringify = require('json-stringify-deterministic');
const sortKeysRecursive = require('sort-keys-recursive');
const { Contract } = require('fabric-contract-api');
const assetCollection = "assetCollection"
const transferAgreementObjectType = "transferAgreement"
// PrivateData SmartContract
class PrivateAssetTransfer extends Contract {
// CreateAsset creates a new asset by placing the main asset details in the assetCollection
// that can be read by both organizations. The appraisal value is stored in the owners org specific collection.
async CreateAsset(ctx) {
// Get the new asset from transient map
const transientMap = await ctx.stub.getTransient();
// Asset properties are private, therefore they get passed in transient field, instead of func args
const transientAssetJSON = transientMap.get("asset_properties");
if (!transientAssetJSON) {
throw new Error('The asset was not found in the transient map input.');
}
let assetInput = JSON.parse(transientAssetJSON);
// inputs validation
if (!assetInput.objectType && assetInput.objectType === "") {
throw new Error('objectType field is required, it must be a non-empty string.');
}
if (!assetInput.assetID && assetInput.assetID === "") {
throw new Error('assetID field is required, it must be a non-empty string');
}
if (!assetInput.color && assetInput.color === "") {
throw new Error('color field is required, it must be a non-empty string.');
}
if (!assetInput.size && assetInput.size <= "") {
throw new Error('size field is required, it must be a positive integer.');
}
if (!assetInput.appraisedValue && assetInput.appraisedValue <= "") {
throw new Error('appraisedValue field is required, it must be a positive integer.');
}
// Check if asset already exists
const assetAsBytes = await ctx.stub.getPrivateData(assetCollection, assetInput.assetID);
if (assetAsBytes != '') {
throw new Error(`This asset (${assetInput.assetID}) already exists`);
}
// Get the ID of submitting client identity
const ClientID = await this.submittingClientIdentity(ctx);
// Verify that the client is submitting request to peer in their organization
// This is to ensure that a client from another org doesn't attempt to read or
// write private data from this peer.
await this.verifyClientOrgMatchesPeerOrg(ctx);
const asset = {
objectType: assetInput.objectType,
color: assetInput.color,
assetID: assetInput.assetID,
size: assetInput.size,
owner: ClientID
};
// Save asset to private data collection
// Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode
// Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz
console.log(`CreateAsset Put: collection ${assetCollection}, ID ${assetInput.assetID}, owner ${ClientID}`);
try {
await ctx.stub.putPrivateData(assetCollection, assetInput.assetID, Buffer.from(stringify(sortKeysRecursive(asset))))
} catch (error) {
throw Error('Failed to put asset into private data collecton.')
}
// Save asset details to collection visible to owning organization
const assetPrivateDetails = {
ID: assetInput.assetID,
AppraisedValue: assetInput.appraisedValue,
}
const orgCollection = await this.getCollectionName(ctx);
// Put asset appraised value into owners org specific private data collection
await ctx.stub.putPrivateData(orgCollection, assetInput.assetID, Buffer.from(stringify(sortKeysRecursive(assetPrivateDetails))));
}
// GetAssetByRange performs a range query based on the start and end keys provided. Range
// queries can be used to read data from private data collections, but can not be used in
// a transaction that also writes to private data.
async GetAssetByRange(ctx, startKey, endKey){
const response = await ctx.stub.getPrivateDataByRange(assetCollection, startKey, endKey);
const promiseOfIterator = response.iterator.response.results;
const allResults = [];
for await (const res of promiseOfIterator) {
allResults.push(res.resultBytes.toString());
}
return allResults;
}
// ReadAssetPrivateDetails reads the asset private details in organization specific collection
async ReadAssetPrivateDetails(ctx, collection, assetID){
const assetDetailsJSON = await ctx.stub.getPrivateData(collection, assetID);
if(!assetDetailsJSON.toString()){
throw Error('Failed to read asset details.')
}
return assetDetailsJSON.toString();
}
// TransferAsset transfers the asset to the new owner by setting a new owner ID
async TransferAsset (ctx){
const transientMap = await ctx.stub.getTransient();
// Asset properties are private, therefore they get passed in transient field
const transientTransferJSON = transientMap.get("asset_owner");
if (!transientTransferJSON) {
throw new Error(`The asset owner not found in the transient map`);
}
const assetTransferInput = {
ID: JSON.parse(transientTransferJSON).assetID,
BuyerMSP: JSON.parse(transientTransferJSON).buyerMSP
}
if (!assetTransferInput.ID && assetTransferInput.ID === "") {
throw new Error('The assetID field is required, it must be a non-empty string.');
}
if (!assetTransferInput.BuyerMSP && assetTransferInput.BuyerMSP === "") {
throw new Error('The buyerMSP field is required, it must be a non-empty string.');
}
// Read asset from the private data collection
let asset = await this.ReadAsset(ctx, assetTransferInput.ID);
if(!asset){
throw new Error(`${assetTransferInput.ID} does not exist.`);
}
// Verify that the client is submitting request to peer in their organization
await this.verifyClientOrgMatchesPeerOrg(ctx);
// Verify transfer details and transfer owner
await this.verifyAgreement(ctx, assetTransferInput.ID, asset.owner, assetTransferInput.BuyerMSP);
const transferAgreement = await this.ReadTransferAgreement(ctx, assetTransferInput.ID);
if(!transferAgreement){
throw new Error(`There has been no agreement related to this asset ${assetTransferInput.ID}.`);
}
if(!transferAgreement.BuyerID){
throw new Error(`The BuyerID was not found in TransferAgreement for ${assetTransferInput.ID}.`);
}
// Transfer asset in private data collection to new owner
console.log(asset);
asset = JSON.parse(asset);
asset.owner = transferAgreement.BuyerID;
console.log(asset);
await ctx.stub.putPrivateData(assetCollection, assetTransferInput.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
// Get collection name for this organization
const ownerCollection = await this.getCollectionName(ctx);
// Delete the asset appraised value from this organization's private data collection
await ctx.stub.deletePrivateData(ownerCollection, assetTransferInput.ID);
// Delete the transfer agreement from the asset collection
let transferAgreeKey = await ctx.stub.createCompositeKey(transferAgreementObjectType, [assetTransferInput.ID]);
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
}
// ReadAsset reads the information from collection
async ReadAsset(ctx, assetID) {
const assetJSON = await ctx.stub.getPrivateData(assetCollection, assetID);
const asset = assetJSON.toString();
//No Asset found, return empty response
if (!asset) {
throw new Error(`${assetID} does not exist in collection ${assetCollection}.`);
}
return asset;
}
// AgreeToTransfer is used by the potential buyer of the asset to agree to the
// asset value. The agreed to appraisal value is stored in the buying orgs
// org specifc collection, while the the buyer client ID is stored in the asset collection
// using a composite key
async AgreeToTransfer(ctx){
// Get ID of submitting client identity
const ClientID = await this.submittingClientIdentity(ctx);
// Value is private, therefore it gets passed in transient field
const transientMap = await ctx.stub.getTransient();
// Persist the JSON bytes as-is so that there is no risk of nondeterministic marshaling.
const transientAssetJSON = transientMap.get("asset_value");
if (!transientAssetJSON) {
throw new Error(`The asset was not found in the transient map input.`);
}
let valueJSON = JSON.parse(transientAssetJSON);
// Do some error checking since we get the chance
if (!valueJSON.assetID && valueJSON.assetID === "") {
throw new Error(`assetID field must be a non-empty string`);
}
if (valueJSON.appraisedValue <= 0) {
throw new Error(`AppraisedValue field must be a non-empty string`);
}
// Read asset from the private data collection
const asset = await this.ReadAsset(ctx, valueJSON.assetID);
if(!asset){
throw new Error(`${assetTransferInput.ID} does not exist.`);
}
// Verify that the client is submitting request to peer in their organization
await this.verifyClientOrgMatchesPeerOrg(ctx);
// Get collection name for this organization. Needs to be read by a member of the organization.
const orgCollection = await this.getCollectionName(ctx);
// Put agreed value in the org specifc private data collection
await ctx.stub.putPrivateData(orgCollection, valueJSON.assetID, Buffer.from(stringify(sortKeysRecursive(valueJSON))))
// Create agreeement that indicates which identity has agreed to purchase
// In a more realistic transfer scenario, a transfer agreement would be secured to ensure that it cannot
// be overwritten by another channel member.
let transferAgreeKey = await ctx.stub.createCompositeKey(transferAgreementObjectType, [valueJSON.assetID])
await ctx.stub.putPrivateData(assetCollection, transferAgreeKey, Buffer.from(stringify(sortKeysRecursive(ClientID))));
}
// DeleteTranferAgreement can be used by the buyer to withdraw a proposal from
// the asset collection and from his own collection.
async DeleteTransferAgreement(ctx){
const transientMap = await ctx.stub.getTransient();
if (!transientMap) {
throw new Error(`error getting transient`);
}
// Asset properties are private, therefore they get passed in transient field
const transientDeleteJSON = transientMap.get("agreement_delete");
if (!transientDeleteJSON) {
throw new Error(`Asset not found in the transient map input`);
}
let assetDelete = JSON.parse(transientDeleteJSON);
if (!assetDelete.assetID && assetDelete.assetID === "") {
throw new Error(`assetID field must be a non-empty string`);
}
// Verify that the client is submitting request to peer in their organization
await this.verifyClientOrgMatchesPeerOrg(ctx);
// Delete private details of agreement
const orgCollection = await this.getCollectionName(ctx);
let transferAgreeKey = await ctx.stub.createCompositeKey(transferAgreementObjectType, [assetDelete.assetID])
const valAsBytes = await ctx.stub.getPrivateData(assetCollection, transferAgreeKey);
if (!valAsBytes) {
throw new Error(`Asset's transfer_agreement does not exist.`);
}
await ctx.stub.deletePrivateData(orgCollection, assetDelete.assetID);
// Delete transfer agreement record
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
}
// ReadTransferAgreement gets the buyer's identity from the transfer agreement from collection
async ReadTransferAgreement(ctx, assetID){
const transferAgreeKey = await ctx.stub.createCompositeKey(transferAgreementObjectType, [assetID]);
const buyerIdentity = await ctx.stub.getPrivateData(assetCollection, transferAgreeKey);
if(!buyerIdentity.toString()) {
return Error(`TransferAgreement for ${assetID} does not exist.`)
}
const agreement = {
ID: assetID,
BuyerID: buyerIdentity.toString()
}
return agreement;
}
// DeleteAsset can be used by the owner of the asset to delete the asset
async DeleteAsset(ctx){
const transientMap = await ctx.stub.getTransient();
if (!transientMap) {
throw new Error(`error getting transient`);
}
// Asset properties are private, therefore they get passed in transient field
const transientDeleteJSON = transientMap.get("asset_delete");
if (!transientDeleteJSON) {
throw new Error(`asset not found in the transient map input`);
}
let assetDelete = JSON.parse(transientDeleteJSON);
if (!assetDelete.assetID && assetDelete.assetID === "") {
throw new Error(`assetID field must be a non-empty string`);
}
// Verify that the client is submitting request to peer in their organization
await this.verifyClientOrgMatchesPeerOrg(ctx);
let assetAsBytes = await ctx.stub.getPrivateData(assetCollection, assetDelete.assetID);
if (assetAsBytes == '') {
throw new Error(`The asset not found`);
}
const ownerCollection = await this.getCollectionName(ctx);
assetAsBytes = await ctx.stub.getPrivateData(ownerCollection, assetDelete.assetID);
if (assetAsBytes == '') {
throw new Error(`The asset not found in owner s private collection.`);
}
// delete the asset from state
await ctx.stub.deletePrivateData(assetCollection, assetDelete.assetID)
// Finally, delete private details of asset
await ctx.stub.deletePrivateData(ownerCollection, assetDelete.assetID)
}
// submittingClientIdentity is an internal function to get client identity who submit the transaction.
async submittingClientIdentity(ctx){
const ClientID = await ctx.clientIdentity.getID();
if (!ClientID && ClientID === '') {
throw new Error(`Failed to read clientID`);
}
return ClientID;
}
// verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id.
async verifyClientOrgMatchesPeerOrg(ctx){
const ClientMSPID = await ctx.clientIdentity.getMSPID();
if (!ClientMSPID && ClientMSPID === '') {
throw new Error("Failed getting the client's MSPID.");
}
const peerMSPID = await ctx.stub.getMspID();
if (!peerMSPID && peerMSPID === '') {
throw new Error("Failed getting the peer's MSPID.");
}
if (ClientMSPID !== peerMSPID) {
throw new Error(`Client from org ${ClientMSPID} is not authorized to read or write private data from an org ${peerMSPID} peer.`);
}
}
// getCollectionName is an internal helper function to get collection of submitting client identity.
async getCollectionName(ctx){
const ClientMSPID = await ctx.clientIdentity.getMSPID();
if (!ClientMSPID && ClientMSPID === '') {
throw new Error("Failed getting the client's MSPID.");
}
// Create the collection name
const orgCollection = ClientMSPID + "PrivateCollection";
return orgCollection;
}
// verifyAgreement is an internal helper function used by TransferAsset to verify
// that the transfer is being initiated by the owner and that the buyer has agreed
// to the same appraisal value as the owner
async verifyAgreement(ctx, assetID, owner, buyerMSP){
// Check 1: verify that the transfer is being initiatied by the owner
// Get ID of submitting client identity
const ClientID = await this.submittingClientIdentity(ctx);
if (ClientID !== owner) {
return Error("Error: Submitting client identity does not own the asset.")
}
// Check 2: verify that the buyer has agreed to the appraised value
// Get collection names
const collectionOwner = await this.getCollectionName(ctx); // get owner collection from caller identity
const collectionBuyer = buyerMSP + "PrivateCollection"; // get buyers collection
// Get hash of owners agreed to value
const ownerAppraisedValueHash = await ctx.stub.getPrivateDataHash(collectionOwner, assetID);
if (!ownerAppraisedValueHash) {
throw Error(`Hash of appraised value for ${assetID} does not exist in collection ${collectionOwner}.`)
}
// Get hash of buyers agreed to value
const buyerAppraisedValueHash = await ctx.stub.getPrivateDataHash(collectionBuyer, assetID);
if (!buyerAppraisedValueHash) {
throw Error(`Hash of appraised value for ${assetID} does not exist in collection ${collectionOwner}.`)
}
console.log("collectionOwner", collectionOwner);
console.log("collectionBuyer", collectionBuyer);
console.log("ownerAppraisedValueHash", ownerAppraisedValueHash);
console.log("buyerAppraisedValueHash", buyerAppraisedValueHash);
// Verify that the two hashes match
if (ownerAppraisedValueHash !== buyerAppraisedValueHash) {
throw new Error(`Hash for the appraised value for owner ${ownerAppraisedValueHash} does not match the value for seller which is ${buyerAppraisedValueHash}.`);
}
}
}
module.exports = PrivateAssetTransfer;

View file

@ -0,0 +1,53 @@
{
"name": "asset-transfer-basic",
"version": "1.0.0",
"description": "Asset-Transfer-Basic contract implemented in JavaScript",
"main": "index.js",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"scripts": {
"lint": "eslint *.js */**.js",
"pretest": "npm run lint",
"test": "nyc mocha --recursive",
"start": "fabric-chaincode-node start"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "^2.0.0",
"fabric-shim": "^2.0.0",
"json-stringify-deterministic": "^1.0.1",
"node-forge": "^1.3.1",
"openssl-nodejs": "^1.0.5",
"sort-keys-recursive": "^2.1.2"
},
"devDependencies": {
"chai": "^4.1.2",
"eslint": "^4.19.1",
"mocha": "^8.0.1",
"nyc": "^14.1.1",
"sinon": "^6.0.0",
"sinon-chai": "^3.2.0"
},
"nyc": {
"exclude": [
"coverage/**",
"test/**",
"index.js",
".eslintrc.js"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}

View file

@ -0,0 +1,268 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub } = require('fabric-shim');
const AssetTransfer = require('../lib/assetTransfer.js');
let assert = sinon.assert;
chai.use(sinonChai);
describe('Asset Transfer Basic Tests', () => {
let transactionContext, chaincodeStub, asset;
beforeEach(() => {
transactionContext = new Context();
chaincodeStub = sinon.createStubInstance(ChaincodeStub);
transactionContext.setChaincodeStub(chaincodeStub);
chaincodeStub.putState.callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = value;
});
chaincodeStub.getState.callsFake(async (key) => {
let ret;
if (chaincodeStub.states) {
ret = chaincodeStub.states[key];
}
return Promise.resolve(ret);
});
chaincodeStub.deleteState.callsFake(async (key) => {
if (chaincodeStub.states) {
delete chaincodeStub.states[key];
}
return Promise.resolve(key);
});
chaincodeStub.getStateByRange.callsFake(async () => {
function* internalGetStateByRange() {
if (chaincodeStub.states) {
// Shallow copy
const copied = Object.assign({}, chaincodeStub.states);
for (let key in copied) {
yield {value: copied[key]};
}
}
}
return Promise.resolve(internalGetStateByRange());
});
asset = {
ID: 'asset1',
Color: 'blue',
Size: 5,
Owner: 'Tomoko',
AppraisedValue: 300,
};
});
describe('Test InitLedger', () => {
it('should return error on InitLedger', async () => {
chaincodeStub.putState.rejects('failed inserting key');
let assetTransfer = new AssetTransfer();
try {
await assetTransfer.InitLedger(transactionContext);
assert.fail('InitLedger should have failed');
} catch (err) {
expect(err.name).to.equal('failed inserting key');
}
});
it('should return success on InitLedger', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.InitLedger(transactionContext);
let ret = JSON.parse((await chaincodeStub.getState('asset1')).toString());
expect(ret).to.eql(Object.assign({docType: 'asset'}, asset));
});
});
describe('Test CreateAsset', () => {
it('should return error on CreateAsset', async () => {
chaincodeStub.putState.rejects('failed inserting key');
let assetTransfer = new AssetTransfer();
try {
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
assert.fail('CreateAsset should have failed');
} catch(err) {
expect(err.name).to.equal('failed inserting key');
}
});
it('should return success on CreateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(asset);
});
});
describe('Test ReadAsset', () => {
it('should return error on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.ReadAsset(transactionContext, 'asset2');
assert.fail('ReadAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
expect(ret).to.eql(asset);
});
});
describe('Test UpdateAsset', () => {
it('should return error on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.UpdateAsset(transactionContext, 'asset2', 'orange', 10, 'Me', 500);
assert.fail('UpdateAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.UpdateAsset(transactionContext, 'asset1', 'orange', 10, 'Me', 500);
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
let expected = {
ID: 'asset1',
Color: 'orange',
Size: 10,
Owner: 'Me',
AppraisedValue: 500
};
expect(ret).to.eql(expected);
});
});
describe('Test DeleteAsset', () => {
it('should return error on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.DeleteAsset(transactionContext, 'asset2');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.DeleteAsset(transactionContext, asset.ID);
let ret = await chaincodeStub.getState(asset.ID);
expect(ret).to.equal(undefined);
});
});
describe('Test TransferAsset', () => {
it('should return error on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.TransferAsset(transactionContext, 'asset2', 'Me');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.TransferAsset(transactionContext, asset.ID, 'Me');
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(Object.assign({}, asset, {Owner: 'Me'}));
});
});
describe('Test GetAllAssets', () => {
it('should return success on GetAllAssets', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, 'asset1', 'blue', 5, 'Robert', 100);
await assetTransfer.CreateAsset(transactionContext, 'asset2', 'orange', 10, 'Paul', 200);
await assetTransfer.CreateAsset(transactionContext, 'asset3', 'red', 15, 'Troy', 300);
await assetTransfer.CreateAsset(transactionContext, 'asset4', 'pink', 20, 'Van', 400);
let ret = await assetTransfer.GetAllAssets(transactionContext);
ret = JSON.parse(ret);
expect(ret.length).to.equal(4);
let expected = [
{Record: {ID: 'asset1', Color: 'blue', Size: 5, Owner: 'Robert', AppraisedValue: 100}},
{Record: {ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200}},
{Record: {ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300}},
{Record: {ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}}
];
expect(ret).to.eql(expected);
});
it('should return success on GetAllAssets for non JSON value', async () => {
let assetTransfer = new AssetTransfer();
chaincodeStub.putState.onFirstCall().callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = 'non-json-value';
});
await assetTransfer.CreateAsset(transactionContext, 'asset1', 'blue', 5, 'Robert', 100);
await assetTransfer.CreateAsset(transactionContext, 'asset2', 'orange', 10, 'Paul', 200);
await assetTransfer.CreateAsset(transactionContext, 'asset3', 'red', 15, 'Troy', 300);
await assetTransfer.CreateAsset(transactionContext, 'asset4', 'pink', 20, 'Van', 400);
let ret = await assetTransfer.GetAllAssets(transactionContext);
ret = JSON.parse(ret);
expect(ret.length).to.equal(4);
let expected = [
{Record: 'non-json-value'},
{Record: {ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200}},
{Record: {ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300}},
{Record: {ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}}
];
expect(ret).to.eql(expected);
});
});
});