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