Repository: ergoplatform/sigma-rust (language: rust)

Issue:
[300 SigmaUSD] Integration testing suite for P2P networking
### Motivation
- To test our implementation of P2P networking against the reference implementation in Scala.
- To catch regressions introduced during the development.

### Implementation options
The idea is to test P2P requests and responses against running ergo node built from the master branch https://github.com/ergoplatform/ergo
In our tests, we should connect to the node and run our scenarios. Ideally, we want to make tests stateless, i.e. tests should not depend on changes to the node's state made in other tests. Although we can ignore it for now due to initial ergo-p2p requests being intended for the SPV node and does not change the node's state. In the future when we implement P2P requests that change the node's state we'd want them to be stateless. So it makes sense to lay the foundation for it now.

Possible options to run ergo node:
### 1. Launch as a standalone application
Launch the node as usual, let it sync with the mainnet, and then kill it after our tests are done.
Cons:
- wasting mainnet nodes resources;
- risk of banning by github since running nodes are against github actions TOS;

### 2. Run node's integration test
Create a special integration test that starts the node in its own net in mining (block generation) mode. This test can be then called via `sbt it:test [name]` command.

See the example of ergo integration test - https://github.com/ergoplatform/ergo/blob/95139ec990fc91f8f4c70a61468edf8834143329/src/it/scala/org/ergoplatform/it/NodeRecoverySpec.scala#L14-L53

Clearly, we want the second option, i.e. run the node via its own integration test. Also, it fits into the future stateless tests requirement, where we just launch the node via its own integration test for any of our single tests.

### Todo
- Write an integration test in ergo node to run it in our tests;
- Establish GA workflow for p2p integration tests;



Discussion / hints:
Starting this one.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7b68ee1da..2a4dd81b2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -109,10 +109,24 @@ jobs:
     runs-on: ubuntu-latest
     env:
       HAS_NPM_TOKEN: ${{ secrets.NPM_TOKEN != '' }}
-    defaults:
-      run:
-        working-directory: bindings/ergo-lib-wasm
+
+    # Service containers to run `ergo` node in `devnet` mode
+    services:
+      # Label used to access the service container
+      ergo_node:
+        # Docker Hub image
+        image: ergoplatform/ergo-devnet:latest
+        ports:
+          # Maps tcp port 9053 on service container to the host
+          - 9053:9053
+    
     steps:
+      # Need the following command to get around a GA bug where the workspace can't be cleared due
+      # to incorrect access permissions. This comes up due to the use of the service container
+      # above. See https://github.com/actions/checkout/issues/211
+      - name: set_file_permissions
+        run: sudo chown -R $USER:$USER ${{ github.workspace }}
+
       - name: checkout
         uses: actions/checkout@v2
 
@@ -123,12 +137,14 @@ jobs:
 
       - name: install deps
         run: |
+          cd bindings/ergo-lib-wasm
           curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
           curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
           sudo apt-get -y install nodejs
 
       - name: run Wasm tests
         run: |
+          cd bindings/ergo-lib-wasm
           wasm-pack build
           # wasm-pack test  --firefox --headless
           # wasm-pack test  --firefox --headless --release
@@ -137,18 +153,28 @@ jobs:
 
       - name: run node JS tests
         run: |
+          cd bindings/ergo-lib-wasm
           wasm-pack build
           npm install
           npm run test
-
+      
+      - name: run node JS integration tests
+        run: |
+          cd bindings/ergo-lib-wasm
+          # wasm-pack build # --features rest
+          npm install
+          npm run integration-test
+      
       - name: run browser JS tests
         run: |
+          cd bindings/ergo-lib-wasm
           wasm-pack build
           npm install
           npm run test-browser
 
       - name: build alpha versions
         run: |
+          cd bindings/ergo-lib-wasm
           npm run build-nodejs-alpha
           npm run build-browser-alpha
 
@@ -207,8 +233,20 @@ jobs:
 
   ios_tests:
     name: Test Swift(iOS) bindings
-    runs-on: macos-latest
+    runs-on: ubuntu-latest
+
+    # Service containers to run `ergo` node in `devnet` mode
+    services:
+      # Label used to access the service container
+      ergo_node:
+        # Docker Hub image
+        image: ergoplatform/ergo-devnet:latest
+        ports:
+          # Maps tcp port 9053 on service container to the host
+          - 9053:9053
+
     steps:
+      - uses: fwal/setup-swift@v1
       - name: checkout
         uses: actions/checkout@v2
 
diff --git a/bindings/ergo-lib-ios/Sources/ErgoLib/RestNodeApi.swift b/bindings/ergo-lib-ios/Sources/ErgoLib/RestNodeApi.swift
index 75fd5c2df..84b4ca703 100644
--- a/bindings/ergo-lib-ios/Sources/ErgoLib/RestNodeApi.swift
+++ b/bindings/ergo-lib-ios/Sources/ErgoLib/RestNodeApi.swift
@@ -32,14 +32,17 @@ class RestNodeApi {
     }
 
 
-    // need macOS 12.0 on GA
-    /* func getInfoAsync(nodeConf: NodeConf) async throws -> NodeInfo { */
-    /*     try await withCheckedThrowingContinuation { continuation in */
-    /*         getInfo(nodeConf: nodeConf) { result in */ 
-    /*             continuation.resume(with: result) */
-    /*         } */
-    /*     } */
-    /* } */
+    /// GET on /info endpoint (async)
+    @available(macOS 10.15, iOS 13, *)
+    func getInfoAsync(nodeConf: NodeConf) async throws -> NodeInfo {
+        try await withCheckedThrowingContinuation { continuation in
+            do {
+                let _ = try getInfo(nodeConf: nodeConf) { result in
+                    continuation.resume(with: result)
+                }
+            } catch {}
+        }
+    }
 
     deinit {
         ergo_lib_rest_api_runtime_delete(self.pointer)
diff --git a/bindings/ergo-lib-ios/Tests/ErgoLibTests/RestNodeApiTests.swift b/bindings/ergo-lib-ios/Tests/ErgoLibTests/RestNodeApiTests.swift
index f665023f3..1a6353419 100644
--- a/bindings/ergo-lib-ios/Tests/ErgoLibTests/RestNodeApiTests.swift
+++ b/bindings/ergo-lib-ios/Tests/ErgoLibTests/RestNodeApiTests.swift
@@ -7,7 +7,7 @@ final class RestNodeApiTests: XCTestCase {
 
     func testGetInfo() throws {
         let expectation = self.expectation(description: "getInfo")
-        let nodeConf = try NodeConf(withAddrString: "213.239.193.208:9053")
+        let nodeConf = try NodeConf(withAddrString: "127.0.0.1:9053")
         let restNodeApi = try RestNodeApi()
         let _ = try restNodeApi.getInfo(nodeConf: nodeConf,
             closure: { (res: Result<NodeInfo, Error>) -> () in 
@@ -23,7 +23,7 @@ final class RestNodeApiTests: XCTestCase {
     }
 
     func testGetInfoAbort() throws {
-        let nodeConf = try NodeConf(withAddrString: "213.239.193.208:9053")
+        let nodeConf = try NodeConf(withAddrString: "127.0.0.1:9053")
         let restNodeApi = try RestNodeApi()
         let handle = try restNodeApi.getInfo(nodeConf: nodeConf,
             closure: { (res: Result<NodeInfo, Error>) -> () in 
@@ -33,17 +33,17 @@ final class RestNodeApiTests: XCTestCase {
     }
 
     // need macOS 12.0 on Github GA
-    /* func testGetInfoAsync() throws { */
-    /*     let nodeConf = try NodeConf(withAddrString: "213.239.193.208:9053") */
-    /*     let restNodeApi = try RestNodeApi() */
-    /*     XCTAssertNoThrow(Task(priority: .medium) { */
-    /*         let nodeInfo = try await restNodeApi.getInfoAsync(nodeConf: nodeConf) */
-    /*         XCTAssert(!nodeInfo.getName().isEmpty) */
-    /*     }) */
-    /*     // test of re-using of tokio runtime */
-    /*     XCTAssertNoThrow(Task(priority: .medium) { */
-    /*         let nodeInfo = try await restNodeApi.getInfoAsync(nodeConf: nodeConf) */
-    /*         XCTAssert(!nodeInfo.getName().isEmpty) */
-    /*     }) */
-    /* } */
+    func testGetInfoAsync() throws {
+        let nodeConf = try NodeConf(withAddrString: "127.0.0.1:9053")
+        let restNodeApi = try RestNodeApi()
+        XCTAssertNoThrow(Task(priority: .medium) {
+            let nodeInfo = try await restNodeApi.getInfoAsync(nodeConf: nodeConf)
+            XCTAssert(!nodeInfo.getName().isEmpty)
+        })
+        // test of re-using of tokio runtime
+        XCTAssertNoThrow(Task(priority: .medium) {
+            let nodeInfo = try await restNodeApi.getInfoAsync(nodeConf: nodeConf)
+            XCTAssert(!nodeInfo.getName().isEmpty)
+        }) 
+    } 
 }
diff --git a/bindings/ergo-lib-wasm/package.json b/bindings/ergo-lib-wasm/package.json
index 52c3601eb..a65ad6232 100644
--- a/bindings/ergo-lib-wasm/package.json
+++ b/bindings/ergo-lib-wasm/package.json
@@ -6,6 +6,7 @@
         "build": "webpack",
         "serve": "wasm-pack build --target browser && webpack-dev-server",
         "test": "wasm-pack build --target nodejs && mocha --require @babel/register --require @babel/polyfill 'tests/test*.js'",
+        "integration-test": "wasm-pack build --target nodejs --features rest && mocha --require @babel/register --require @babel/polyfill 'tests/integration_test*.js'",
         "test-browser": "rm -rf ./pkg && npm run build-browser && mv ./pkg-browser ./pkg && ./node_modules/.bin/karma start",
         "doc": "jsdoc -c jsdoc.json pkg/ergo_wallet_wasm.js README.md -d docs",
         "build-nodejs": "rm -rf ./pkg-nodejs && wasm-pack build --target nodejs --out-dir pkg-nodejs && cd pkg-nodejs && node ../scripts/publish_helper -nodejs",
@@ -33,10 +34,11 @@
         "karma-webpack": "^5.0.0",
         "mocha": "^6.2.3",
         "mocha-loader": "^2.0.1",
+        "node-fetch": "^2.6.7",
         "text-encoder": "0.0.4",
         "webpack": "^5.37.1"
     },
     "dependencies": {
         "babel": "^6.23.0"
     }
-}
+}
\ No newline at end of file


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/integration_test_rest_api.js b/bindings/ergo-lib-wasm/tests/integration_test_rest_api.js
new file mode 100644
index 000000000..b46c36e93
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/integration_test_rest_api.js
@@ -0,0 +1,26 @@
+import { assert } from 'chai';
+
+import {
+  get_info, NodeConf,
+} from '../pkg/ergo_lib_wasm';
+
+// Note that REST calls fail without bringing in `Headers`, `Requests` and `Response` from the
+// `node-fetch` JS package. See https://rustwasm.github.io/wasm-pack/book/prerequisites/considerations.html
+import fetch from 'node-fetch';
+
+// @ts-ignore
+global.fetch = fetch;
+// @ts-ignore
+global.Headers = fetch.Headers;
+// @ts-ignore
+global.Request = fetch.Request;
+// @ts-ignore
+global.Response = fetch.Response;
+
+it('node REST API get_info endpoint', async () => {
+  let node_conf = new NodeConf("127.0.0.1:9053");
+  assert(node_conf != null);
+  let res = await get_info(node_conf);
+  assert(res != null);
+  assert(node_conf != null);
+});
diff --git a/bindings/ergo-lib-wasm/tests/test_rest_api.js b/bindings/ergo-lib-wasm/tests/test_rest_api.js
deleted file mode 100644
index ff9c521a5..000000000
--- a/bindings/ergo-lib-wasm/tests/test_rest_api.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { assert } from 'chai';
-
-import {
-  get_info, NodeConf,
-} from '../pkg/ergo_lib_wasm';
-
-// it('node REST API get_info endpoint', async () => {
-//   let node_conf = new NodeConf("213.239.193.208:9053");
-//   assert(node_conf != null);
-//   let res = await get_info(node_conf);
-//   assert(res != null);
-//   assert(node_conf != null);
-// });