From 27ca6cbdf4ab0c94fa1210a7f1d68505ff8dc454 Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Fri, 1 Mar 2024 15:42:31 -0800 Subject: [PATCH] docs(examples/supply-chain): fix test infra - migrate to Fabric v2.5.6 The supply chain app's build and execution scripts should finally be working after this and also be much more stable than before due to the flakiness of the Fabric V1 test ledger not being an issue anymore. 1.The new contract is compiled with go v1.20 and therefore has to have the contract method names capitalized insted of camelCase, otherwise the methods are not possible to be exported and the contract deployment fails even if everything else is correct. 2. The supply chain app now uses the newest edition of the Fabric v2 AIO test ledger container image which uses Fabric v2.5.6 (current LTS at the time of this writing). 3. The shipment contract's source code has been migrated to Fabric v2 meaning that instead of a stub object we get a context object for each method's first parameter and then the stub can be acquired from that context object. 4. The method arguments no longer need to be passed around as an array of strings and instead the contract method's input arguments are first-class go method parameters. 5. Re-enabled a test case that was being skipped until now due to flakiness: ...`src/test/typescript/integration/supply-chain-backend-api-calls.test.ts` Depends on #3058 Depends on #3054 Fixes #2945 Fixes #2969 Fixes #1899 Fixes #1521 Fixes #1518 Signed-off-by: Peter Somogyvari --- .cspell.json | 12 ++- .github/workflows/ci.yaml | 2 + .../src/main/go/shipment.ts | 98 ++++++++++--------- ...ent-golang-contract-pinned-dependencies.ts | 37 +++++++ .../supply-chain-app-dummy-infrastructure.ts | 68 +++++-------- .../src/main/typescript/supply-chain-app.ts | 12 +-- .../supply-chain-backend-api-calls.test.ts | 41 +++++++- .../supply-chain-cli-via-npm-script.test.ts | 91 ++++++++++++----- .../web-services/insert-shipment-endpoint.ts | 2 +- .../web-services/list-shipment-endpoint.ts | 10 +- 10 files changed, 246 insertions(+), 127 deletions(-) create mode 100644 examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts diff --git a/.cspell.json b/.cspell.json index 686dfceff26..b45f60df1f5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -45,6 +45,7 @@ "COUCHDBCONFIG", "Creds", "data", + "davecgh", "dclm", "DHTAPI", "dids", @@ -61,9 +62,12 @@ "fidm", "flowdb", "fsouza", + "genproto", "GETHKEYCHAINPASSWORD", "ghcr", + "gobuffalo", "gopath", + "gopkg", "goquorum", "grpc", "grpcs", @@ -87,6 +91,7 @@ "Irohad", "isready", "jboss", + "joho", "JORDI", "jsrsa", "jsrsasign", @@ -101,6 +106,7 @@ "leveldb", "lmify", "LOCALMSPID", + "mailru", "miekg", "mitchellh", "MSPCONFIGPATH", @@ -129,7 +135,9 @@ "Orgs", "ossp", "outsh", + "Panicf", "parameterizable", + "pmezard", "Postgres", "proto", "protobuf", @@ -137,6 +145,7 @@ "protos", "qscc", "recoverupdateackmessage", + "rogpeppe", "RUSTC", "Rwset", "satp", @@ -171,7 +180,8 @@ "vuln", "wasm", "WSPROVIDER", - "Xdai" + "Xdai", + "xeipuuv" ], "dictionaries": [ "typescript,node,npm,go,rust" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1f6dbe9e4fd..8f65a8a96cc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -612,6 +612,8 @@ jobs: cactus-example-supply-chain-backend: continue-on-error: false env: + DUMP_DISK_USAGE_INFO_DISABLED: false + FREE_UP_GITHUB_RUNNER_DISK_SPACE_DISABLED: false FULL_BUILD_DISABLED: true JEST_TEST_PATTERN: examples/cactus-example-supply-chain-backend/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts JEST_TEST_RUNNER_DISABLED: false diff --git a/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts b/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts index 2a526cc3f28..481231181bb 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts @@ -1,15 +1,23 @@ export const SHIPMENT_CONTRACT_GO_SOURCE = `package main import ( - "bytes" "encoding/json" "fmt" + "log" + "strings" + "github.com/hyperledger/fabric-contract-api-go/contractapi" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/hyperledger/fabric/protos/peer" ) +func isNonBlank(str *string) bool { + if str == nil { + return false + } + return strings.TrimSpace(*str) != "" +} + type ShipmentChaincode struct { + contractapi.Contract } type Shipment struct { @@ -18,85 +26,79 @@ type Shipment struct { } func main() { - if err := shim.Start(new(ShipmentChaincode)); err != nil { - fmt.Printf("Error starting ShipmentChaincode chaincode: %s", err) + shipmentChaincode, err := contractapi.NewChaincode(&ShipmentChaincode{}) + if err != nil { + log.Panicf("Error creating supply-chain shipment chaincode: %v", err) } -} -func (t *ShipmentChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { - return shim.Success(nil) + if err := shipmentChaincode.Start(); err != nil { + log.Panicf("Error starting supply-chain shipment chaincode: %v", err) + } } -func (t *ShipmentChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { - fn, args := stub.GetFunctionAndParameters() +// InitLedger adds a base set of assets to the ledger +func (t *ShipmentChaincode) InitLedger(ctx contractapi.TransactionContextInterface) error { - if fn == "insertShipment" { - return t.insertShipment(stub, args) - } else if fn == "getListShipment" { - return t.getListShipment(stub, args) - } - return shim.Error("Unknown function") + log.Println("InitLedger ran for supply-chain shipment chaincode. %v", t) + + return nil } -func (t *ShipmentChaincode) insertShipment(stub shim.ChaincodeStubInterface, args []string) peer.Response { - if len(args) != 2 { - return shim.Error("Incorrect arguments. Expecting an id and a bookshelfId") +func (t *ShipmentChaincode) InsertShipment(ctx contractapi.TransactionContextInterface, newShipmentId string, bookshelfId string) (string, error) { + if !isNonBlank(&newShipmentId) { + return "E_NEW_SHIPMENT_ID_BLANK", fmt.Errorf("Incorrect arguments. Expecting a shipment ID as a non-blank string.") + } + if !isNonBlank(&bookshelfId) { + return "E_NEW_BOOKSHELF_ID_BLANK", fmt.Errorf("Incorrect arguments. Expecting a bookshelf ID as a non-blank string.") } var newShipment Shipment - newShipment.Id = args[0] - newShipment.BookshelfId = args[1] + newShipment.Id = newShipmentId + newShipment.BookshelfId = bookshelfId newShipmentJSON, err := json.Marshal(newShipment) if err != nil { - return shim.Error(err.Error()) + return "OP_FAILED", fmt.Errorf("Failed to JSON marshal new shipment data: %v", err) } - err2 := stub.PutState(args[0], newShipmentJSON) + stub := ctx.GetStub() + + err2 := stub.PutState(newShipmentId, newShipmentJSON) if err2 != nil { - return shim.Error("Failed to insert shipment:" + args[0] + err2.Error()) + return "E_PUT_STATE_FAIL", fmt.Errorf("Failed to insert new shipment to ledger state: %v --- %v", newShipmentJSON, err2) } - return shim.Success([]byte(args[0])) + return newShipmentId, nil } -func (t *ShipmentChaincode) getListShipment(stub shim.ChaincodeStubInterface, args []string) peer.Response { - if len(args) != 0 { - return shim.Error("Incorrect arguments. No arguments expected") - } - bookmark := "" +func (t *ShipmentChaincode) GetListShipment(ctx contractapi.TransactionContextInterface) ([]Shipment, error) { - resultsIterator, _, err := stub.GetStateByRangeWithPagination("", "", 25, bookmark) + stub := ctx.GetStub() + + resultsIterator, _, err := stub.GetStateByRangeWithPagination("", "", 25, "") if err != nil { - return shim.Error("Error in getListShipment: " + err.Error()) + return nil, fmt.Errorf("Error in GetListShipment: %v", err) } defer resultsIterator.Close() - var listShipment []Shipment + var shipments []Shipment for resultsIterator.HasNext() { - var buffer bytes.Buffer - var aux Shipment response, err := resultsIterator.Next() if err != nil { - return shim.Error("Error in iterator: " + err.Error()) + return nil, fmt.Errorf("Error in shipment result iterator: %v", err) } - buffer.WriteString(string(response.Value)) - - _ = json.Unmarshal(buffer.Bytes(), &aux) - - listShipment = append(listShipment, aux) - } + var aux Shipment + if err := json.Unmarshal(response.Value, &aux); err != nil { + return nil, fmt.Errorf("Error un-marshalling shipment: %v", err) + } - jsonResponse, err := json.Marshal(listShipment) - if err != nil { - return shim.Error("Error get result: " + err.Error()) + shipments = append(shipments, aux) } - return shim.Success(jsonResponse) -} -`; + return shipments, nil +}`; const exportSourceToFs = async () => { const path = await import("path"); diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts new file mode 100644 index 00000000000..4f5bab11b26 --- /dev/null +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/shipment-golang-contract-pinned-dependencies.ts @@ -0,0 +1,37 @@ +/** + * These are the pinned dependencies used to build the go source code that is + * located in the file at: + * examples/cactus-example-supply-chain-backend/src/main/go/shipment.ts + */ +export const SHIPMENT_GOLANG_CONTRACT_PINNED_DEPENDENCIES = [ + "github.com/hyperledger/fabric-chaincode-go@v0.0.0-20240124143825-7dec3c7e7d45", + "github.com/hyperledger/fabric-contract-api-go@v1.2.2", + "github.com/hyperledger/fabric-protos-go@v0.3.3", + "github.com/hyperledger/fabric-samples/asset-transfer-private-data/chaincode-go@v0.0.0-20240226031326-db86460086c0", + "github.com/stretchr/testify@v1.8.4", + "google.golang.org/protobuf@v1.32.0", + "github.com/davecgh/go-spew@v1.1.1", // indirect + "github.com/go-openapi/jsonpointer@v0.20.0", // indirect + "github.com/go-openapi/jsonreference@v0.20.2", // indirect + "github.com/go-openapi/spec@v0.20.9", // indirect + "github.com/go-openapi/swag@v0.22.4", // indirect + "github.com/gobuffalo/envy@v1.10.2", // indirect + "github.com/gobuffalo/packd@v1.0.2", // indirect + "github.com/gobuffalo/packr@v1.30.1", // indirect + "github.com/golang/protobuf@v1.5.3", // indirect + "github.com/joho/godotenv@v1.5.1", // indirect + "github.com/josharian/intern@v1.0.0", // indirect + "github.com/mailru/easyjson@v0.7.7", // indirect + "github.com/pmezard/go-difflib@v1.0.0", // indirect + "github.com/rogpeppe/go-internal@v1.11.0", // indirect + "github.com/xeipuuv/gojsonpointer@v0.0.0-20190905194746-02993c407bfb", // indirect + "github.com/xeipuuv/gojsonreference@v0.0.0-20180127040603-bd5ef7bd5415", // indirect + "github.com/xeipuuv/gojsonschema@v1.2.0", // indirect + "golang.org/x/mod@v0.14.0", // indirect + "golang.org/x/net@v0.20.0", // indirect + "golang.org/x/sys@v0.16.0", // indirect + "golang.org/x/text@v0.14.0", // indirect + "google.golang.org/genproto/googleapis/rpc@v0.0.0-20231030173426-d783a09b4405", // indirect + "google.golang.org/grpc@v1.59.0", // indirect + "gopkg.in/yaml.v3@v3.0.1", // indirect +]; diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts index 53f6ad805b1..f39c4d298b8 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts @@ -1,5 +1,6 @@ import { Account } from "web3-core"; import { v4 as uuidv4 } from "uuid"; +import { DiscoveryOptions } from "fabric-network"; import { Logger, Checks, @@ -13,38 +14,31 @@ import { PluginLedgerConnectorQuorum, Web3SigningCredentialType, } from "@hyperledger/cactus-plugin-ledger-connector-quorum"; +import { IPluginKeychain } from "@hyperledger/cactus-core-api"; +import { FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1 } from "@hyperledger/cactus-test-tooling"; +import { FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2 } from "@hyperledger/cactus-test-tooling"; import { BesuTestLedger, + DEFAULT_FABRIC_2_AIO_IMAGE_NAME, + FABRIC_25_LTS_AIO_FABRIC_VERSION, + FABRIC_25_LTS_AIO_IMAGE_VERSION, FabricTestLedgerV1, QuorumTestLedger, } from "@hyperledger/cactus-test-tooling"; - -import BambooHarvestRepositoryJSON from "../../json/generated/BambooHarvestRepository.json"; -import BookshelfRepositoryJSON from "../../json/generated/BookshelfRepository.json"; import { IEthContractDeployment, ISupplyChainContractDeploymentInfo, IFabricContractDeployment, - // OrgEnv, } from "@hyperledger/cactus-example-supply-chain-business-logic-plugin"; import { PluginLedgerConnectorFabric, DefaultEventHandlerStrategy, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; -import { DiscoveryOptions } from "fabric-network"; -import { SHIPMENT_CONTRACT_GO_SOURCE } from "../../go/shipment"; -import { IPluginKeychain } from "@hyperledger/cactus-core-api"; -export 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", -}; +import BambooHarvestRepositoryJSON from "../../json/generated/BambooHarvestRepository.json"; +import BookshelfRepositoryJSON from "../../json/generated/BookshelfRepository.json"; +import { SHIPMENT_CONTRACT_GO_SOURCE } from "../../go/shipment"; +import { SHIPMENT_GOLANG_CONTRACT_PINNED_DEPENDENCIES } from "./shipment-golang-contract-pinned-dependencies"; export interface ISupplyChainAppDummyInfrastructureOptions { logLevel?: LogLevelDesc; @@ -113,8 +107,10 @@ export class SupplyChainAppDummyInfrastructure { }); this.fabric = new FabricTestLedgerV1({ publishAllPorts: true, - imageName: "ghcr.io/hyperledger/cactus-fabric-all-in-one", + imageName: DEFAULT_FABRIC_2_AIO_IMAGE_NAME, + imageVersion: FABRIC_25_LTS_AIO_IMAGE_VERSION, logLevel: level, + envVars: new Map([["FABRIC_VERSION", FABRIC_25_LTS_AIO_FABRIC_VERSION]]), emitContainerLogs: true, }); @@ -292,7 +288,7 @@ export class SupplyChainAppDummyInfrastructure { sshConfig: sshConfig, logLevel: this.options.logLevel || "INFO", connectionProfile: connectionProfile, - cliContainerEnv: org1Env, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, discoveryOptions: discoveryOptions, eventHandlerOptions: { strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, @@ -300,8 +296,12 @@ export class SupplyChainAppDummyInfrastructure { }); const res = await connector.deployContractGoSourceV1({ - tlsRootCertFiles: org1Env.CORE_PEER_TLS_ROOTCERT_FILE as string, - targetPeerAddresses: [org1Env.CORE_PEER_ADDRESS as string], + tlsRootCertFiles: + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.CORE_PEER_TLS_ROOTCERT_FILE, + targetPeerAddresses: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.CORE_PEER_ADDRESS, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2.CORE_PEER_ADDRESS, + ], policyDslSource: "OR('Org1MSP.member','Org2MSP.member')", channelId: "mychannel", chainCodeVersion: "1.0.0", @@ -311,29 +311,11 @@ export class SupplyChainAppDummyInfrastructure { filename: "shipment.go", }, moduleName: "shipment", - targetOrganizations: [org1Env], - 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", + targetOrganizations: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, ], + pinnedDeps: SHIPMENT_GOLANG_CONTRACT_PINNED_DEPENDENCIES, }); this.log.debug("Supply chain app Fabric contract deployment result:", res); diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts index 363e17d810f..75aeb381ede 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts @@ -60,10 +60,9 @@ import { DefaultEventHandlerStrategy, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; -import { - SupplyChainAppDummyInfrastructure, - org1Env, -} from "./infrastructure/supply-chain-app-dummy-infrastructure"; +import { FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1 } from "@hyperledger/cactus-test-tooling"; + +import { SupplyChainAppDummyInfrastructure } from "./infrastructure/supply-chain-app-dummy-infrastructure"; import { Configuration, DefaultApi as SupplyChainApi, @@ -193,6 +192,7 @@ export class SupplyChainApp { if (!this.options.disableSignalHandlers) { exitHook((callback: IAsyncExitHookDoneCallback) => { + console.log(`Executing Registered signal handler to stop container.`); this.stop().then(callback); }); this.log.debug(`Registered signal handlers for graceful auto-shutdown`); @@ -388,7 +388,7 @@ export class SupplyChainApp { besuApiClient, quorumApiClient, fabricApiClient, - fabricEnvironment: org1Env, + fabricEnvironment: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, }), this.keychain, ], @@ -403,7 +403,7 @@ export class SupplyChainApp { instanceId: "PluginLedgerConnectorFabric_C", dockerBinary: "/usr/local/bin/docker", peerBinary: "peer", - cliContainerEnv: org1Env, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, connectionProfile: connectionProfile, sshConfig: sshConfig, logLevel: "INFO", diff --git a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts index ab5e8d0c9a3..36fd3affd22 100644 --- a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts +++ b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts @@ -1,3 +1,4 @@ +import { v4 as uuidV4 } from "uuid"; import test, { Test } from "tape-promise/tape"; import { LogLevelDesc } from "@hyperledger/cactus-common"; import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; @@ -8,12 +9,13 @@ import { ConfigService } from "@hyperledger/cactus-cmd-api-server"; import * as publicApi from "../../../main/typescript/public-api"; import { ISupplyChainAppOptions } from "../../../main/typescript/public-api"; import { SupplyChainApp } from "../../../main/typescript/public-api"; +import { Shipment } from "@hyperledger/cactus-example-supply-chain-business-logic-plugin"; const testCase = "can launch via CLI with generated API server .config.json file"; const logLevel: LogLevelDesc = "TRACE"; -test.skip("BEFORE " + testCase, async (t: Test) => { +test("BEFORE " + testCase, async (t: Test) => { const pruning = pruneDockerAllIfGithubAction({ logLevel }); await t.doesNotReject(pruning, "Pruning did not throw OK"); t.end(); @@ -21,7 +23,7 @@ test.skip("BEFORE " + testCase, async (t: Test) => { // FIXME: https://github.com/hyperledger/cactus/issues/1521 // Skipping until test can be stabilized. -test.skip("Supply chain backend API calls can be executed", async (t: Test) => { +test("Supply chain backend API calls can be executed", async (t: Test) => { t.ok(publicApi, "Public API of the package imported OK"); const configService = new ConfigService(); @@ -69,7 +71,7 @@ test.skip("Supply chain backend API calls can be executed", async (t: Test) => { // Node A => Besu // Node B => Quorum - // Node C => Fabric 1.4.x + // Node C => Fabric v2.5.6 const startResult = await app.start(); const { apiServerA, apiServerB, apiServerC } = startResult; t.ok(apiServerA, "ApiServerA truthy OK"); @@ -144,5 +146,38 @@ test.skip("Supply chain backend API calls can be executed", async (t: Test) => { "listShipmentRes status < 300 truthy OK", ); + const shipment: Shipment = { + bookshelfId: "some-id-of-a-bookshelf-" + uuidV4(), + id: "some-id-of-a-shipment-" + uuidV4(), + }; + + const insertShipmentRes = await supplyChainApiClientC.insertShipmentV1({ + shipment, + }); + t.ok(insertShipmentRes, "insertShipmentRes truthy OK"); + t.true( + insertShipmentRes.status > 199, + "insertShipmentRes status > 199 truthy OK", + ); + t.true( + insertShipmentRes.status < 300, + "insertShipmentRes status < 300 truthy OK", + ); + + const listShipmentRes2 = await supplyChainApiClientC.listShipmentV1(); + t.ok(listShipmentRes2, "listShipmentRes2 truthy OK"); + t.true( + listShipmentRes2.status > 199, + "listShipmentRes2 status > 199 truthy OK", + ); + t.true( + listShipmentRes2.status < 300, + "listShipmentRes2 status < 300 truthy OK", + ); + + const shipments = listShipmentRes2.data.data; + const [firstShipment] = shipments; + t.equal(shipment.id, firstShipment.id, "Inserted shipment matches IDs OK"); + t.end(); }); diff --git a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts index a5256b331fe..ccbff0f52a6 100644 --- a/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts +++ b/examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts @@ -1,53 +1,79 @@ import path from "path"; -import { spawn } from "child_process"; +import { promisify } from "util"; +import { spawn, exec } from "child_process"; +import { v4 as uuidV4 } from "uuid"; import test, { Test } from "tape-promise/tape"; import { LogLevelDesc } from "@hyperledger/cactus-common"; import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; import * as publicApi from "../../../main/typescript/public-api"; -const testCase = - "can launch via CLI with generated API server .config.json file"; +const testCase = "SupplyChainApp can launch via root package.json script"; const logLevel: LogLevelDesc = "TRACE"; -test.skip("BEFORE " + testCase, async (t: Test) => { +test("BEFORE " + testCase, async (t: Test) => { const pruning = pruneDockerAllIfGithubAction({ logLevel }); await t.doesNotReject(pruning, "Pruning did not throw OK"); t.end(); }); -// FIXME: remove the skip once this issue is fixed: -// https://github.com/hyperledger/cactus/issues/1518 -test.skip("Supply chain backend API calls can be executed", async (t: Test) => { +async function psFilter(filter?: string): Promise> { + const execAsync = promisify(exec); + try { + const { stdout } = await execAsync("ps -wo pid,cmd"); + const rows = stdout.split("\n"); + const map = new Map(); + rows.forEach((row) => { + const [pidStr, ...cmdParts] = row.split(" "); + if (pidStr === "") { + return; + } + const cmd = cmdParts.join(" "); + console.log("pid=%o, cmd=%o", pidStr, cmd); + const pid = parseInt(pidStr, 10); + if (filter && cmd.includes(filter)) { + map.set(pid, cmd); + } + }); + return map; + } catch (e: unknown) { + console.error("Crashed while running ps binary. Are you on Linux?", e); + throw e; + } +} + +test(testCase, async (t: Test) => { t.ok(publicApi, "Public API of the package imported OK"); test.onFinish(async () => await pruneDockerAllIfGithubAction({ logLevel })); + const uuid = uuidV4(); + const projectRoot = path.join(__dirname, "../../../../../../"); - const child = spawn("npm", ["run", "start:example-supply-chain"], { - cwd: projectRoot, - }); + // used to find the processes to kill after we are done with the test + const beaconCliArg = "--" + uuid; - const logs = []; - for await (const data of child.stdout) { - console.log(`[child]: ${data}`); - logs.push(data); - } + const child = spawn( + "npm", + ["run", "start:example-supply-chain", "--", beaconCliArg], + { + cwd: projectRoot, + }, + ); - for await (const data of child.stderr) { - console.error(`[child]: ${data}`); - logs.push(data); - } + let apiIsHealthy = false; const childProcessPromise = new Promise((resolve, reject) => { child.once("exit", (code: number, signal: NodeJS.Signals) => { - if (code === 0) { + t.comment(`EVENT:exit child process exited gracefully.`); + t.comment(`EVENT:exit signal=${signal}`); + t.comment(`EVENT:exit exitCode=${code}`); + t.comment(`EVENT:exit apiIsHealthy=${apiIsHealthy}`); + if (apiIsHealthy) { resolve(); } else { const msg = `Child process crashed. exitCode=${code}, signal=${signal}`; reject(new Error(msg)); } - t.comment(`EVENT:exit signal=${signal}`); - t.comment(`EVENT:exit exitCode=${code}`); }); child.on("close", (code: number, signal: NodeJS.Signals) => { t.comment(`EVENT:close signal=${signal}`); @@ -62,7 +88,28 @@ test.skip("Supply chain backend API calls can be executed", async (t: Test) => { }); }); + const logs = []; + for await (const data of child.stdout) { + console.log(`[child]: ${data}`); + if (data.includes("Cactus API reachable http")) { + console.log("Sending kill signal to child process..."); + apiIsHealthy = true; + const killedOK = child.kill("SIGKILL"); + console.log("Sent kill signal, success=%o", killedOK); + + const processMap = await psFilter(uuid); + processMap.forEach((v, k) => { + console.log("Killing sub-process with pid: %o (cmd=%o) ", k, v); + process.kill(k); + }); + break; + } + logs.push(data); + } + await t.doesNotReject(childProcessPromise, "childProcessPromise resolves OK"); t.end(); + + process.exit(0); }); diff --git a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts index 6b651b7f171..01592921856 100644 --- a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts +++ b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/insert-shipment-endpoint.ts @@ -104,7 +104,7 @@ export class InsertShipmentEndpoint implements IWebServiceEndpoint { channelName: "mychannel", contractName: "shipment", invocationType: FabricContractInvocationType.Send, - methodName: "insertShipment", + methodName: "InsertShipment", params: [shipment.id, shipment.bookshelfId], }; const { diff --git a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts index 22c3c7c4614..0e9808bb22b 100644 --- a/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts +++ b/examples/cactus-example-supply-chain-business-logic-plugin/src/main/typescript/business-logic-plugin/web-services/list-shipment-endpoint.ts @@ -7,6 +7,7 @@ import { LoggerProvider, IAsyncProvider, safeStringifyException, + Strings, } from "@hyperledger/cactus-common"; import { IEndpointAuthzOptions, @@ -103,13 +104,16 @@ export class ListShipmentEndpoint implements IWebServiceEndpoint { channelName: "mychannel", contractName: "shipment", invocationType: FabricContractInvocationType.Call, - methodName: "getListShipment", + methodName: "GetListShipment", params: [], }; const { - data: { functionOutput }, + data: { functionOutput: fnOutJsonRaw }, } = await this.opts.fabricApi.runTransactionV1(request); - const output = JSON.parse(functionOutput); + // The functionOutput might come back as an empty string which fails to + // JSON parse and if that happens we just massage it into an empty array. + const fnOutJson = Strings.isNonBlank(fnOutJsonRaw) ? fnOutJsonRaw : "[]"; + const output = JSON.parse(fnOutJson); const body = { data: output }; res.status(200); res.json(body);