From daacd5372732fa88f12b03721a6a416c6f8a20e6 Mon Sep 17 00:00:00 2001 From: Elena Izaguirre Date: Wed, 22 Sep 2021 09:23:56 +0200 Subject: [PATCH] fix: openapi validation test for fabric plugin Includes tests for endpoints deployContractGoSourceV1, deployContractV1 and runTransactionV1, each of them with test cases: - Right request - Request including an invalid parameter - Request without a required parameter Closes #1295 Relationed with #847 Signed-off-by: Elena Izaguirre --- .../src/main/json/openapi.json | 1 - .../generated/openapi/typescript-axios/api.ts | 2 +- .../openapi/openapi-validation-go.test.ts | 350 +++++++++++++ .../openapi/openapi-validation.test.ts | 482 ++++++++++++++++++ 4 files changed, 833 insertions(+), 2 deletions(-) create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation-go.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation.test.ts diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json index 325446b3308..3197a0ed65b 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json @@ -137,7 +137,6 @@ "type": "object", "required": [ "name", - "description", "version", "organizations", "peers" diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts index d95a59a17a0..221a5844bec 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -120,7 +120,7 @@ export interface ConnectionProfile { * @type {string} * @memberof ConnectionProfile */ - description: string; + description?: string; /** * * @type {string} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation-go.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation-go.test.ts new file mode 100644 index 00000000000..fa0d1bf66c5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation-go.test.ts @@ -0,0 +1,350 @@ +import { AddressInfo } from "net"; +import http from "http"; +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import express from "express"; +import bodyParser from "body-parser"; +import { + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { + DefaultEventHandlerStrategy, + PluginLedgerConnectorFabric, + DeployContractGoSourceV1Request, +} from "../../../../main/typescript/public-api"; + +import { HELLO_WORLD_CONTRACT_GO_SOURCE } from "../../fixtures/go/hello-world-contract-fabric-v14/hello-world-contract-go-source"; + +import { DefaultApi as FabricApi } from "../../../../main/typescript/public-api"; + +import { IPluginLedgerConnectorFabricOptions } from "../../../../main/typescript/plugin-ledger-connector-fabric"; + +import { DiscoveryOptions } from "fabric-network"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { Configuration } from "@hyperledger/cactus-core-api"; + +import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core"; +import OAS from "../../../../main/json/openapi.json"; + +const testCase = "check openapi validation in fabric endpoints"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + test.onFailure(async () => { + await Containers.logDiagnostics({ logLevel }); + }); + const ledger = new FabricTestLedgerV1({ + emitContainerLogs: true, + logLevel, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + imageVersion: "2021-09-02--fix-876-supervisord-retries", + }); + + const tearDown = async () => { + await ledger.stop(); + await ledger.destroy(); + }; + + test.onFinish(tearDown); + + await ledger.start(); + const connectionProfile = await ledger.getConnectionProfileOrg1(); + const enrollAdminOut = await ledger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await ledger.enrollUser(adminWallet); + const sshConfig = await ledger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_PEER_LOCALMSPID: "Org1MSP", + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + CORE_PEER_MSPCONFIGPATH: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp", + CORE_PEER_TLS_ROOTCERT_FILE: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", + ORDERER_TLS_ROOTCERT_FILE: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem", + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_PEER_LOCALMSPID: "Org2MSP", + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp", + CORE_PEER_TLS_ROOTCERT_FILE: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt", + ORDERER_TLS_ROOTCERT_FILE: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem", + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + pluginRegistry, + peerBinary: "/fabric-samples/bin/peer", + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + const plugin = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { port } = addressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + + await installOpenapiValidationMiddleware({ + logLevel, + app: expressApp, + apiSpec: OAS, + }); + + await plugin.getOrCreateWebServices(); + await plugin.registerWebServices(expressApp); + const apiUrl = `http://localhost:${port}`; + + const config = new Configuration({ basePath: apiUrl }); + const apiClient = new FabricApi(config); + + const fDeployGo = "deployContractGoSourceV1"; + const cOk = "without bad request error"; + const cWithoutParams = "not sending all required parameters"; + const cInvalidParams = "sending invalid parameters"; + + test(`${testCase} - ${fDeployGo} - ${cOk}`, async (t2: Test) => { + const parameters = { + targetPeerAddresses: ["peer0.org1.example.com:7051"], + tlsRootCertFiles: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", + policyDslSource: "AND('Org1MSP.member','Org2MSP.member')", + channelId: "mychannel", + chainCodeVersion: "1.0.0", + constructorArgs: { Args: ["john", "99"] }, + goSource: { + body: Buffer.from(HELLO_WORLD_CONTRACT_GO_SOURCE).toString("base64"), + filename: "hello-world.go", + }, + moduleName: "hello-world", + targetOrganizations: [org1Env, org2Env], + pinnedDeps: [ + "github.com/Knetic/govaluate@v3.0.0+incompatible", + "github.com/Shopify/sarama@v1.27.0", + "github.com/fsouza/go-dockerclient@v1.6.5", + "github.com/grpc-ecosystem/go-grpc-middleware@v1.2.1", + "github.com/hashicorp/go-version@v1.2.1", + "github.com/hyperledger/fabric@v1.4.8", + "github.com/hyperledger/fabric-amcl@v0.0.0-20200424173818-327c9e2cf77a", + "github.com/miekg/pkcs11@v1.0.3", + "github.com/mitchellh/mapstructure@v1.3.3", + "github.com/onsi/ginkgo@v1.14.1", + "github.com/onsi/gomega@v1.10.2", + "github.com/op/go-logging@v0.0.0-20160315200505-970db520ece7", + "github.com/pkg/errors@v0.9.1", + "github.com/spf13/viper@v1.7.1", + "github.com/stretchr/testify@v1.6.1", + "github.com/sykesm/zap-logfmt@v0.0.3", + "go.uber.org/zap@v1.16.0", + "golang.org/x/crypto@v0.0.0-20200820211705-5c72a883971a", + "golang.org/x/net@v0.0.0-20210503060351-7fd8e65b6420", + "google.golang.org/grpc@v1.31.1", + ], + }; + + const res = await apiClient.deployContractGoSourceV1(parameters); + + t2.equal( + res.status, + 200, + `Endpoint ${fDeployGo}: response.status === 200 OK`, + ); + t2.true( + res.data.success, + "deployContractGoSourceV1 res.data.success === true", + ); + + t2.end(); + }); + + test(`${testCase} - ${fDeployGo} - ${cWithoutParams}`, async (t2: Test) => { + const parameters = { + // targetPeerAddresses: ["peer0.org1.example.com:7051"], + tlsRootCertFiles: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", + policyDslSource: "AND('Org1MSP.member','Org2MSP.member')", + channelId: "mychannel", + chainCodeVersion: "1.0.0", + constructorArgs: { Args: ["john", "99"] }, + goSource: { + body: Buffer.from(HELLO_WORLD_CONTRACT_GO_SOURCE).toString("base64"), + filename: "hello-world.go", + }, + moduleName: "hello-world", + targetOrganizations: [org1Env, org2Env], + pinnedDeps: [ + "github.com/Knetic/govaluate@v3.0.0+incompatible", + "github.com/Shopify/sarama@v1.27.0", + "github.com/fsouza/go-dockerclient@v1.6.5", + "github.com/grpc-ecosystem/go-grpc-middleware@v1.2.1", + "github.com/hashicorp/go-version@v1.2.1", + "github.com/hyperledger/fabric@v1.4.8", + "github.com/hyperledger/fabric-amcl@v0.0.0-20200424173818-327c9e2cf77a", + "github.com/miekg/pkcs11@v1.0.3", + "github.com/mitchellh/mapstructure@v1.3.3", + "github.com/onsi/ginkgo@v1.14.1", + "github.com/onsi/gomega@v1.10.2", + "github.com/op/go-logging@v0.0.0-20160315200505-970db520ece7", + "github.com/pkg/errors@v0.9.1", + "github.com/spf13/viper@v1.7.1", + "github.com/stretchr/testify@v1.6.1", + "github.com/sykesm/zap-logfmt@v0.0.3", + "go.uber.org/zap@v1.16.0", + "golang.org/x/crypto@v0.0.0-20200820211705-5c72a883971a", + "golang.org/x/net@v0.0.0-20210503060351-7fd8e65b6420", + "google.golang.org/grpc@v1.31.1", + ], + }; + + try { + await apiClient.deployContractGoSourceV1( + (parameters as any) as DeployContractGoSourceV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeployGo} without required targetPeerAddresses: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("targetPeerAddresses"), + "Rejected because targetPeerAddresses is required", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fDeployGo} - ${cInvalidParams}`, async (t2: Test) => { + const parameters = { + targetPeerAddresses: ["peer0.org1.example.com:7051"], + tlsRootCertFiles: + "/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", + policyDslSource: "AND('Org1MSP.member','Org2MSP.member')", + channelId: "mychannel", + chainCodeVersion: "1.0.0", + constructorArgs: { Args: ["john", "99"] }, + goSource: { + body: Buffer.from(HELLO_WORLD_CONTRACT_GO_SOURCE).toString("base64"), + filename: "hello-world.go", + }, + moduleName: "hello-world", + targetOrganizations: [org1Env, org2Env], + pinnedDeps: [ + "github.com/Knetic/govaluate@v3.0.0+incompatible", + "github.com/Shopify/sarama@v1.27.0", + "github.com/fsouza/go-dockerclient@v1.6.5", + "github.com/grpc-ecosystem/go-grpc-middleware@v1.2.1", + "github.com/hashicorp/go-version@v1.2.1", + "github.com/hyperledger/fabric@v1.4.8", + "github.com/hyperledger/fabric-amcl@v0.0.0-20200424173818-327c9e2cf77a", + "github.com/miekg/pkcs11@v1.0.3", + "github.com/mitchellh/mapstructure@v1.3.3", + "github.com/onsi/ginkgo@v1.14.1", + "github.com/onsi/gomega@v1.10.2", + "github.com/op/go-logging@v0.0.0-20160315200505-970db520ece7", + "github.com/pkg/errors@v0.9.1", + "github.com/spf13/viper@v1.7.1", + "github.com/stretchr/testify@v1.6.1", + "github.com/sykesm/zap-logfmt@v0.0.3", + "go.uber.org/zap@v1.16.0", + "golang.org/x/crypto@v0.0.0-20200820211705-5c72a883971a", + "golang.org/x/net@v0.0.0-20210503060351-7fd8e65b6420", + "google.golang.org/grpc@v1.31.1", + ], + fake: 4, + }; + + try { + await apiClient.deployContractGoSourceV1( + (parameters as any) as DeployContractGoSourceV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeployGo} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation.test.ts new file mode 100644 index 00000000000..7b287edef4d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation.test.ts @@ -0,0 +1,482 @@ +import { AddressInfo } from "net"; +import http from "http"; +import fs from "fs-extra"; +import path from "path"; +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import express from "express"; +import bodyParser from "body-parser"; +import { + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + ChainCodeProgrammingLanguage, + DefaultEventHandlerStrategy, + DeployContractV1Request, + FabricContractInvocationType, + FileBase64, + PluginLedgerConnectorFabric, + RunTransactionRequest, +} from "../../../../main/typescript/public-api"; +import { DefaultApi as FabricApi } from "../../../../main/typescript/public-api"; +import { IPluginLedgerConnectorFabricOptions } from "../../../../main/typescript/plugin-ledger-connector-fabric"; +import { DiscoveryOptions } from "fabric-network"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { Configuration } from "@hyperledger/cactus-core-api"; + +import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core"; +import OAS from "../../../../main/json/openapi.json"; + +const testCase = "deploys Fabric 2.x contract from typescript source"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const channelId = "mychannel"; + const channelName = channelId; + + test.onFailure(async () => { + await Containers.logDiagnostics({ logLevel }); + }); + + const ledger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + imageVersion: "2021-09-02--fix-876-supervisord-retries", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + const tearDown = async () => { + await ledger.stop(); + await ledger.destroy(); + }; + + test.onFinish(tearDown); + await ledger.start(); + + const connectionProfile = await ledger.getConnectionProfileOrg1(); + t.ok(connectionProfile, "getConnectionProfileOrg1() out truthy OK"); + + const enrollAdminOut = await ledger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await ledger.enrollUser(adminWallet); + const sshConfig = await ledger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // This is the directory structure of the Fabirc 2.x CLI container (fabric-tools image) + // const orgCfgDir = "/fabric-samples/test-network/organizations/"; + const orgCfgDir = + "/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations/"; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org1MSP", + + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt`, + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp`, + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org2MSP", + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp`, + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt`, + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + const plugin = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { port } = addressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + + await installOpenapiValidationMiddleware({ + logLevel, + app: expressApp, + apiSpec: OAS, + }); + + await plugin.getOrCreateWebServices(); + await plugin.registerWebServices(expressApp); + const apiUrl = `http://localhost:${port}`; + + const config = new Configuration({ basePath: apiUrl }); + + const apiClient = new FabricApi(config); + + const contractName = "basic-asset-transfer-2"; + + const contractRelPath = + "../../fixtures/go/basic-asset-transfer/chaincode-typescript"; + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ └── index.ts + // ├── tsconfig.json + // └── tslint.json + const sourceFiles: FileBase64[] = []; + { + const filename = "./tslint.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./tsconfig.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./package.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./index.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./asset.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./assetTransfer.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + + const fDeploy = "deployContractV1"; + const fRun = "runTransactionV1"; + const cOk = "without bad request error"; + const cWithoutParams = "not sending all required parameters"; + const cInvalidParams = "sending invalid parameters"; + + test(`${testCase} - ${fDeploy} - ${cOk}`, async (t2: Test) => { + const parameters = { + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: contractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }; + + const res = await apiClient.deployContractV1(parameters); + + t2.equal( + res.status, + 200, + `Endpoint ${fDeploy}: response.status === 200 OK`, + ); + t2.true(res.data.success, "res.data.success === true"); + + t2.end(); + }); + + test(`${testCase} - ${fDeploy} - ${cWithoutParams}`, async (t2: Test) => { + const parameters = { + // channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: contractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }; + + try { + await apiClient.deployContractV1( + (parameters as any) as DeployContractV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeploy} without required channelId: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("channelId"), + "Rejected because channelId is required", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fDeploy} - ${cInvalidParams}`, async (t2: Test) => { + const parameters = { + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: contractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + fake: 4, + }; + + try { + await apiClient.deployContractV1( + (parameters as any) as DeployContractV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeploy} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fRun} - ${cOk}`, async (t2: Test) => { + await new Promise((resolve) => setTimeout(resolve, 10000)); + + const assetId = uuidv4(); + const assetOwner = uuidv4(); + + const parameters = { + contractName, + channelName, + params: [assetId, "Green", "19", assetOwner, "9999"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }; + const res = await apiClient.runTransactionV1(parameters); + t2.ok(res, "res truthy OK"); + t2.equal(res.status, 200, `Endpoint ${fRun}: response.status === 200 OK`); + + t2.end(); + }); + + test(`${testCase} - ${fRun} - ${cWithoutParams}`, async (t2: Test) => { + const assetId = uuidv4(); + const assetOwner = uuidv4(); + + const parameters = { + // contractName, + channelName, + params: [assetId, "Green", "19", assetOwner, "9999"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }; + + try { + await apiClient.runTransactionV1( + (parameters as any) as RunTransactionRequest, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fRun} without required contractName: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("contractName"), + "Rejected because contractName is required", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fRun} - ${cInvalidParams}`, async (t2: Test) => { + const assetId = uuidv4(); + const assetOwner = uuidv4(); + + const parameters = { + contractName, + channelName, + params: [assetId, "Green", "19", assetOwner, "9999"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + fake: 4, + }; + + try { + await apiClient.runTransactionV1( + (parameters as any) as RunTransactionRequest, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fRun} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +});