diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 522824e9cd..6472a53cc7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -29,23 +29,46 @@ jobs: e2e-test: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - test-suite: [TestEthereumE2ESuite, TestFabricE2ESuite] blockchain-provider: [geth, fabric] - token-provider: [none, erc1155, erc20_erc721] - database-type: [sqlite3, postgres] - exclude: - - blockchain-provider: geth - test-suite: TestFabricE2ESuite - - blockchain-provider: fabric - test-suite: TestEthereumE2ESuite - - blockchain-provider: fabric - token-provider: erc1155 - - blockchain-provider: fabric - token-provider: erc20_erc721 - - blockchain-provider: geth - token-provider: none - fail-fast: false + exclude: [ blockchain-provider: geth, blockchain-provider: fabric ] + include: + - blockchain-provider: geth + test-suite: TestEthereumMultipartyE2ESuite + database-type: sqlite3 + token-provider: erc20_erc721 + multiparty-enabled: true + + - blockchain-provider: geth + test-suite: TestEthereumMultipartyE2ESuite + database-type: postgres + token-provider: erc20_erc721 + multiparty-enabled: true + + - blockchain-provider: geth + test-suite: TestEthereumMultipartyE2ESuite + database-type: sqlite3 + token-provider: erc1155 + multiparty-enabled: true + + - blockchain-provider: fabric + test-suite: TestFabricE2ESuite + database-type: sqlite3 + token-provider: none + multiparty-enabled: true + + - blockchain-provider: geth + test-suite: TestEthereumGatewayE2ESuite + database-type: sqlite3 + token-provider: erc20_erc721 + multiparty-enabled: false + + - blockchain-provider: fabric + test-suite: TestFabricGatewayE2ESuite + database-type: sqlite3 + token-provider: none + multiparty-enabled: false steps: - uses: actions/checkout@v2 with: @@ -62,6 +85,7 @@ jobs: BLOCKCHAIN_PROVIDER: ${{ matrix.blockchain-provider }} TOKENS_PROVIDER: ${{ matrix.token-provider }} DATABASE_TYPE: ${{ matrix.database-type }} + MULTIPARTY_ENABLED: ${{ matrix.multiparty-enabled }} run: ./test/e2e/run.sh - name: Archive container logs diff --git a/.vscode/launch.json b/.vscode/launch.json index 0888a22f57..dc280359eb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,11 +8,11 @@ "name": "Debug E2E Tests", "type": "go", "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}/test/e2e", - "args": ["-test.run"], + "mode": "test", + "program": "${fileDirname}", "env": { - "STACK_FILE": "{env:HOME}/.firefly/stacks/firefly_e2e/stack.json" + "STACK_FILE": "${env:HOME}/.firefly/stacks/firefly_e2e/stack.json", + "STACK_STATE": "${env:HOME}/.firefly/stacks/firefly_e2e/runtime/stackState.json" }, "showLog": true }, diff --git a/manifest.json b/manifest.json index 632b08b559..d6fddb98ad 100644 --- a/manifest.json +++ b/manifest.json @@ -44,6 +44,6 @@ "release": "v1.1.0-alpha.1" }, "cli": { - "tag": "v1.0.2" + "tag": "v1.1.0-alpha.1" } } diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go new file mode 100644 index 0000000000..5f3769c990 --- /dev/null +++ b/test/e2e/e2e.go @@ -0,0 +1,248 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/go-resty/resty/v2" + "github.com/gorilla/websocket" + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type TestState interface { + T() *testing.T + StartTime() time.Time + Done() func() +} + +var WidgetSchemaJSON = []byte(`{ + "$id": "https://example.com/widget.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Widget", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the widget." + }, + "name": { + "type": "string", + "description": "The person's last name." + } + }, + "additionalProperties": false +}`) + +func PollForUp(t *testing.T, client *resty.Client) { + var resp *resty.Response + var err error + for i := 0; i < 3; i++ { + resp, err = GetNamespaces(client) + if err == nil && resp.StatusCode() == 200 { + break + } + time.Sleep(5 * time.Second) + } + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode()) +} + +func ValidateAccountBalances(t *testing.T, client *resty.Client, poolID *fftypes.UUID, tokenIndex string, balances map[string]int64) { + for key, balance := range balances { + account := GetTokenBalance(t, client, poolID, tokenIndex, key) + assert.Equal(t, balance, account.Balance.Int().Int64()) + } +} + +func PickTopic(i int, options []string) string { + return options[i%len(options)] +} + +func ReadStack(t *testing.T) *Stack { + stackFile := os.Getenv("STACK_FILE") + if stackFile == "" { + t.Fatal("STACK_FILE must be set") + } + stack, err := ReadStackFile(stackFile) + assert.NoError(t, err) + return stack +} + +func ReadStackState(t *testing.T) *StackState { + stackFile := os.Getenv("STACK_STATE") + if stackFile == "" { + t.Fatal("STACK_STATE must be set") + } + stackState, err := ReadStackStateFile(stackFile) + assert.NoError(t, err) + return stackState +} + +func WsReader(conn *websocket.Conn, dbChanges bool) chan *core.EventDelivery { + events := make(chan *core.EventDelivery, 100) + go func() { + for { + _, b, err := conn.ReadMessage() + if err != nil { + fmt.Printf("Websocket %s closing, error: %s\n", conn.RemoteAddr(), err) + return + } + var wsa core.WSActionBase + err = json.Unmarshal(b, &wsa) + if err != nil { + panic(fmt.Errorf("invalid JSON received on WebSocket: %s", err)) + } + var ed core.EventDelivery + err = json.Unmarshal(b, &ed) + if err != nil { + panic(fmt.Errorf("invalid JSON received on WebSocket: %s", err)) + } + if err == nil { + fmt.Printf("Websocket %s event: %s/%s/%s -> %s (tx=%s)\n", conn.RemoteAddr(), ed.Namespace, ed.Type, ed.ID, ed.Reference, ed.Transaction) + events <- &ed + } + } + }() + return events +} + +func WaitForEvent(t *testing.T, c chan *core.EventDelivery, eventType core.EventType, ref *fftypes.UUID) { + for { + ed := <-c + if ed.Type == eventType && (ref == nil || *ref == *ed.Reference) { + t.Logf("Detected '%s' event for ref '%s'", ed.Type, ed.Reference) + return + } + t.Logf("Ignored event '%s'", ed.ID) + } +} + +func WaitForMessageConfirmed(t *testing.T, c chan *core.EventDelivery, msgType core.MessageType) *core.EventDelivery { + for { + ed := <-c + if ed.Type == core.EventTypeMessageConfirmed && ed.Message != nil && ed.Message.Header.Type == msgType { + t.Logf("Detected '%s' event for message '%s' of type '%s'", ed.Type, ed.Message.Header.ID, msgType) + return ed + } + t.Logf("Ignored event '%s'", ed.ID) + } +} + +func WaitForIdentityConfirmed(t *testing.T, c chan *core.EventDelivery) *core.EventDelivery { + for { + ed := <-c + if ed.Type == core.EventTypeIdentityConfirmed { + t.Logf("Detected '%s' event for identity '%s'", ed.Type, ed.Reference) + return ed + } + t.Logf("Ignored event '%s'", ed.ID) + } +} + +func WaitForContractEvent(t *testing.T, client *resty.Client, c chan *core.EventDelivery, match map[string]interface{}) map[string]interface{} { + for { + eventDelivery := <-c + if eventDelivery.Type == core.EventTypeBlockchainEventReceived { + event, err := GetBlockchainEvent(t, client, eventDelivery.Event.Reference.String()) + if err != nil { + t.Logf("WARN: unable to get event: %v", err.Error()) + continue + } + eventJSON, ok := event.(map[string]interface{}) + if !ok { + t.Logf("WARN: unable to parse changeEvent: %v", event) + continue + } + if checkObject(t, match, eventJSON) { + return eventJSON + } + } + } +} + +func checkObject(t *testing.T, expected interface{}, actual interface{}) bool { + match := true + + // check if this is a nested object + expectedObject, expectedIsObject := expected.(map[string]interface{}) + actualObject, actualIsObject := actual.(map[string]interface{}) + + t.Logf("Matching blockchain event: %s", fftypes.JSONObject(actualObject).String()) + + // check if this is an array + expectedArray, expectedIsArray := expected.([]interface{}) + actualArray, actualIsArray := actual.([]interface{}) + switch { + case expectedIsObject && actualIsObject: + for expectedKey, expectedValue := range expectedObject { + if !checkObject(t, expectedValue, actualObject[expectedKey]) { + return false + } + } + case expectedIsArray && actualIsArray: + for _, expectedItem := range expectedArray { + for j, actualItem := range actualArray { + if checkObject(t, expectedItem, actualItem) { + break + } + if j == len(actualArray)-1 { + return false + } + } + } + default: + expectedString, expectedIsString := expected.(string) + actualString, actualIsString := actual.(string) + if expectedIsString && actualIsString { + return strings.EqualFold(expectedString, actualString) + } + return expected == actual + } + return match +} + +func VerifyAllOperationsSucceeded(t *testing.T, clients []*resty.Client, startTime time.Time) { + tries := 3 + delay := 2 * time.Second + + var pending string + for i := 0; i < tries; i++ { + pending = "" + for _, client := range clients { + for _, op := range GetOperations(t, client, startTime) { + if op.Status != core.OpStatusSucceeded { + pending += fmt.Sprintf("Operation '%s' (%s) on '%s' is not successful\n", op.ID, op.Type, client.BaseURL) + } + } + } + if pending == "" { + return + } + time.Sleep(delay) + } + + assert.Fail(t, pending) +} diff --git a/test/e2e/gateway/ethereum_contract_test.go b/test/e2e/gateway/ethereum_contract_test.go new file mode 100644 index 0000000000..e00349ce48 --- /dev/null +++ b/test/e2e/gateway/ethereum_contract_test.go @@ -0,0 +1,311 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gateway + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/aidarkhanov/nanoid" + "github.com/go-resty/resty/v2" + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +var contractVersion, _ = nanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", nanoid.DefaultSize) + +type uploadABIResult struct { + ID string `json:"id"` +} + +type deployABIResult struct { + ContractAddress string `json:"contractAddress"` +} + +type ethconnectOutput struct { + Output string `json:"output"` +} + +type simpleStorageBody struct { + NewValue string `json:"newValue"` +} + +func simpleStorageFFIChanged() *fftypes.FFIEvent { + return &fftypes.FFIEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "Changed", + Params: fftypes.FFIParams{ + { + Name: "_from", + Schema: fftypes.JSONAnyPtr(`{"type": "string", "details": {"type": "address", "indexed": true}}`), + }, + { + Name: "_value", + Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`), + }, + }, + }, + } +} + +func simpleStorageFFI() *fftypes.FFI { + return &fftypes.FFI{ + Name: "SimpleStorage", + Version: contractVersion, + Methods: []*fftypes.FFIMethod{ + simpleStorageFFISet(), + simpleStorageFFIGet(), + }, + Events: []*fftypes.FFIEvent{ + simpleStorageFFIChanged(), + }, + } +} + +func simpleStorageFFISet() *fftypes.FFIMethod { + return &fftypes.FFIMethod{ + Name: "set", + Params: fftypes.FFIParams{ + { + Name: "newValue", + Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`), + }, + }, + Returns: fftypes.FFIParams{}, + } +} + +func simpleStorageFFIGet() *fftypes.FFIMethod { + return &fftypes.FFIMethod{ + Name: "get", + Params: fftypes.FFIParams{}, + Returns: fftypes.FFIParams{ + { + Name: "output", + Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`), + }, + }, + } +} + +type EthereumContractTestSuite struct { + suite.Suite + testState *testState + contractAddress string + interfaceID *fftypes.UUID + ethClient *resty.Client + ethIdentity string +} + +func (suite *EthereumContractTestSuite) SetupSuite() { + suite.testState = beforeE2ETest(suite.T()) + stack := e2e.ReadStack(suite.T()) + stackState := e2e.ReadStackState(suite.T()) + suite.ethClient = e2e.NewResty(suite.T()) + suite.ethClient.SetBaseURL(fmt.Sprintf("http://localhost:%d", stack.Members[0].ExposedConnectorPort)) + account := stackState.Accounts[0].(map[string]interface{}) + suite.ethIdentity = account["address"].(string) + suite.contractAddress = os.Getenv("CONTRACT_ADDRESS") + if suite.contractAddress == "" { + suite.T().Fatal("CONTRACT_ADDRESS must be set") + } + suite.T().Logf("contractAddress: %s", suite.contractAddress) + + res, err := e2e.CreateFFI(suite.T(), suite.testState.client1, simpleStorageFFI()) + suite.interfaceID = fftypes.MustParseUUID(res.(map[string]interface{})["id"].(string)) + suite.T().Logf("interfaceID: %s", suite.interfaceID) + assert.NoError(suite.T(), err) +} + +func (suite *EthereumContractTestSuite) BeforeTest(suiteName, testName string) { + suite.testState = beforeE2ETest(suite.T()) +} + +func (suite *EthereumContractTestSuite) AfterTest(suiteName, testName string) { + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1}, suite.testState.startTime) +} + +func (suite *EthereumContractTestSuite) TestDirectInvokeMethod() { + defer suite.testState.Done() + + received1 := e2e.WsReader(suite.testState.ws1, true) + listener := e2e.CreateContractListener(suite.T(), suite.testState.client1, simpleStorageFFIChanged(), &fftypes.JSONObject{ + "address": suite.contractAddress, + }) + + listeners := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(listeners)) + assert.Equal(suite.T(), listener.BackendID, listeners[0].BackendID) + + location := map[string]interface{}{ + "address": suite.contractAddress, + } + locationBytes, _ := json.Marshal(location) + invokeContractRequest := &core.ContractCallRequest{ + Location: fftypes.JSONAnyPtrBytes(locationBytes), + Method: simpleStorageFFISet(), + Input: map[string]interface{}{ + "newValue": float64(2), + }, + } + + res, err := e2e.InvokeContractMethod(suite.T(), suite.testState.client1, invokeContractRequest) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), res) + + match := map[string]interface{}{ + "info": map[string]interface{}{ + "address": suite.contractAddress, + }, + "output": map[string]interface{}{ + "_value": "2", + "_from": suite.ethIdentity, + }, + "listener": listener.ID.String(), + } + + event := e2e.WaitForContractEvent(suite.T(), suite.testState.client1, received1, match) + assert.NotNil(suite.T(), event) + + queryContractRequest := &core.ContractCallRequest{ + Location: fftypes.JSONAnyPtrBytes(locationBytes), + Method: simpleStorageFFIGet(), + } + res, err = e2e.QueryContractMethod(suite.T(), suite.testState.client1, queryContractRequest) + assert.NoError(suite.T(), err) + resJSON, err := json.Marshal(res) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), `{"output":"2"}`, string(resJSON)) + e2e.DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) +} + +func (suite *EthereumContractTestSuite) TestFFIInvokeMethod() { + defer suite.testState.Done() + + received1 := e2e.WsReader(suite.testState.ws1, true) + + ffiReference := &fftypes.FFIReference{ + ID: suite.interfaceID, + } + listener := e2e.CreateFFIContractListener(suite.T(), suite.testState.client1, ffiReference, "Changed", &fftypes.JSONObject{ + "address": suite.contractAddress, + }) + + listeners := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(listeners)) + assert.Equal(suite.T(), listener.BackendID, listeners[0].BackendID) + + location := map[string]interface{}{ + "address": suite.contractAddress, + } + locationBytes, _ := json.Marshal(location) + invokeContractRequest := &core.ContractCallRequest{ + Location: fftypes.JSONAnyPtrBytes(locationBytes), + Input: map[string]interface{}{ + "newValue": float64(42), + }, + Interface: suite.interfaceID, + MethodPath: "set", + } + + res, err := e2e.InvokeContractMethod(suite.T(), suite.testState.client1, invokeContractRequest) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), res) + + match := map[string]interface{}{ + "info": map[string]interface{}{ + "address": suite.contractAddress, + }, + "output": map[string]interface{}{ + "_value": "42", + "_from": suite.ethIdentity, + }, + "listener": listener.ID.String(), + } + event := e2e.WaitForContractEvent(suite.T(), suite.testState.client1, received1, match) + assert.NotNil(suite.T(), event) + + queryContractRequest := &core.ContractCallRequest{ + Location: fftypes.JSONAnyPtrBytes(locationBytes), + Interface: suite.interfaceID, + MethodPath: "get", + } + res, err = e2e.QueryContractMethod(suite.T(), suite.testState.client1, queryContractRequest) + assert.NoError(suite.T(), err) + resJSON, err := json.Marshal(res) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), `{"output":"42"}`, string(resJSON)) + e2e.DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) +} + +func (suite *EthereumContractTestSuite) TestContractAPIMethod() { + defer suite.testState.Done() + + received1 := e2e.WsReader(suite.testState.ws1, true) + APIName := fftypes.NewUUID().String() + + ffiReference := &fftypes.FFIReference{ + ID: suite.interfaceID, + } + + location := map[string]interface{}{ + "address": suite.contractAddress, + } + locationBytes, _ := json.Marshal(location) + + createContractAPIResult, err := e2e.CreateContractAPI(suite.T(), suite.testState.client1, APIName, ffiReference, fftypes.JSONAnyPtr(string(locationBytes))) + assert.NotNil(suite.T(), createContractAPIResult) + assert.NoError(suite.T(), err) + + listener, err := e2e.CreateContractAPIListener(suite.T(), suite.testState.client1, APIName, "Changed", "firefly_e2e") + assert.NoError(suite.T(), err) + + listeners := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(listeners)) + assert.Equal(suite.T(), listener.BackendID, listeners[0].BackendID) + + input := fftypes.JSONAny(`{"newValue": 42}`) + invokeResult, err := e2e.InvokeContractAPIMethod(suite.T(), suite.testState.client1, APIName, "set", &input) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), invokeResult) + + match := map[string]interface{}{ + "info": map[string]interface{}{ + "address": suite.contractAddress, + }, + "output": map[string]interface{}{ + "_value": "42", + "_from": suite.ethIdentity, + }, + "listener": listener.ID.String(), + } + event := e2e.WaitForContractEvent(suite.T(), suite.testState.client1, received1, match) + assert.NotNil(suite.T(), event) + assert.NoError(suite.T(), err) + + res, err := e2e.QueryContractAPIMethod(suite.T(), suite.testState.client1, APIName, "get", fftypes.JSONAnyPtr("{}")) + assert.NoError(suite.T(), err) + resJSON, err := json.Marshal(res) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), `{"output":"42"}`, string(resJSON)) + + e2e.DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) +} diff --git a/test/e2e/gateway/ethereum_test.go b/test/e2e/gateway/ethereum_test.go new file mode 100644 index 0000000000..7ce541258b --- /dev/null +++ b/test/e2e/gateway/ethereum_test.go @@ -0,0 +1,28 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestEthereumGatewayE2ESuite(t *testing.T) { + suite.Run(t, new(TokensTestSuite)) + suite.Run(t, new(EthereumContractTestSuite)) +} diff --git a/test/e2e/fabric_contract_test.go b/test/e2e/gateway/fabric_contract_test.go similarity index 81% rename from test/e2e/fabric_contract_test.go rename to test/e2e/gateway/fabric_contract_test.go index 366035783a..ff2c5d85e6 100644 --- a/test/e2e/fabric_contract_test.go +++ b/test/e2e/gateway/fabric_contract_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package gateway import ( "encoding/json" @@ -27,6 +27,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -87,7 +88,7 @@ func deployChaincode(t *testing.T, stackName string) string { assert.NoError(t, err) chaincodeName := "e2e_" + id - cmd := exec.Command("bash", "./deploy_chaincode.sh") + cmd := exec.Command("bash", "../deploy_chaincode.sh") cmd.Env = append(cmd.Env, "STACK_NAME="+stackName) cmd.Env = append(cmd.Env, "CHAINCODE_NAME="+chaincodeName) cmd.Env = append(cmd.Env, "PATH="+os.Getenv("PATH")) @@ -127,10 +128,10 @@ type FabricContractTestSuite struct { } func (suite *FabricContractTestSuite) SetupSuite() { - stack := readStackFile(suite.T()) + stack := e2e.ReadStack(suite.T()) suite.chaincodeName = deployChaincode(suite.T(), stack.Name) - suite.fabClient = NewResty(suite.T()) + suite.fabClient = e2e.NewResty(suite.T()) suite.fabClient.SetBaseURL(fmt.Sprintf("http://localhost:%d", stack.Members[0].ExposedConnectorPort)) } @@ -139,20 +140,20 @@ func (suite *FabricContractTestSuite) BeforeTest(suiteName, testName string) { } func (suite *FabricContractTestSuite) AfterTest(suiteName, testName string) { - verifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1}, suite.testState.startTime) } func (suite *FabricContractTestSuite) TestE2EContractEvents() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, true) + received1 := e2e.WsReader(suite.testState.ws1, true) - sub := CreateContractListener(suite.T(), suite.testState.client1, assetCreatedEvent, &fftypes.JSONObject{ + sub := e2e.CreateContractListener(suite.T(), suite.testState.client1, assetCreatedEvent, &fftypes.JSONObject{ "channel": "firefly", "chaincode": suite.chaincodeName, }) - subs := GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + subs := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(subs)) assert.Equal(suite.T(), sub.BackendID, subs[0].BackendID) @@ -170,13 +171,13 @@ func (suite *FabricContractTestSuite) TestE2EContractEvents() { }, } - res, err := InvokeContractMethod(suite.testState.t, suite.testState.client1, invokeContractRequest) + res, err := e2e.InvokeContractMethod(suite.testState.t, suite.testState.client1, invokeContractRequest) suite.T().Log(res) assert.NoError(suite.T(), err) <-received1 - events := GetContractEvents(suite.T(), suite.testState.client1, suite.testState.startTime, sub.ID) + events := e2e.GetContractEvents(suite.T(), suite.testState.client1, suite.testState.startTime, sub.ID) assert.Equal(suite.T(), 1, len(events)) assert.Equal(suite.T(), "AssetCreated", events[0].Name) assert.Equal(suite.T(), assetName, events[0].Output.GetString("name")) @@ -189,13 +190,13 @@ func (suite *FabricContractTestSuite) TestE2EContractEvents() { }, } - res, err = QueryContractMethod(suite.testState.t, suite.testState.client1, queryContractRequest) + res, err = e2e.QueryContractMethod(suite.testState.t, suite.testState.client1, queryContractRequest) suite.T().Log(res) assert.NoError(suite.T(), err) assert.Equal(suite.T(), assetName, res.(map[string]interface{})["name"]) - DeleteContractListener(suite.T(), suite.testState.client1, subs[0].ID) - subs = GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + e2e.DeleteContractListener(suite.T(), suite.testState.client1, subs[0].ID) + subs = e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 0, len(subs)) } diff --git a/test/e2e/gateway/fabric_test.go b/test/e2e/gateway/fabric_test.go new file mode 100644 index 0000000000..0021278b54 --- /dev/null +++ b/test/e2e/gateway/fabric_test.go @@ -0,0 +1,27 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestFabricGatewayE2ESuite(t *testing.T) { + suite.Run(t, new(FabricContractTestSuite)) +} diff --git a/test/e2e/gateway/gateway_test.go b/test/e2e/gateway/gateway_test.go new file mode 100644 index 0000000000..81143efb95 --- /dev/null +++ b/test/e2e/gateway/gateway_test.go @@ -0,0 +1,128 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gateway + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + "os" + "testing" + "time" + + "github.com/go-resty/resty/v2" + "github.com/gorilla/websocket" + "github.com/hyperledger/firefly/test/e2e" + "github.com/stretchr/testify/require" +) + +type testState struct { + t *testing.T + startTime time.Time + done func() + ws1 *websocket.Conn + client1 *resty.Client + unregisteredAccounts []interface{} + namespace string +} + +func (m *testState) T() *testing.T { + return m.t +} + +func (m *testState) StartTime() time.Time { + return m.startTime +} + +func (m *testState) Done() func() { + return m.done +} + +func beforeE2ETest(t *testing.T) *testState { + stack := e2e.ReadStack(t) + stackState := e2e.ReadStackState(t) + namespace := "default" + + var authHeader1 http.Header + + ts := &testState{ + t: t, + startTime: time.Now(), + client1: e2e.NewResty(t), + unregisteredAccounts: stackState.Accounts[2:], + namespace: namespace, + } + + httpProtocolClient1 := "http" + websocketProtocolClient1 := "ws" + if stack.Members[0].UseHTTPS { + httpProtocolClient1 = "https" + websocketProtocolClient1 = "wss" + } + + member0WithPort := "" + if stack.Members[0].ExposedFireflyPort != 0 { + member0WithPort = fmt.Sprintf(":%d", stack.Members[0].ExposedFireflyPort) + } + + ts.client1.SetBaseURL(fmt.Sprintf("%s://%s%s/api/v1", httpProtocolClient1, stack.Members[0].FireflyHostname, member0WithPort)) + + t.Logf("Blockchain provider: %s", stack.BlockchainProvider) + + if stack.Members[0].Username != "" && stack.Members[0].Password != "" { + t.Log("Setting auth for user 1") + ts.client1.SetBasicAuth(stack.Members[0].Username, stack.Members[0].Password) + authHeader1 = http.Header{ + "Authorization": []string{fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", stack.Members[0].Username, stack.Members[0].Password))))}, + } + } + + // If no namespace is set to run tests against, use the default namespace + if os.Getenv("NAMESPACE") != "" { + namespace := os.Getenv("NAMESPACE") + ts.namespace = namespace + } + + t.Logf("Client 1: " + ts.client1.HostURL) + e2e.PollForUp(t, ts.client1) + + eventNames := "message_confirmed|token_pool_confirmed|token_transfer_confirmed|blockchain_event_received|token_approval_confirmed|identity_confirmed" + queryString := fmt.Sprintf("namespace=%s&ephemeral&autoack&filter.events=%s&changeevents=.*", ts.namespace, eventNames) + + wsUrl1 := url.URL{ + Scheme: websocketProtocolClient1, + Host: fmt.Sprintf("%s%s", stack.Members[0].FireflyHostname, member0WithPort), + Path: "/ws", + RawQuery: queryString, + } + + t.Logf("Websocket 1: " + wsUrl1.String()) + + var err error + ts.ws1, _, err = websocket.DefaultDialer.Dial(wsUrl1.String(), authHeader1) + if err != nil { + t.Logf(err.Error()) + } + require.NoError(t, err) + + ts.done = func() { + ts.ws1.Close() + t.Log("WebSockets closed") + } + return ts +} diff --git a/test/e2e/gateway/tokens_test.go b/test/e2e/gateway/tokens_test.go new file mode 100644 index 0000000000..d5c89cf8c8 --- /dev/null +++ b/test/e2e/gateway/tokens_test.go @@ -0,0 +1,103 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gateway + +import ( + "fmt" + "math/rand" + "time" + + "github.com/go-resty/resty/v2" + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type TokensTestSuite struct { + suite.Suite + testState *testState + connector string + key string +} + +func (suite *TokensTestSuite) SetupSuite() { + suite.testState = beforeE2ETest(suite.T()) + stack := e2e.ReadStack(suite.T()) + stackState := e2e.ReadStackState(suite.T()) + suite.connector = stack.TokenProviders[0] + account := stackState.Accounts[0].(map[string]interface{}) + suite.key = account["address"].(string) +} + +func (suite *TokensTestSuite) BeforeTest(suiteName, testName string) { + suite.testState = beforeE2ETest(suite.T()) +} + +func (suite *TokensTestSuite) AfterTest(suiteName, testName string) { + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1}, suite.testState.startTime) +} + +func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { + defer suite.testState.done() + + received1 := e2e.WsReader(suite.testState.ws1, false) + + pools := e2e.GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) + rand.Seed(time.Now().UnixNano()) + poolName := fmt.Sprintf("pool%d", rand.Intn(10000)) + suite.T().Logf("Pool name: %s", poolName) + + pool := &core.TokenPool{ + Name: poolName, + Type: core.TokenTypeFungible, + Config: fftypes.JSONObject{}, + } + + poolResp := e2e.CreateTokenPool(suite.T(), suite.testState.client1, pool, false) + poolID := poolResp.ID + + e2e.WaitForEvent(suite.T(), received1, core.EventTypePoolConfirmed, poolID) + pools = e2e.GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(pools)) + assert.Equal(suite.T(), suite.testState.namespace, pools[0].Namespace) + assert.Equal(suite.T(), suite.connector, pools[0].Connector) + assert.Equal(suite.T(), poolName, pools[0].Name) + assert.Equal(suite.T(), core.TokenTypeFungible, pools[0].Type) + assert.NotEmpty(suite.T(), pools[0].Locator) + + transfer := &core.TokenTransferInput{ + TokenTransfer: core.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)}, + Pool: poolName, + } + transferOut := e2e.MintTokens(suite.T(), suite.testState.client1, transfer, false) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, transferOut.LocalID) + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ + suite.key: 1, + }) + + transfer = &core.TokenTransferInput{ + TokenTransfer: core.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)}, + Pool: poolName, + } + transferOut = e2e.BurnTokens(suite.T(), suite.testState.client1, transfer, false) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, transferOut.LocalID) + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ + suite.key: 0, + }) +} diff --git a/test/e2e/ethereum_contract_test.go b/test/e2e/multiparty/ethereum_contract_test.go similarity index 69% rename from test/e2e/ethereum_contract_test.go rename to test/e2e/multiparty/ethereum_contract_test.go index b5d7db5c5a..7954eac384 100644 --- a/test/e2e/ethereum_contract_test.go +++ b/test/e2e/multiparty/ethereum_contract_test.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "encoding/json" @@ -25,6 +25,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -116,8 +117,8 @@ type EthereumContractTestSuite struct { func (suite *EthereumContractTestSuite) SetupSuite() { suite.testState = beforeE2ETest(suite.T()) - stack := readStackFile(suite.T()) - suite.ethClient = NewResty(suite.T()) + stack := e2e.ReadStack(suite.T()) + suite.ethClient = e2e.NewResty(suite.T()) suite.ethClient.SetBaseURL(fmt.Sprintf("http://localhost:%d", stack.Members[0].ExposedConnectorPort)) suite.ethIdentity = suite.testState.org1key.Value suite.contractAddress = os.Getenv("CONTRACT_ADDRESS") @@ -126,7 +127,7 @@ func (suite *EthereumContractTestSuite) SetupSuite() { } suite.T().Logf("contractAddress: %s", suite.contractAddress) - res, err := CreateFFI(suite.T(), suite.testState.client1, simpleStorageFFI()) + res, err := e2e.CreateFFI(suite.T(), suite.testState.client1, simpleStorageFFI()) suite.interfaceID = fftypes.MustParseUUID(res.(map[string]interface{})["id"].(string)) suite.T().Logf("interfaceID: %s", suite.interfaceID) assert.NoError(suite.T(), err) @@ -137,18 +138,18 @@ func (suite *EthereumContractTestSuite) BeforeTest(suiteName, testName string) { } func (suite *EthereumContractTestSuite) AfterTest(suiteName, testName string) { - verifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) } func (suite *EthereumContractTestSuite) TestDirectInvokeMethod() { - defer suite.testState.done() + defer suite.testState.Done() - received1 := wsReader(suite.testState.ws1, true) - listener := CreateContractListener(suite.T(), suite.testState.client1, simpleStorageFFIChanged(), &fftypes.JSONObject{ + received1 := e2e.WsReader(suite.testState.ws1, true) + listener := e2e.CreateContractListener(suite.T(), suite.testState.client1, simpleStorageFFIChanged(), &fftypes.JSONObject{ "address": suite.contractAddress, }) - listeners := GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + listeners := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(listeners)) assert.Equal(suite.T(), listener.BackendID, listeners[0].BackendID) @@ -164,7 +165,7 @@ func (suite *EthereumContractTestSuite) TestDirectInvokeMethod() { }, } - res, err := InvokeContractMethod(suite.testState.t, suite.testState.client1, invokeContractRequest) + res, err := e2e.InvokeContractMethod(suite.T(), suite.testState.client1, invokeContractRequest) assert.NoError(suite.T(), err) assert.NotNil(suite.T(), res) @@ -179,34 +180,34 @@ func (suite *EthereumContractTestSuite) TestDirectInvokeMethod() { "listener": listener.ID.String(), } - event := waitForContractEvent(suite.T(), suite.testState.client1, received1, match) + event := e2e.WaitForContractEvent(suite.T(), suite.testState.client1, received1, match) assert.NotNil(suite.T(), event) queryContractRequest := &core.ContractCallRequest{ Location: fftypes.JSONAnyPtrBytes(locationBytes), Method: simpleStorageFFIGet(), } - res, err = QueryContractMethod(suite.testState.t, suite.testState.client1, queryContractRequest) - assert.NoError(suite.testState.t, err) + res, err = e2e.QueryContractMethod(suite.T(), suite.testState.client1, queryContractRequest) + assert.NoError(suite.T(), err) resJSON, err := json.Marshal(res) - assert.NoError(suite.testState.t, err) - assert.Equal(suite.testState.t, `{"output":"2"}`, string(resJSON)) - DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), `{"output":"2"}`, string(resJSON)) + e2e.DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) } func (suite *EthereumContractTestSuite) TestFFIInvokeMethod() { - defer suite.testState.done() + defer suite.testState.Done() - received1 := wsReader(suite.testState.ws1, true) + received1 := e2e.WsReader(suite.testState.ws1, true) ffiReference := &fftypes.FFIReference{ ID: suite.interfaceID, } - listener := CreateFFIContractListener(suite.T(), suite.testState.client1, ffiReference, "Changed", &fftypes.JSONObject{ + listener := e2e.CreateFFIContractListener(suite.T(), suite.testState.client1, ffiReference, "Changed", &fftypes.JSONObject{ "address": suite.contractAddress, }) - listeners := GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + listeners := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(listeners)) assert.Equal(suite.T(), listener.BackendID, listeners[0].BackendID) @@ -223,9 +224,9 @@ func (suite *EthereumContractTestSuite) TestFFIInvokeMethod() { MethodPath: "set", } - res, err := InvokeContractMethod(suite.testState.t, suite.testState.client1, invokeContractRequest) - assert.NoError(suite.testState.t, err) - assert.NotNil(suite.testState.t, res) + res, err := e2e.InvokeContractMethod(suite.T(), suite.testState.client1, invokeContractRequest) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), res) match := map[string]interface{}{ "info": map[string]interface{}{ @@ -237,7 +238,7 @@ func (suite *EthereumContractTestSuite) TestFFIInvokeMethod() { }, "listener": listener.ID.String(), } - event := waitForContractEvent(suite.T(), suite.testState.client1, received1, match) + event := e2e.WaitForContractEvent(suite.T(), suite.testState.client1, received1, match) assert.NotNil(suite.T(), event) queryContractRequest := &core.ContractCallRequest{ @@ -245,18 +246,18 @@ func (suite *EthereumContractTestSuite) TestFFIInvokeMethod() { Interface: suite.interfaceID, MethodPath: "get", } - res, err = QueryContractMethod(suite.testState.t, suite.testState.client1, queryContractRequest) - assert.NoError(suite.testState.t, err) + res, err = e2e.QueryContractMethod(suite.T(), suite.testState.client1, queryContractRequest) + assert.NoError(suite.T(), err) resJSON, err := json.Marshal(res) - assert.NoError(suite.testState.t, err) - assert.Equal(suite.testState.t, `{"output":"42"}`, string(resJSON)) - DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), `{"output":"42"}`, string(resJSON)) + e2e.DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) } func (suite *EthereumContractTestSuite) TestContractAPIMethod() { - defer suite.testState.done() + defer suite.testState.Done() - received1 := wsReader(suite.testState.ws1, true) + received1 := e2e.WsReader(suite.testState.ws1, true) APIName := fftypes.NewUUID().String() ffiReference := &fftypes.FFIReference{ @@ -268,20 +269,20 @@ func (suite *EthereumContractTestSuite) TestContractAPIMethod() { } locationBytes, _ := json.Marshal(location) - createContractAPIResult, err := CreateContractAPI(suite.T(), suite.testState.client1, APIName, ffiReference, fftypes.JSONAnyPtr(string(locationBytes))) + createContractAPIResult, err := e2e.CreateContractAPI(suite.T(), suite.testState.client1, APIName, ffiReference, fftypes.JSONAnyPtr(string(locationBytes))) assert.NotNil(suite.T(), createContractAPIResult) assert.NoError(suite.T(), err) - listener, err := CreateContractAPIListener(suite.T(), suite.testState.client1, APIName, "Changed", "firefly_e2e") + listener, err := e2e.CreateContractAPIListener(suite.T(), suite.testState.client1, APIName, "Changed", "firefly_e2e") assert.NoError(suite.T(), err) - listeners := GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + listeners := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(listeners)) assert.Equal(suite.T(), listener.BackendID, listeners[0].BackendID) input := fftypes.JSONAny(`{"newValue": 42}`) - invokeResult, err := InvokeContractAPIMethod(suite.T(), suite.testState.client1, APIName, "set", &input) - assert.NoError(suite.testState.t, err) + invokeResult, err := e2e.InvokeContractAPIMethod(suite.T(), suite.testState.client1, APIName, "set", &input) + assert.NoError(suite.T(), err) assert.NotNil(suite.T(), invokeResult) match := map[string]interface{}{ @@ -294,15 +295,15 @@ func (suite *EthereumContractTestSuite) TestContractAPIMethod() { }, "listener": listener.ID.String(), } - event := waitForContractEvent(suite.T(), suite.testState.client1, received1, match) + event := e2e.WaitForContractEvent(suite.T(), suite.testState.client1, received1, match) assert.NotNil(suite.T(), event) - assert.NoError(suite.testState.t, err) + assert.NoError(suite.T(), err) - res, err := QueryContractAPIMethod(suite.T(), suite.testState.client1, APIName, "get", fftypes.JSONAnyPtr("{}")) - assert.NoError(suite.testState.t, err) + res, err := e2e.QueryContractAPIMethod(suite.T(), suite.testState.client1, APIName, "get", fftypes.JSONAnyPtr("{}")) + assert.NoError(suite.T(), err) resJSON, err := json.Marshal(res) - assert.NoError(suite.testState.t, err) - assert.Equal(suite.testState.t, `{"output":"42"}`, string(resJSON)) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), `{"output":"42"}`, string(resJSON)) - DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) + e2e.DeleteContractListener(suite.T(), suite.testState.client1, listener.ID) } diff --git a/test/e2e/ethereum_namespace_test.go b/test/e2e/multiparty/ethereum_namespace_test.go similarity index 93% rename from test/e2e/ethereum_namespace_test.go rename to test/e2e/multiparty/ethereum_namespace_test.go index da54f4fad5..095251ee82 100644 --- a/test/e2e/ethereum_namespace_test.go +++ b/test/e2e/multiparty/ethereum_namespace_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "testing" diff --git a/test/e2e/ethereum_test.go b/test/e2e/multiparty/ethereum_test.go similarity index 88% rename from test/e2e/ethereum_test.go rename to test/e2e/multiparty/ethereum_test.go index 5e54dc617b..6aa380d8a6 100644 --- a/test/e2e/ethereum_test.go +++ b/test/e2e/multiparty/ethereum_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "testing" @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/suite" ) -func TestEthereumE2ESuite(t *testing.T) { +func TestEthereumMultipartyE2ESuite(t *testing.T) { suite.Run(t, new(IdentityTestSuite)) suite.Run(t, new(OnChainOffChainTestSuite)) suite.Run(t, new(TokensTestSuite)) diff --git a/test/e2e/multiparty/fabric_contract_test.go b/test/e2e/multiparty/fabric_contract_test.go new file mode 100644 index 0000000000..35e817becf --- /dev/null +++ b/test/e2e/multiparty/fabric_contract_test.go @@ -0,0 +1,202 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package multiparty + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "testing" + + "github.com/aidarkhanov/nanoid" + "github.com/go-resty/resty/v2" + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type fabConnectHeaders struct { + Type string `json:"type"` + Channel string `json:"channel"` + Signer string `json:"signer"` + Chaincode string `json:"chaincode"` +} + +type createAssetBody struct { + Headers fabConnectHeaders `json:"headers"` + Func string `json:"func"` + Args []string `json:"args"` +} + +var assetCreatedEvent = &fftypes.FFIEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "AssetCreated", + }, +} + +func assetManagerCreateAsset() *fftypes.FFIMethod { + return &fftypes.FFIMethod{ + Name: "CreateAsset", + Params: fftypes.FFIParams{ + { + Name: "name", + Schema: fftypes.JSONAnyPtr(`{"type": "string"}`), + }, + }, + Returns: fftypes.FFIParams{}, + } +} + +func assetManagerGetAsset() *fftypes.FFIMethod { + return &fftypes.FFIMethod{ + Name: "GetAsset", + Params: fftypes.FFIParams{ + { + Name: "name", + Schema: fftypes.JSONAnyPtr(`{"type": "string"}`), + }, + }, + Returns: fftypes.FFIParams{ + { + Name: "name", + Schema: fftypes.JSONAnyPtr(`{"type": "string"}`), + }, + }, + } +} + +func deployChaincode(t *testing.T, stackName string) string { + id, err := nanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", nanoid.DefaultSize) + assert.NoError(t, err) + chaincodeName := "e2e_" + id + + cmd := exec.Command("bash", "../deploy_chaincode.sh") + cmd.Env = append(cmd.Env, "STACK_NAME="+stackName) + cmd.Env = append(cmd.Env, "CHAINCODE_NAME="+chaincodeName) + cmd.Env = append(cmd.Env, "PATH="+os.Getenv("PATH")) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + require.NoError(t, err) + + return chaincodeName +} + +func invokeFabContract(t *testing.T, client *resty.Client, channel, chaincode, signer, method string, args []string) { + path := "/transactions" + body := &createAssetBody{ + Headers: fabConnectHeaders{ + Type: "SendTransaction", + Channel: channel, + Signer: signer, + Chaincode: chaincode, + }, + Func: method, + Args: args, + } + resp, err := client.R(). + SetHeader("x-firefly-sync", "true"). + SetBody(body). + Post(path) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode(), "POST %s [%d]: %s", path, resp.StatusCode(), resp.String()) +} + +type FabricContractTestSuite struct { + suite.Suite + testState *testState + chaincodeName string + fabClient *resty.Client +} + +func (suite *FabricContractTestSuite) SetupSuite() { + stack := e2e.ReadStack(suite.T()) + suite.chaincodeName = deployChaincode(suite.T(), stack.Name) + + suite.fabClient = e2e.NewResty(suite.T()) + suite.fabClient.SetBaseURL(fmt.Sprintf("http://localhost:%d", stack.Members[0].ExposedConnectorPort)) +} + +func (suite *FabricContractTestSuite) BeforeTest(suiteName, testName string) { + suite.testState = beforeE2ETest(suite.T()) +} + +func (suite *FabricContractTestSuite) AfterTest(suiteName, testName string) { + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) +} + +func (suite *FabricContractTestSuite) TestE2EContractEvents() { + defer suite.testState.done() + + received1 := e2e.WsReader(suite.testState.ws1, true) + + sub := e2e.CreateContractListener(suite.T(), suite.testState.client1, assetCreatedEvent, &fftypes.JSONObject{ + "channel": "firefly", + "chaincode": suite.chaincodeName, + }) + + subs := e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(subs)) + assert.Equal(suite.T(), sub.BackendID, subs[0].BackendID) + + assetName := nanoid.New() + location := map[string]interface{}{ + "chaincode": suite.chaincodeName, + "channel": "firefly", + } + locationBytes, _ := json.Marshal(location) + invokeContractRequest := &core.ContractCallRequest{ + Location: fftypes.JSONAnyPtrBytes(locationBytes), + Method: assetManagerCreateAsset(), + Input: map[string]interface{}{ + "name": assetName, + }, + } + + res, err := e2e.InvokeContractMethod(suite.testState.t, suite.testState.client1, invokeContractRequest) + suite.T().Log(res) + assert.NoError(suite.T(), err) + + <-received1 + + events := e2e.GetContractEvents(suite.T(), suite.testState.client1, suite.testState.startTime, sub.ID) + assert.Equal(suite.T(), 1, len(events)) + assert.Equal(suite.T(), "AssetCreated", events[0].Name) + assert.Equal(suite.T(), assetName, events[0].Output.GetString("name")) + + queryContractRequest := &core.ContractCallRequest{ + Location: fftypes.JSONAnyPtrBytes(locationBytes), + Method: assetManagerGetAsset(), + Input: map[string]interface{}{ + "name": assetName, + }, + } + + res, err = e2e.QueryContractMethod(suite.testState.t, suite.testState.client1, queryContractRequest) + suite.T().Log(res) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), assetName, res.(map[string]interface{})["name"]) + + e2e.DeleteContractListener(suite.T(), suite.testState.client1, subs[0].ID) + subs = e2e.GetContractListeners(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 0, len(subs)) + +} diff --git a/test/e2e/fabric_test.go b/test/e2e/multiparty/fabric_test.go similarity index 88% rename from test/e2e/fabric_test.go rename to test/e2e/multiparty/fabric_test.go index 9970f651d7..dfbf6a932f 100644 --- a/test/e2e/fabric_test.go +++ b/test/e2e/multiparty/fabric_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "testing" @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/suite" ) -func TestFabricE2ESuite(t *testing.T) { +func TestFabricMultipartyE2ESuite(t *testing.T) { suite.Run(t, new(IdentityTestSuite)) suite.Run(t, new(OnChainOffChainTestSuite)) suite.Run(t, new(FabricContractTestSuite)) diff --git a/test/e2e/identity_test.go b/test/e2e/multiparty/identity_test.go similarity index 72% rename from test/e2e/identity_test.go rename to test/e2e/multiparty/identity_test.go index 44fda88cc7..38c5ddc94c 100644 --- a/test/e2e/identity_test.go +++ b/test/e2e/multiparty/identity_test.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "context" @@ -25,6 +25,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -40,21 +41,21 @@ func (suite *IdentityTestSuite) BeforeTest(suiteName, testName string) { } func (suite *IdentityTestSuite) AfterTest(suiteName, testName string) { - verifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) } func (suite *IdentityTestSuite) TestCustomChildIdentityBroadcasts() { defer suite.testState.done() ctx := context.Background() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) totalIdentities := 2 ts := time.Now().Unix() for i := 0; i < totalIdentities; i++ { key := getUnregisteredAccount(suite, suite.testState.org1.Name) - ClaimCustomIdentity(suite.T(), + e2e.ClaimCustomIdentity(suite.T(), suite.testState.client1, key, fmt.Sprintf("custom_%d_%d", ts, i), @@ -66,10 +67,10 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityBroadcasts() { identityIDs := make(map[fftypes.UUID]bool) for i := 0; i < totalIdentities; i++ { - ed := waitForIdentityConfirmed(suite.T(), received1) + ed := e2e.WaitForIdentityConfirmed(suite.T(), received1) identityIDs[*ed.Reference] = true suite.T().Logf("Received node 1 confirmation of identity %s", ed.Reference) - ed = waitForIdentityConfirmed(suite.T(), received2) + ed = e2e.WaitForIdentityConfirmed(suite.T(), received2) identityIDs[*ed.Reference] = true suite.T().Logf("Received node 2 confirmation of identity %s", ed.Reference) } @@ -77,23 +78,23 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityBroadcasts() { identities := make(map[string]*core.Identity) for identityID := range identityIDs { - identityNode1 := GetIdentity(suite.T(), suite.testState.client1, &identityID) - identityNode2 := GetIdentity(suite.T(), suite.testState.client1, &identityID) + identityNode1 := e2e.GetIdentity(suite.T(), suite.testState.client1, &identityID) + identityNode2 := e2e.GetIdentity(suite.T(), suite.testState.client1, &identityID) assert.True(suite.T(), identityNode1.IdentityBase.Equals(ctx, &identityNode2.IdentityBase)) identities[identityNode1.DID] = identityNode1 } // Send a broadcast from each custom identity for did := range identities { - resp, err := BroadcastMessageAsIdentity(suite.T(), suite.testState.client1, did, "identitytest", &core.DataRefOrValue{ + resp, err := e2e.BroadcastMessageAsIdentity(suite.T(), suite.testState.client1, did, "identitytest", &core.DataRefOrValue{ Value: fftypes.JSONAnyPtr(`{"some": "data"}`), }, false) require.NoError(suite.T(), err) assert.Equal(suite.T(), 202, resp.StatusCode()) } for range identities { - waitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) } } @@ -101,14 +102,14 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityBroadcasts() { func (suite *IdentityTestSuite) TestCustomChildIdentityPrivate() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) org1key := getUnregisteredAccount(suite, suite.testState.org1.Name) org2key := getUnregisteredAccount(suite, suite.testState.org2.Name) ts := time.Now().Unix() - custom1 := ClaimCustomIdentity(suite.T(), + custom1 := e2e.ClaimCustomIdentity(suite.T(), suite.testState.client1, org1key, fmt.Sprintf("custom_%d_org1priv", ts), @@ -116,7 +117,7 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityPrivate() { nil, suite.testState.org1.ID, true) - custom2 := ClaimCustomIdentity(suite.T(), + custom2 := e2e.ClaimCustomIdentity(suite.T(), suite.testState.client2, org2key, fmt.Sprintf("custom_%d_org2priv", ts), @@ -125,25 +126,25 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityPrivate() { suite.testState.org2.ID, true) for i := 0; i < 2; i++ { - waitForIdentityConfirmed(suite.T(), received1) - waitForIdentityConfirmed(suite.T(), received2) + e2e.WaitForIdentityConfirmed(suite.T(), received1) + e2e.WaitForIdentityConfirmed(suite.T(), received2) } - resp, err := PrivateMessageWithKey(suite.testState, suite.testState.client1, org1key, "topic1", &core.DataRefOrValue{ + resp, err := e2e.PrivateMessageWithKey(suite.testState, suite.testState.client1, org1key, "topic1", &core.DataRefOrValue{ Value: fftypes.JSONAnyPtr(`"test private custom identity"`), }, []string{custom1.DID, custom2.DID}, "tag1", core.TransactionTypeBatchPin, true) require.NoError(suite.T(), err) assert.Equal(suite.T(), 200, resp.StatusCode()) - waitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) } func getUnregisteredAccount(suite *IdentityTestSuite, orgName string) string { - verifiers := GetVerifiers(suite.T(), suite.testState.client1) + verifiers := e2e.GetVerifiers(suite.T(), suite.testState.client1) suite.T().Logf("checking for account with orgName: %s", orgName) for i, account := range suite.testState.unregisteredAccounts { - alreadyRegisted := false + alreadyRegistered := false accountMap := account.(map[string]interface{}) suite.T().Logf("account from stackState has orgName: %s", accountMap["orgName"]) if accountOrgName, ok := accountMap["orgName"]; ok { @@ -168,15 +169,16 @@ func getUnregisteredAccount(suite *IdentityTestSuite, orgName string) string { suite.T().Logf("verifier value: %s", verifier.Value) if strings.Contains(verifier.Value, key) { // Already registered. Look at the next account - alreadyRegisted = true + alreadyRegistered = true break } } } - if !alreadyRegisted { + if !alreadyRegistered { suite.testState.unregisteredAccounts = append(suite.testState.unregisteredAccounts[:i], suite.testState.unregisteredAccounts[i+1:]...) return key } } + suite.T().Logf("could not find an unregistered account!") return "" } diff --git a/test/e2e/e2e_test.go b/test/e2e/multiparty/multiparty_test.go similarity index 52% rename from test/e2e/e2e_test.go rename to test/e2e/multiparty/multiparty_test.go index 52de56510d..93cd992956 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/multiparty/multiparty_test.go @@ -14,18 +14,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "context" "crypto/sha256" "encoding/base64" - "encoding/json" "fmt" "net/http" "net/url" "os" - "strings" "testing" "time" @@ -33,151 +31,42 @@ import ( "github.com/gorilla/websocket" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type testState struct { - startTime time.Time t *testing.T - client1 *resty.Client - client2 *resty.Client + startTime time.Time + done func() ws1 *websocket.Conn ws2 *websocket.Conn org1 *core.Identity org1key *core.Verifier org2 *core.Identity org2key *core.Verifier - done func() - stackState *StackState + client1 *resty.Client + client2 *resty.Client unregisteredAccounts []interface{} namespace string } -var widgetSchemaJSON = []byte(`{ - "$id": "https://example.com/widget.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Widget", - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier for the widget." - }, - "name": { - "type": "string", - "description": "The person's last name." - } - }, - "additionalProperties": false -}`) - -func pollForUp(t *testing.T, client *resty.Client) { - var resp *resty.Response - var err error - for i := 0; i < 3; i++ { - resp, err = GetNamespaces(client) - if err == nil && resp.StatusCode() == 200 { - break - } - time.Sleep(5 * time.Second) - } - require.NoError(t, err) - assert.Equal(t, 200, resp.StatusCode()) +func (m *testState) T() *testing.T { + return m.t } -func validateReceivedMessages(ts *testState, client *resty.Client, topic string, msgType core.MessageType, txtype core.TransactionType, count int) (data core.DataArray) { - var group *fftypes.Bytes32 - var messages []*core.Message - events := GetMessageEvents(ts.t, client, ts.startTime, topic, 200) - for i, event := range events { - if event.Message != nil { - message := event.Message - ts.t.Logf("Message %d: %+v", i, *message) - if message.Header.Type != msgType { - continue - } - if group != nil { - assert.Equal(ts.t, group.String(), message.Header.Group.String(), "All messages must be same group") - } - group = message.Header.Group - messages = append(messages, message) - } - } - assert.Equal(ts.t, count, len(messages)) - - var returnData []*core.Data - for idx := 0; idx < len(messages); idx++ { - assert.Equal(ts.t, txtype, (messages)[idx].Header.TxType) - assert.Equal(ts.t, core.FFStringArray{topic}, (messages)[idx].Header.Topics) - assert.Equal(ts.t, topic, (messages)[idx].Header.Topics[0]) - - data := GetDataForMessage(ts.t, client, ts.startTime, (messages)[idx].Header.ID) - var msgData *core.Data - for i, d := range data { - ts.t.Logf("Data %d: %+v", i, *d) - if *d.ID == *messages[idx].Data[0].ID { - msgData = d - } - } - assert.NotNil(ts.t, msgData, "Found data with ID '%s'", messages[idx].Data[0].ID) - if group == nil { - assert.Equal(ts.t, 1, len(data)) - } - - returnData = append(returnData, msgData) - assert.Equal(ts.t, ts.namespace, msgData.Namespace) - expectedHash, err := msgData.CalcHash(context.Background()) - assert.NoError(ts.t, err) - assert.Equal(ts.t, *expectedHash, *msgData.Hash) - - if msgData.Blob != nil { - blob := GetBlob(ts.t, client, msgData, 200) - assert.NotNil(ts.t, blob) - var hash fftypes.Bytes32 = sha256.Sum256(blob) - assert.Equal(ts.t, *msgData.Blob.Hash, hash) - } - - } - - // Flip data (returned in most recent order) into delivery order - return returnData +func (m *testState) StartTime() time.Time { + return m.startTime } -func validateAccountBalances(t *testing.T, client *resty.Client, poolID *fftypes.UUID, tokenIndex string, balances map[string]int64) { - for key, balance := range balances { - account := GetTokenBalance(t, client, poolID, tokenIndex, key) - assert.Equal(t, balance, account.Balance.Int().Int64()) - } -} - -func pickTopic(i int, options []string) string { - return options[i%len(options)] -} - -func readStackFile(t *testing.T) *Stack { - stackFile := os.Getenv("STACK_FILE") - if stackFile == "" { - t.Fatal("STACK_FILE must be set") - } - stack, err := ReadStack(stackFile) - assert.NoError(t, err) - return stack -} - -func readStackState(t *testing.T) *StackState { - stackFile := os.Getenv("STACK_STATE") - if stackFile == "" { - t.Fatal("STACK_STATE must be set") - } - stackState, err := ReadStackState(stackFile) - assert.NoError(t, err) - return stackState +func (m *testState) Done() func() { + return m.done } func beforeE2ETest(t *testing.T) *testState { - stack := readStackFile(t) - stackState := readStackState(t) + stack := e2e.ReadStack(t) + stackState := e2e.ReadStackState(t) namespace := "default" var authHeader1 http.Header @@ -186,9 +75,8 @@ func beforeE2ETest(t *testing.T) *testState { ts := &testState{ t: t, startTime: time.Now(), - client1: NewResty(t), - client2: NewResty(t), - stackState: stackState, + client1: e2e.NewResty(t), + client2: e2e.NewResty(t), unregisteredAccounts: stackState.Accounts[2:], namespace: namespace, } @@ -240,18 +128,16 @@ func beforeE2ETest(t *testing.T) *testState { if os.Getenv("NAMESPACE") != "" { namespace := os.Getenv("NAMESPACE") ts.namespace = namespace - CreateNamespaces(ts.client1, namespace) - CreateNamespaces(ts.client2, namespace) } t.Logf("Client 1: " + ts.client1.HostURL) t.Logf("Client 2: " + ts.client2.HostURL) - pollForUp(t, ts.client1) - pollForUp(t, ts.client2) + e2e.PollForUp(t, ts.client1) + e2e.PollForUp(t, ts.client2) for { - orgsC1 := GetOrgs(t, ts.client1, 200) - orgsC2 := GetOrgs(t, ts.client2, 200) + orgsC1 := e2e.GetOrgs(t, ts.client1, 200) + orgsC2 := e2e.GetOrgs(t, ts.client2, 200) if len(orgsC1) >= 2 && len(orgsC2) >= 2 { // in case there are more than two orgs in the network we need to ensure // we select the same two that were provided in the first two elements @@ -270,8 +156,8 @@ func beforeE2ETest(t *testing.T) *testState { t.Logf("Waiting for 2 orgs to appear. Currently have: node1=%d node2=%d", len(orgsC1), len(orgsC2)) time.Sleep(3 * time.Second) } - ts.org1key = GetIdentityBlockchainKeys(t, ts.client1, ts.org1.ID, 200)[0] - ts.org2key = GetIdentityBlockchainKeys(t, ts.client2, ts.org2.ID, 200)[0] + ts.org1key = e2e.GetIdentityBlockchainKeys(t, ts.client1, ts.org1.ID, 200)[0] + ts.org2key = e2e.GetIdentityBlockchainKeys(t, ts.client2, ts.org2.ID, 200)[0] t.Logf("Org1: ID=%s DID=%s Key=%s", ts.org1.DID, ts.org1.ID, ts.org1key.Value) t.Logf("Org2: ID=%s DID=%s Key=%s", ts.org2.DID, ts.org2.ID, ts.org2key.Value) @@ -312,151 +198,60 @@ func beforeE2ETest(t *testing.T) *testState { return ts } -func wsReader(conn *websocket.Conn, dbChanges bool) chan *core.EventDelivery { - events := make(chan *core.EventDelivery, 100) - go func() { - for { - _, b, err := conn.ReadMessage() - if err != nil { - fmt.Printf("Websocket %s closing, error: %s\n", conn.RemoteAddr(), err) - return - } - var wsa core.WSActionBase - err = json.Unmarshal(b, &wsa) - if err != nil { - panic(fmt.Errorf("Invalid JSON received on WebSocket: %s", err)) - } - switch wsa.Type { - default: - var ed core.EventDelivery - err = json.Unmarshal(b, &ed) - if err != nil { - panic(fmt.Errorf("Invalid JSON received on WebSocket: %s", err)) - } - if err == nil { - fmt.Printf("Websocket %s event: %s/%s/%s -> %s (tx=%s)\n", conn.RemoteAddr(), ed.Namespace, ed.Type, ed.ID, ed.Reference, ed.Transaction) - events <- &ed - } - } - } - }() - return events -} - -func waitForEvent(t *testing.T, c chan *core.EventDelivery, eventType core.EventType, ref *fftypes.UUID) { - for { - ed := <-c - if ed.Type == eventType && (ref == nil || *ref == *ed.Reference) { - t.Logf("Detected '%s' event for ref '%s'", ed.Type, ed.Reference) - return - } - t.Logf("Ignored event '%s'", ed.ID) - } -} - -func waitForMessageConfirmed(t *testing.T, c chan *core.EventDelivery, msgType core.MessageType) *core.EventDelivery { - for { - ed := <-c - if ed.Type == core.EventTypeMessageConfirmed && ed.Message != nil && ed.Message.Header.Type == msgType { - t.Logf("Detected '%s' event for message '%s' of type '%s'", ed.Type, ed.Message.Header.ID, msgType) - return ed - } - t.Logf("Ignored event '%s'", ed.ID) - } -} - -func waitForIdentityConfirmed(t *testing.T, c chan *core.EventDelivery) *core.EventDelivery { - for { - ed := <-c - if ed.Type == core.EventTypeIdentityConfirmed { - t.Logf("Detected '%s' event for identity '%s'", ed.Type, ed.Reference) - return ed - } - t.Logf("Ignored event '%s'", ed.ID) - } -} - -func waitForContractEvent(t *testing.T, client *resty.Client, c chan *core.EventDelivery, match map[string]interface{}) map[string]interface{} { - for { - eventDelivery := <-c - if eventDelivery.Type == core.EventTypeBlockchainEventReceived { - event, err := GetBlockchainEvent(t, client, eventDelivery.Event.Reference.String()) - if err != nil { - t.Logf("WARN: unable to get event: %v", err.Error()) - continue - } - eventJSON, ok := event.(map[string]interface{}) - if !ok { - t.Logf("WARN: unable to parse changeEvent: %v", event) +func validateReceivedMessages(ts *testState, client *resty.Client, topic string, msgType core.MessageType, txtype core.TransactionType, count int) (data core.DataArray) { + var group *fftypes.Bytes32 + var messages []*core.Message + events := e2e.GetMessageEvents(ts.t, client, ts.startTime, topic, 200) + for i, event := range events { + if event.Message != nil { + message := event.Message + ts.t.Logf("Message %d: %+v", i, *message) + if message.Header.Type != msgType { continue } - if checkObject(t, match, eventJSON) { - return eventJSON + if group != nil { + assert.Equal(ts.t, group.String(), message.Header.Group.String(), "All messages must be same group") } + group = message.Header.Group + messages = append(messages, message) } } -} - -func checkObject(t *testing.T, expected interface{}, actual interface{}) bool { - match := true - - // check if this is a nested object - expectedObject, expectedIsObject := expected.(map[string]interface{}) - actualObject, actualIsObject := actual.(map[string]interface{}) + assert.Equal(ts.t, count, len(messages)) - t.Logf("Matching blockchain event: %s", fftypes.JSONObject(actualObject).String()) + var returnData []*core.Data + for idx := 0; idx < len(messages); idx++ { + assert.Equal(ts.t, txtype, (messages)[idx].Header.TxType) + assert.Equal(ts.t, core.FFStringArray{topic}, (messages)[idx].Header.Topics) + assert.Equal(ts.t, topic, (messages)[idx].Header.Topics[0]) - // check if this is an array - expectedArray, expectedIsArray := expected.([]interface{}) - actualArray, actualIsArray := actual.([]interface{}) - switch { - case expectedIsObject && actualIsObject: - for expectedKey, expectedValue := range expectedObject { - if !checkObject(t, expectedValue, actualObject[expectedKey]) { - return false - } - } - case expectedIsArray && actualIsArray: - for _, expectedItem := range expectedArray { - for j, actualItem := range actualArray { - if checkObject(t, expectedItem, actualItem) { - break - } - if j == len(actualArray)-1 { - return false - } + data := e2e.GetDataForMessage(ts.t, client, ts.startTime, (messages)[idx].Header.ID) + var msgData *core.Data + for i, d := range data { + ts.t.Logf("Data %d: %+v", i, *d) + if *d.ID == *messages[idx].Data[0].ID { + msgData = d } } - default: - expectedString, expectedIsString := expected.(string) - actualString, actualIsString := actual.(string) - if expectedIsString && actualIsString { - return strings.ToLower(expectedString) == strings.ToLower(actualString) + assert.NotNil(ts.t, msgData, "Found data with ID '%s'", messages[idx].Data[0].ID) + if group == nil { + assert.Equal(ts.t, 1, len(data)) } - return expected == actual - } - return match -} -func verifyAllOperationsSucceeded(t *testing.T, clients []*resty.Client, startTime time.Time) { - tries := 3 - delay := 2 * time.Second - - var pending string - for i := 0; i < tries; i++ { - pending = "" - for _, client := range clients { - for _, op := range GetOperations(t, client, startTime) { - if op.Status != core.OpStatusSucceeded { - pending += fmt.Sprintf("Operation '%s' (%s) on '%s' is not successful\n", op.ID, op.Type, client.BaseURL) - } - } - } - if pending == "" { - return + returnData = append(returnData, msgData) + assert.Equal(ts.t, ts.namespace, msgData.Namespace) + expectedHash, err := msgData.CalcHash(context.Background()) + assert.NoError(ts.t, err) + assert.Equal(ts.t, *expectedHash, *msgData.Hash) + + if msgData.Blob != nil { + blob := e2e.GetBlob(ts.t, client, msgData, 200) + assert.NotNil(ts.t, blob) + var hash fftypes.Bytes32 = sha256.Sum256(blob) + assert.Equal(ts.t, *msgData.Blob.Hash, hash) } - time.Sleep(delay) + } - assert.Fail(t, pending) + // Flip data (returned in most recent order) into delivery order + return returnData } diff --git a/test/e2e/onchain_offchain_test.go b/test/e2e/multiparty/onchain_offchain_test.go similarity index 73% rename from test/e2e/onchain_offchain_test.go rename to test/e2e/multiparty/onchain_offchain_test.go index 2bde1cebd3..5fb4a7df1b 100644 --- a/test/e2e/onchain_offchain_test.go +++ b/test/e2e/multiparty/onchain_offchain_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "bytes" @@ -30,6 +30,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -45,14 +46,14 @@ func (suite *OnChainOffChainTestSuite) BeforeTest(suiteName, testName string) { } func (suite *OnChainOffChainTestSuite) AfterTest(suiteName, testName string) { - verifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) } func (suite *OnChainOffChainTestSuite) TestE2EBroadcast() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, true) - received2 := wsReader(suite.testState.ws2, true) + received1 := e2e.WsReader(suite.testState.ws1, true) + received2 := e2e.WsReader(suite.testState.ws2, true) // Broadcast some messages, that should get batched, across two topics totalMessages := 10 @@ -63,19 +64,19 @@ func (suite *OnChainOffChainTestSuite) TestE2EBroadcast() { data := &core.DataRefOrValue{ Value: value, } - topic := pickTopic(i, topics) + topic := e2e.PickTopic(i, topics) expectedData[topic] = append(expectedData[topic], data) - resp, err := BroadcastMessage(suite.T(), suite.testState.client1, topic, data, false) + resp, err := e2e.BroadcastMessage(suite.T(), suite.testState.client1, topic, data, false) require.NoError(suite.T(), err) assert.Equal(suite.T(), 202, resp.StatusCode()) } for i := 0; i < totalMessages; i++ { // Wait for all the message-confirmed events, from both participants - waitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) } for topic, dataArray := range expectedData { @@ -93,8 +94,8 @@ func (suite *OnChainOffChainTestSuite) TestE2EBroadcast() { func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, true) - received2 := wsReader(suite.testState.ws2, true) + received1 := e2e.WsReader(suite.testState.ws1, true) + received2 := e2e.WsReader(suite.testState.ws2, true) var resp *resty.Response value := fftypes.JSONAnyPtr(`"Hello"`) @@ -109,7 +110,7 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() { } // Should be rejected as datatype not known - resp, err := BroadcastMessage(suite.T(), suite.testState.client1, "topic1", &data, true) + resp, err := e2e.BroadcastMessage(suite.T(), suite.testState.client1, "topic1", &data, true) require.NoError(suite.T(), err) assert.Equal(suite.T(), 400, resp.StatusCode()) assert.Contains(suite.T(), resp.String(), "FF10195") // datatype not found @@ -117,11 +118,11 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() { dt := &core.Datatype{ Name: "widget", Version: version, - Value: fftypes.JSONAnyPtrBytes(widgetSchemaJSON), + Value: fftypes.JSONAnyPtrBytes(e2e.WidgetSchemaJSON), } - dt = CreateDatatype(suite.T(), suite.testState.client1, dt, true) + dt = e2e.CreateDatatype(suite.T(), suite.testState.client1, dt, true) - resp, err = BroadcastMessage(suite.T(), suite.testState.client1, "topic1", &data, true) + resp, err = e2e.BroadcastMessage(suite.T(), suite.testState.client1, "topic1", &data, true) require.NoError(suite.T(), err) assert.Equal(suite.T(), 400, resp.StatusCode()) assert.Contains(suite.T(), resp.String(), "FF10198") // does not conform @@ -131,19 +132,19 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() { "name": "mywidget" }`) - resp, err = BroadcastMessage(suite.T(), suite.testState.client1, "topic1", &data, true) + resp, err = e2e.BroadcastMessage(suite.T(), suite.testState.client1, "topic1", &data, true) require.NoError(suite.T(), err) assert.Equal(suite.T(), 200, resp.StatusCode()) - waitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) } func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, true) - received2 := wsReader(suite.testState.ws2, true) + received1 := e2e.WsReader(suite.testState.ws1, true) + received2 := e2e.WsReader(suite.testState.ws2, true) var resp *resty.Response value := fftypes.JSONAnyPtr(`{"foo":"bar"}`) @@ -158,7 +159,7 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() { } // Should be rejected as datatype not known - resp, err := PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ + resp, err := e2e.PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ suite.testState.org1.Name, suite.testState.org2.Name, }, "", core.TransactionTypeBatchPin, true) @@ -169,11 +170,11 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() { dt := &core.Datatype{ Name: "widget", Version: version, - Value: fftypes.JSONAnyPtrBytes(widgetSchemaJSON), + Value: fftypes.JSONAnyPtrBytes(e2e.WidgetSchemaJSON), } - dt = CreateDatatype(suite.T(), suite.testState.client1, dt, true) + dt = e2e.CreateDatatype(suite.T(), suite.testState.client1, dt, true) - resp, err = PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ + resp, err = e2e.PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ suite.testState.org1.Name, suite.testState.org2.Name, }, "", core.TransactionTypeBatchPin, false) @@ -186,23 +187,23 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() { "name": "mywidget" }`) - resp, err = PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ + resp, err = e2e.PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ suite.testState.org1.Name, suite.testState.org2.Name, }, "", core.TransactionTypeBatchPin, true) require.NoError(suite.T(), err) assert.Equal(suite.T(), 200, resp.StatusCode()) - waitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) } func (suite *OnChainOffChainTestSuite) TestE2EPrivate() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) // Send 10 messages, that should get batched, across two topics totalMessages := 10 @@ -213,11 +214,11 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivate() { data := &core.DataRefOrValue{ Value: value, } - topic := pickTopic(i, topics) + topic := e2e.PickTopic(i, topics) expectedData[topic] = append(expectedData[topic], data) - resp, err := PrivateMessage(suite.testState, suite.testState.client1, topic, data, []string{ + resp, err := e2e.PrivateMessage(suite.testState, suite.testState.client1, topic, data, []string{ suite.testState.org1.Name, suite.testState.org2.Name, }, "", core.TransactionTypeBatchPin, false) @@ -227,8 +228,8 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivate() { for i := 0; i < totalMessages; i++ { // Wait for all thel message-confirmed events, from both participants - waitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) } for topic, dataArray := range expectedData { @@ -246,22 +247,22 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivate() { func (suite *OnChainOffChainTestSuite) TestE2EBroadcastBlob() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) var resp *resty.Response - data, resp, err := BroadcastBlobMessage(suite.T(), suite.testState.client1, "topic1") + data, resp, err := e2e.BroadcastBlobMessage(suite.T(), suite.testState.client1, "topic1") require.NoError(suite.T(), err) assert.Equal(suite.T(), 202, resp.StatusCode()) - waitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypeBroadcast) val1 := validateReceivedMessages(suite.testState, suite.testState.client1, "topic1", core.MessageTypeBroadcast, core.TransactionTypeBatchPin, 1) assert.Regexp(suite.T(), "myfile.txt", val1[0].Value.String()) assert.Equal(suite.T(), "myfile.txt", val1[0].Blob.Name) assert.Equal(suite.T(), data.Blob.Size, val1[0].Blob.Size) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypeBroadcast) val2 := validateReceivedMessages(suite.testState, suite.testState.client2, "topic1", core.MessageTypeBroadcast, core.TransactionTypeBatchPin, 1) assert.Regexp(suite.T(), "myfile.txt", val2[0].Value.String()) assert.Equal(suite.T(), "myfile.txt", val2[0].Blob.Name) @@ -272,12 +273,12 @@ func (suite *OnChainOffChainTestSuite) TestE2EBroadcastBlob() { func (suite *OnChainOffChainTestSuite) TestE2EPrivateBlobDatatypeTagged() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) var resp *resty.Response - data, resp, err := PrivateBlobMessageDatatypeTagged(suite.testState, suite.testState.client1, "topic1", []string{ + data, resp, err := e2e.PrivateBlobMessageDatatypeTagged(suite.testState, suite.testState.client1, "topic1", []string{ suite.testState.org1.Name, suite.testState.org2.Name, }) @@ -286,13 +287,13 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivateBlobDatatypeTagged() { assert.Empty(suite.T(), data.Blob.Name) assert.NotNil(suite.T(), data.Blob.Hash) - waitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) res1 := validateReceivedMessages(suite.testState, suite.testState.client1, "topic1", core.MessageTypePrivate, core.TransactionTypeBatchPin, 1) assert.Equal(suite.T(), data.Blob.Hash.String(), res1[0].Blob.Hash.String()) assert.Empty(suite.T(), res1[0].Blob.Name) assert.Equal(suite.T(), data.Blob.Size, res1[0].Blob.Size) - waitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) res2 := validateReceivedMessages(suite.testState, suite.testState.client2, "topic1", core.MessageTypePrivate, core.TransactionTypeBatchPin, 1) assert.Equal(suite.T(), data.Blob.Hash.String(), res2[0].Blob.Hash.String()) assert.Empty(suite.T(), res2[0].Blob.Name) @@ -302,8 +303,8 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivateBlobDatatypeTagged() { func (suite *OnChainOffChainTestSuite) TestE2EWebhookExchange() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) subJSON := fmt.Sprintf(`{ "transport": "webhooks", @@ -320,8 +321,8 @@ func (suite *OnChainOffChainTestSuite) TestE2EWebhookExchange() { "tag": "myrequest" } }`, suite.testState.namespace) - CleanupExistingSubscription(suite.T(), suite.testState.client2, suite.testState.namespace, "myhook") - sub := CreateSubscription(suite.T(), suite.testState.client2, subJSON, 201) + e2e.CleanupExistingSubscription(suite.T(), suite.testState.client2, suite.testState.namespace, "myhook") + sub := e2e.CreateSubscription(suite.T(), suite.testState.client2, subJSON, 201) assert.NotNil(suite.T(), sub.ID) data := core.DataRefOrValue{ @@ -329,17 +330,17 @@ func (suite *OnChainOffChainTestSuite) TestE2EWebhookExchange() { } var resp *resty.Response - resp, err := PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ + resp, err := e2e.PrivateMessage(suite.testState, suite.testState.client1, "topic1", &data, []string{ suite.testState.org1.Name, suite.testState.org2.Name, }, "myrequest", core.TransactionTypeBatchPin, false) require.NoError(suite.T(), err) assert.Equal(suite.T(), 202, resp.StatusCode()) - waitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) // request 1 - waitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) // request 2 - waitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) // response 1 - waitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) // response 2 + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) // request 1 + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) // request 2 + e2e.WaitForMessageConfirmed(suite.T(), received1, core.MessageTypePrivate) // response 1 + e2e.WaitForMessageConfirmed(suite.T(), received2, core.MessageTypePrivate) // response 2 // When we query the confirmed messages for each receiver, we will see the requests and responses. // We just check the reponses (index 1) @@ -376,15 +377,15 @@ func (suite *OnChainOffChainTestSuite) TestE2EWebhookRequestReplyNoTx() { "tag": "myrequest" } }`, suite.testState.namespace) - CleanupExistingSubscription(suite.T(), suite.testState.client2, suite.testState.namespace, "myhook") - sub := CreateSubscription(suite.T(), suite.testState.client2, subJSON, 201) + e2e.CleanupExistingSubscription(suite.T(), suite.testState.client2, suite.testState.namespace, "myhook") + sub := e2e.CreateSubscription(suite.T(), suite.testState.client2, subJSON, 201) assert.NotNil(suite.T(), sub.ID) data := core.DataRefOrValue{ Value: fftypes.JSONAnyPtr(`{}`), } - reply := RequestReply(suite.testState, suite.testState.client1, &data, []string{ + reply := e2e.RequestReply(suite.testState, suite.testState.client1, &data, []string{ suite.testState.org1.Name, suite.testState.org2.Name, }, "myrequest", core.TransactionTypeUnpinned) diff --git a/test/e2e/tokens_test.go b/test/e2e/multiparty/tokens_test.go similarity index 62% rename from test/e2e/tokens_test.go rename to test/e2e/multiparty/tokens_test.go index 49113836cf..a4247cf296 100644 --- a/test/e2e/tokens_test.go +++ b/test/e2e/multiparty/tokens_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package multiparty import ( "fmt" @@ -24,6 +24,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/test/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -36,7 +37,7 @@ type TokensTestSuite struct { func (suite *TokensTestSuite) SetupSuite() { suite.testState = beforeE2ETest(suite.T()) - stack := readStackFile(suite.T()) + stack := e2e.ReadStack(suite.T()) suite.connector = stack.TokenProviders[0] } @@ -45,16 +46,16 @@ func (suite *TokensTestSuite) BeforeTest(suiteName, testName string) { } func (suite *TokensTestSuite) AfterTest(suiteName, testName string) { - verifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) + e2e.VerifyAllOperationsSucceeded(suite.T(), []*resty.Client{suite.testState.client1, suite.testState.client2}, suite.testState.startTime) } func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) - pools := GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) + pools := e2e.GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) rand.Seed(time.Now().UnixNano()) poolName := fmt.Sprintf("pool%d", rand.Intn(10000)) suite.T().Logf("Pool name: %s", poolName) @@ -65,11 +66,11 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { Config: fftypes.JSONObject{}, } - poolResp := CreateTokenPool(suite.T(), suite.testState.client1, pool, false) + poolResp := e2e.CreateTokenPool(suite.T(), suite.testState.client1, pool, false) poolID := poolResp.ID - waitForEvent(suite.T(), received1, core.EventTypePoolConfirmed, poolID) - pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + e2e.WaitForEvent(suite.T(), received1, core.EventTypePoolConfirmed, poolID) + pools = e2e.GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(pools)) assert.Equal(suite.T(), suite.testState.namespace, pools[0].Namespace) assert.Equal(suite.T(), suite.connector, pools[0].Connector) @@ -77,8 +78,8 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { assert.Equal(suite.T(), core.TokenTypeFungible, pools[0].Type) assert.NotEmpty(suite.T(), pools[0].Locator) - waitForEvent(suite.T(), received2, core.EventTypePoolConfirmed, poolID) - pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + e2e.WaitForEvent(suite.T(), received2, core.EventTypePoolConfirmed, poolID) + pools = e2e.GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(pools)) assert.Equal(suite.T(), suite.testState.namespace, pools[0].Namespace) assert.Equal(suite.T(), suite.connector, pools[0].Connector) @@ -94,10 +95,10 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { }, Pool: poolName, } - approvalOut := TokenApproval(suite.T(), suite.testState.client1, approval, false) + approvalOut := e2e.TokenApproval(suite.T(), suite.testState.client1, approval, false) - waitForEvent(suite.T(), received1, core.EventTypeApprovalConfirmed, approvalOut.LocalID) - approvals := GetTokenApprovals(suite.T(), suite.testState.client1, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeApprovalConfirmed, approvalOut.LocalID) + approvals := e2e.GetTokenApprovals(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 1, len(approvals)) assert.Equal(suite.T(), suite.connector, approvals[0].Connector) assert.Equal(suite.T(), true, approvals[0].Approved) @@ -106,25 +107,25 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { TokenTransfer: core.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)}, Pool: poolName, } - transferOut := MintTokens(suite.T(), suite.testState.client1, transfer, false) + transferOut := e2e.MintTokens(suite.T(), suite.testState.client1, transfer, false) - waitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, transferOut.LocalID) - transfers := GetTokenTransfers(suite.T(), suite.testState.client1, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, transferOut.LocalID) + transfers := e2e.GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), suite.connector, transfers[0].Connector) assert.Equal(suite.T(), core.TokenTransferTypeMint, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ suite.testState.org1key.Value: 1, }) - waitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, nil) - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) + e2e.WaitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, nil) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), suite.connector, transfers[0].Connector) assert.Equal(suite.T(), core.TokenTransferTypeMint, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolID, "", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client2, poolID, "", map[string]int64{ suite.testState.org1key.Value: 1, }) @@ -144,29 +145,29 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { }, }, } - transferOut = TransferTokens(suite.T(), suite.testState.client2, transfer, false) + transferOut = e2e.TransferTokens(suite.T(), suite.testState.client2, transfer, false) - waitForEvent(suite.T(), received1, core.EventTypeMessageConfirmed, transferOut.Message) - transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeMessageConfirmed, transferOut.Message) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), suite.connector, transfers[0].Connector) assert.Equal(suite.T(), core.TokenTransferTypeTransfer, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - data := GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transfers[0].Message) + data := e2e.GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transfers[0].Message) assert.Equal(suite.T(), 1, len(data)) assert.Equal(suite.T(), `"token approval - payment for data"`, data[0].Value.String()) - validateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 1, }) - waitForEvent(suite.T(), received2, core.EventTypeMessageConfirmed, transferOut.Message) - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) + e2e.WaitForEvent(suite.T(), received2, core.EventTypeMessageConfirmed, transferOut.Message) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), suite.connector, transfers[0].Connector) assert.Equal(suite.T(), core.TokenTransferTypeTransfer, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolID, "", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client2, poolID, "", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 1, }) @@ -175,54 +176,54 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { TokenTransfer: core.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)}, Pool: poolName, } - transferOut = BurnTokens(suite.T(), suite.testState.client2, transfer, false) + transferOut = e2e.BurnTokens(suite.T(), suite.testState.client2, transfer, false) - waitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, transferOut.LocalID) - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) + e2e.WaitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, transferOut.LocalID) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), suite.connector, transfers[0].Connector) assert.Equal(suite.T(), core.TokenTransferTypeBurn, transfers[0].Type) assert.Equal(suite.T(), "", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolID, "", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client2, poolID, "", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 0, }) - waitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, nil) - transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, nil) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), suite.connector, transfers[0].Connector) assert.Equal(suite.T(), core.TokenTransferTypeBurn, transfers[0].Type) assert.Equal(suite.T(), "", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 0, }) - accounts := GetTokenAccounts(suite.T(), suite.testState.client1, poolID) + accounts := e2e.GetTokenAccounts(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 2, len(accounts)) assert.Equal(suite.T(), suite.testState.org2key.Value, accounts[0].Key) assert.Equal(suite.T(), suite.testState.org1key.Value, accounts[1].Key) - accounts = GetTokenAccounts(suite.T(), suite.testState.client2, poolID) + accounts = e2e.GetTokenAccounts(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 2, len(accounts)) assert.Equal(suite.T(), suite.testState.org2key.Value, accounts[0].Key) assert.Equal(suite.T(), suite.testState.org1key.Value, accounts[1].Key) - accountPools := GetTokenAccountPools(suite.T(), suite.testState.client1, suite.testState.org1key.Value) + accountPools := e2e.GetTokenAccountPools(suite.T(), suite.testState.client1, suite.testState.org1key.Value) assert.Equal(suite.T(), *poolID, *accountPools[0].Pool) - accountPools = GetTokenAccountPools(suite.T(), suite.testState.client2, suite.testState.org2key.Value) + accountPools = e2e.GetTokenAccountPools(suite.T(), suite.testState.client2, suite.testState.org2key.Value) assert.Equal(suite.T(), *poolID, *accountPools[0].Pool) } func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { defer suite.testState.done() - received1 := wsReader(suite.testState.ws1, false) - received2 := wsReader(suite.testState.ws2, false) + received1 := e2e.WsReader(suite.testState.ws1, false) + received2 := e2e.WsReader(suite.testState.ws2, false) - pools := GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) + pools := e2e.GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) rand.Seed(time.Now().UnixNano()) poolName := fmt.Sprintf("pool%d", rand.Intn(10000)) suite.T().Logf("Pool name: %s", poolName) @@ -233,7 +234,7 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { Config: fftypes.JSONObject{}, } - poolOut := CreateTokenPool(suite.T(), suite.testState.client1, pool, true) + poolOut := e2e.CreateTokenPool(suite.T(), suite.testState.client1, pool, true) assert.Equal(suite.T(), suite.testState.namespace, poolOut.Namespace) assert.Equal(suite.T(), poolName, poolOut.Name) assert.Equal(suite.T(), core.TokenTypeNonFungible, poolOut.Type) @@ -241,9 +242,9 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { poolID := poolOut.ID - waitForEvent(suite.T(), received1, core.EventTypePoolConfirmed, poolID) - waitForEvent(suite.T(), received2, core.EventTypePoolConfirmed, poolID) - pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + e2e.WaitForEvent(suite.T(), received1, core.EventTypePoolConfirmed, poolID) + e2e.WaitForEvent(suite.T(), received2, core.EventTypePoolConfirmed, poolID) + pools = e2e.GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(pools)) assert.Equal(suite.T(), suite.testState.namespace, pools[0].Namespace) assert.Equal(suite.T(), poolName, pools[0].Name) @@ -258,10 +259,10 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { }, Pool: poolName, } - approvalOut := TokenApproval(suite.T(), suite.testState.client1, approval, true) + approvalOut := e2e.TokenApproval(suite.T(), suite.testState.client1, approval, true) - waitForEvent(suite.T(), received1, core.EventTypeApprovalConfirmed, approvalOut.LocalID) - approvals := GetTokenApprovals(suite.T(), suite.testState.client1, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeApprovalConfirmed, approvalOut.LocalID) + approvals := e2e.GetTokenApprovals(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 1, len(approvals)) assert.Equal(suite.T(), suite.connector, approvals[0].Connector) assert.Equal(suite.T(), true, approvals[0].Approved) @@ -273,22 +274,22 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { }, Pool: poolName, } - transferOut := MintTokens(suite.T(), suite.testState.client1, transfer, true) + transferOut := e2e.MintTokens(suite.T(), suite.testState.client1, transfer, true) assert.Equal(suite.T(), core.TokenTransferTypeMint, transferOut.Type) assert.Equal(suite.T(), "1", transferOut.TokenIndex) assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolID, "1", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "1", map[string]int64{ suite.testState.org1key.Value: 1, }) - waitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, transferOut.LocalID) - waitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, nil) - transfers := GetTokenTransfers(suite.T(), suite.testState.client2, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, transferOut.LocalID) + e2e.WaitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, nil) + transfers := e2e.GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), core.TokenTransferTypeMint, transfers[0].Type) assert.Equal(suite.T(), "1", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolID, "1", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client2, poolID, "1", map[string]int64{ suite.testState.org1key.Value: 1, }) @@ -309,26 +310,26 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { }, }, } - transferOut = TransferTokens(suite.T(), suite.testState.client1, transfer, true) + transferOut = e2e.TransferTokens(suite.T(), suite.testState.client1, transfer, true) assert.Equal(suite.T(), core.TokenTransferTypeTransfer, transferOut.Type) assert.Equal(suite.T(), "1", transferOut.TokenIndex) assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) - data := GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transferOut.Message) + data := e2e.GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transferOut.Message) assert.Equal(suite.T(), 1, len(data)) assert.Equal(suite.T(), `"ownership change"`, data[0].Value.String()) - validateAccountBalances(suite.T(), suite.testState.client1, poolID, "1", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "1", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 1, }) - waitForEvent(suite.T(), received1, core.EventTypeMessageConfirmed, transferOut.Message) - waitForEvent(suite.T(), received2, core.EventTypeMessageConfirmed, transferOut.Message) - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeMessageConfirmed, transferOut.Message) + e2e.WaitForEvent(suite.T(), received2, core.EventTypeMessageConfirmed, transferOut.Message) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), core.TokenTransferTypeTransfer, transfers[0].Type) assert.Equal(suite.T(), "1", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolID, "1", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client2, poolID, "1", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 1, }) @@ -340,38 +341,38 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { }, Pool: poolName, } - transferOut = BurnTokens(suite.T(), suite.testState.client2, transfer, true) + transferOut = e2e.BurnTokens(suite.T(), suite.testState.client2, transfer, true) assert.Equal(suite.T(), core.TokenTransferTypeBurn, transferOut.Type) assert.Equal(suite.T(), "1", transferOut.TokenIndex) assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolID, "1", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client2, poolID, "1", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 0, }) - waitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, transferOut.LocalID) - waitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, nil) - transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolID) + e2e.WaitForEvent(suite.T(), received2, core.EventTypeTransferConfirmed, transferOut.LocalID) + e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, nil) + transfers = e2e.GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), core.TokenTransferTypeBurn, transfers[0].Type) assert.Equal(suite.T(), "1", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolID, "1", map[string]int64{ + e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolID, "1", map[string]int64{ suite.testState.org1key.Value: 0, suite.testState.org2key.Value: 0, }) - accounts := GetTokenAccounts(suite.T(), suite.testState.client1, poolID) + accounts := e2e.GetTokenAccounts(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 2, len(accounts)) assert.Equal(suite.T(), suite.testState.org2key.Value, accounts[0].Key) assert.Equal(suite.T(), suite.testState.org1key.Value, accounts[1].Key) - accounts = GetTokenAccounts(suite.T(), suite.testState.client2, poolID) + accounts = e2e.GetTokenAccounts(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 2, len(accounts)) assert.Equal(suite.T(), suite.testState.org2key.Value, accounts[0].Key) assert.Equal(suite.T(), suite.testState.org1key.Value, accounts[1].Key) - accountPools := GetTokenAccountPools(suite.T(), suite.testState.client1, suite.testState.org1key.Value) + accountPools := e2e.GetTokenAccountPools(suite.T(), suite.testState.client1, suite.testState.org1key.Value) assert.Equal(suite.T(), *poolID, *accountPools[0].Pool) - accountPools = GetTokenAccountPools(suite.T(), suite.testState.client2, suite.testState.org2key.Value) + accountPools = e2e.GetTokenAccountPools(suite.T(), suite.testState.client2, suite.testState.org2key.Value) assert.Equal(suite.T(), *poolID, *accountPools[0].Pool) } diff --git a/test/e2e/restclient_test.go b/test/e2e/restclient.go similarity index 93% rename from test/e2e/restclient_test.go rename to test/e2e/restclient.go index 0decd47d7a..a9793236ac 100644 --- a/test/e2e/restclient_test.go +++ b/test/e2e/restclient.go @@ -99,16 +99,6 @@ func GetNamespaces(client *resty.Client) (*resty.Response, error) { Get(urlGetNamespaces) } -func CreateNamespaces(client *resty.Client, namespace string) (*resty.Response, error) { - namespaceJSON := map[string]interface{}{ - "description": "Firefly test namespace", - "name": namespace, - } - return client.R(). - SetBody(namespaceJSON). - Post(urlGetNamespaces) -} - func GetMessageEvents(t *testing.T, client *resty.Client, startTime time.Time, topic string, expectedStatus int) (events []*core.EnrichedEvent) { path := urlGetEvents resp, err := client.R(). @@ -355,8 +345,8 @@ func BroadcastBlobMessage(t *testing.T, client *resty.Client, topic string) (*co return data, res, err } -func PrivateBlobMessageDatatypeTagged(ts *testState, client *resty.Client, topic string, orgNames []string) (*core.Data, *resty.Response, error) { - data := CreateBlob(ts.t, client, &core.DatatypeRef{Name: "myblob"}) +func PrivateBlobMessageDatatypeTagged(ts TestState, client *resty.Client, topic string, orgNames []string) (*core.Data, *resty.Response, error) { + data := CreateBlob(ts.T(), client, &core.DatatypeRef{Name: "myblob"}) members := make([]core.MemberInput, len(orgNames)) for i, oName := range orgNames { // We let FireFly resolve the friendly name of the org to the identity @@ -376,18 +366,18 @@ func PrivateBlobMessageDatatypeTagged(ts *testState, client *resty.Client, topic }, Group: &core.InputGroup{ Members: members, - Name: fmt.Sprintf("test_%d", ts.startTime.UnixNano()), + Name: fmt.Sprintf("test_%d", ts.StartTime().UnixNano()), }, }). Post(urlPrivateMessage) return data, res, err } -func PrivateMessage(ts *testState, client *resty.Client, topic string, data *core.DataRefOrValue, orgNames []string, tag string, txType core.TransactionType, confirm bool) (*resty.Response, error) { +func PrivateMessage(ts TestState, client *resty.Client, topic string, data *core.DataRefOrValue, orgNames []string, tag string, txType core.TransactionType, confirm bool) (*resty.Response, error) { return PrivateMessageWithKey(ts, client, "", topic, data, orgNames, tag, txType, confirm) } -func PrivateMessageWithKey(ts *testState, client *resty.Client, key, topic string, data *core.DataRefOrValue, orgNames []string, tag string, txType core.TransactionType, confirm bool) (*resty.Response, error) { +func PrivateMessageWithKey(ts TestState, client *resty.Client, key, topic string, data *core.DataRefOrValue, orgNames []string, tag string, txType core.TransactionType, confirm bool) (*resty.Response, error) { members := make([]core.MemberInput, len(orgNames)) for i, oName := range orgNames { // We let FireFly resolve the friendly name of the org to the identity @@ -409,7 +399,7 @@ func PrivateMessageWithKey(ts *testState, client *resty.Client, key, topic strin InlineData: core.InlineData{data}, Group: &core.InputGroup{ Members: members, - Name: fmt.Sprintf("test_%d", ts.startTime.UnixNano()), + Name: fmt.Sprintf("test_%d", ts.StartTime().UnixNano()), }, } res, err := client.R(). @@ -417,11 +407,11 @@ func PrivateMessageWithKey(ts *testState, client *resty.Client, key, topic strin SetQueryParam("confirm", strconv.FormatBool(confirm)). SetResult(&msg.Message). Post(urlPrivateMessage) - ts.t.Logf("Sent private message %s to %+v", msg.Header.ID, msg.Group.Members) + ts.T().Logf("Sent private message %s to %+v", msg.Header.ID, msg.Group.Members) return res, err } -func RequestReply(ts *testState, client *resty.Client, data *core.DataRefOrValue, orgNames []string, tag string, txType core.TransactionType) *core.MessageInOut { +func RequestReply(ts TestState, client *resty.Client, data *core.DataRefOrValue, orgNames []string, tag string, txType core.TransactionType) *core.MessageInOut { members := make([]core.MemberInput, len(orgNames)) for i, oName := range orgNames { // We let FireFly resolve the friendly name of the org to the identity @@ -439,7 +429,7 @@ func RequestReply(ts *testState, client *resty.Client, data *core.DataRefOrValue InlineData: core.InlineData{data}, Group: &core.InputGroup{ Members: members, - Name: fmt.Sprintf("test_%d", ts.startTime.UnixNano()), + Name: fmt.Sprintf("test_%d", ts.StartTime().UnixNano()), }, } var replyMsg core.MessageInOut @@ -447,8 +437,8 @@ func RequestReply(ts *testState, client *resty.Client, data *core.DataRefOrValue SetBody(msg). SetResult(&replyMsg). Post(urlRequestMessage) - require.NoError(ts.t, err) - require.Equal(ts.t, 200, resp.StatusCode(), "POST %s [%d]: %s", urlUploadData, resp.StatusCode(), resp.String()) + require.NoError(ts.T(), err) + require.Equal(ts.T(), 200, resp.StatusCode(), "POST %s [%d]: %s", urlUploadData, resp.StatusCode(), resp.String()) return &replyMsg } @@ -732,10 +722,10 @@ func CreateFFI(t *testing.T, client *resty.Client, ffi *fftypes.FFI) (interface{ return res, err } -func CreateContractAPI(t *testing.T, client *resty.Client, name string, FFIReference *fftypes.FFIReference, location *fftypes.JSONAny) (interface{}, error) { +func CreateContractAPI(t *testing.T, client *resty.Client, name string, ffiReference *fftypes.FFIReference, location *fftypes.JSONAny) (interface{}, error) { apiReqBody := &core.ContractAPI{ Name: name, - Interface: FFIReference, + Interface: ffiReference, Location: location, } @@ -751,12 +741,12 @@ func CreateContractAPI(t *testing.T, client *resty.Client, name string, FFIRefer return res, err } -func InvokeContractAPIMethod(t *testing.T, client *resty.Client, APIName string, methodName string, input *fftypes.JSONAny) (interface{}, error) { +func InvokeContractAPIMethod(t *testing.T, client *resty.Client, apiName string, methodName string, input *fftypes.JSONAny) (interface{}, error) { apiReqBody := map[string]interface{}{ "input": input, } var res interface{} - path := fmt.Sprintf("%s/%s/invoke/%s", urlContractAPI, APIName, methodName) + path := fmt.Sprintf("%s/%s/invoke/%s", urlContractAPI, apiName, methodName) resp, err := client.R(). SetBody(apiReqBody). SetResult(&res). @@ -767,12 +757,12 @@ func InvokeContractAPIMethod(t *testing.T, client *resty.Client, APIName string, return res, err } -func QueryContractAPIMethod(t *testing.T, client *resty.Client, APIName string, methodName string, input *fftypes.JSONAny) (interface{}, error) { +func QueryContractAPIMethod(t *testing.T, client *resty.Client, apiName string, methodName string, input *fftypes.JSONAny) (interface{}, error) { apiReqBody := map[string]interface{}{ "input": input, } var res interface{} - path := fmt.Sprintf("%s/%s/query/%s", urlContractAPI, APIName, methodName) + path := fmt.Sprintf("%s/%s/query/%s", urlContractAPI, apiName, methodName) resp, err := client.R(). SetBody(apiReqBody). SetResult(&res). @@ -782,12 +772,12 @@ func QueryContractAPIMethod(t *testing.T, client *resty.Client, APIName string, return res, err } -func CreateContractAPIListener(t *testing.T, client *resty.Client, APIName, eventName, topic string) (*core.ContractListener, error) { +func CreateContractAPIListener(t *testing.T, client *resty.Client, apiName, eventName, topic string) (*core.ContractListener, error) { apiReqBody := map[string]interface{}{ "topic": topic, } var listener core.ContractListener - path := fmt.Sprintf("%s/%s/listeners/%s", urlContractAPI, APIName, eventName) + path := fmt.Sprintf("%s/%s/listeners/%s", urlContractAPI, apiName, eventName) resp, err := client.R(). SetBody(apiReqBody). SetResult(&listener). diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 8368e7ccc7..6d1ddcb5b7 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -10,13 +10,13 @@ CLI_VERSION=$(cat $CWD/../../manifest.json | jq -r .cli.tag) STACKS_DIR=~/.firefly/stacks create_accounts() { - if [ "$TEST_SUITE" == "TestEthereumE2ESuite" ]; then + if [ "$TEST_SUITE" == "TestEthereumMultipartyE2ESuite" ]; then # Create 4 new accounts for use in testing for i in {1..4} do $CLI accounts create $STACK_NAME done - elif [ "$TEST_SUITE" == "TestFabricE2ESuite" ]; then + elif [ "$TEST_SUITE" == "TestFabricMultipartyE2ESuite" ]; then # Create 4 new accounts for the first org for use in testing for i in {1..3} do @@ -61,6 +61,10 @@ if [ -z "${DATABASE_TYPE}" ]; then DATABASE_TYPE=sqlite3 fi +if [ -z "${MULTIPARTY_ENABLED}" ]; then + MULTIPARTY_ENABLED=true +fi + if [ -z "${STACK_FILE}" ]; then STACK_FILE=$STACKS_DIR/$STACK_NAME/stack.json fi @@ -78,7 +82,19 @@ if [ -z "${TOKENS_PROVIDER}" ]; then fi if [ -z "${TEST_SUITE}" ]; then - TEST_SUITE=TestEthereumE2ESuite + if [ "${BLOCKCHAIN_PROVIDER}" == "fabric" ]; then + if [ "${MULTIPARTY_ENABLED}" == "true" ]; then + TEST_SUITE=TestFabricMultipartyE2ESuite + else + TEST_SUITE=TestFabricGatewayE2ESuite + fi + else + if [ "${MULTIPARTY_ENABLED}" == "true" ]; then + TEST_SUITE=TestEthereumMultipartyE2ESuite + else + TEST_SUITE=TestEthereumGatewayE2ESuite + fi + fi fi cd $CWD @@ -98,7 +114,7 @@ if [ "$DOWNLOAD_CLI" == "true" ]; then fi if [ "$CREATE_STACK" == "true" ]; then - $CLI init --prometheus-enabled --database $DATABASE_TYPE $STACK_NAME 2 --blockchain-provider $BLOCKCHAIN_PROVIDER --token-providers $TOKENS_PROVIDER --manifest ../../manifest.json $EXTRA_INIT_ARGS --sandbox-enabled=false + $CLI init --prometheus-enabled --database $DATABASE_TYPE $STACK_NAME 2 --blockchain-provider $BLOCKCHAIN_PROVIDER --token-providers $TOKENS_PROVIDER --manifest ../../manifest.json $EXTRA_INIT_ARGS --sandbox-enabled=false --multiparty=$MULTIPARTY_ENABLED checkOk $? $CLI pull $STACK_NAME -r 3 @@ -108,7 +124,7 @@ if [ "$CREATE_STACK" == "true" ]; then checkOk $? fi -if [ "$TEST_SUITE" == "TestEthereumE2ESuite" ]; then +if [ "$TEST_SUITE" == "TestEthereumMultipartyE2ESuite" ] || [ "$TEST_SUITE" == "TestEthereumGatewayE2ESuite" ]; then export CONTRACT_ADDRESS=$($CLI deploy ethereum $STACK_NAME ../data/simplestorage/simple_storage.json | jq -r '.address') fi @@ -120,7 +136,7 @@ checkOk $? export STACK_FILE export STACK_STATE -go clean -testcache && go test -v . -run $TEST_SUITE +go clean -testcache && go test -v ./multiparty ./gateway -run $TEST_SUITE checkOk $? if [ "$RESTART" == "true" ]; then @@ -132,6 +148,6 @@ if [ "$RESTART" == "true" ]; then create_accounts - go clean -testcache && go test -v . -run $TEST_SUITE + go clean -testcache && go test -v ./multiparty ./gateway -run $TEST_SUITE checkOk $? fi \ No newline at end of file diff --git a/test/e2e/stack.go b/test/e2e/stack.go index 42f5212af7..75df4e9dc3 100644 --- a/test/e2e/stack.go +++ b/test/e2e/stack.go @@ -74,7 +74,7 @@ func GetMemberHostname(filename string, n int) (string, error) { return stack.Members[n].FireflyHostname, nil } -func ReadStack(filename string) (*Stack, error) { +func ReadStackFile(filename string) (*Stack, error) { jsonBytes, err := ioutil.ReadFile(filename) if err != nil { return nil, err @@ -97,7 +97,7 @@ func ReadStack(filename string) (*Stack, error) { return stack, nil } -func ReadStackState(filename string) (*StackState, error) { +func ReadStackStateFile(filename string) (*StackState, error) { jsonBytes, err := ioutil.ReadFile(filename) if err != nil { return nil, err