Skip to content

Commit

Permalink
Add Wasm examples to CI (#345)
Browse files Browse the repository at this point in the history
* Fix Wasm README.md example

Add Wasm examples CI job.
Update build-and-test CI to run wasm_bindgen annotated tests.

* Add txm annotations to test Wasm README.md example

Add txm to examples CI job.

* Fix Wasm node and README.md examples tests

The examples will now throw error codes on unsuccessful termination.

* Enable wasm examples CI job on PR temporarily

* Add configurable branch/ref for example CI

* Disable examples CI job on pull requests

* Rename Wasm Examples CI job

* Switch to jest for testing node bindings examples

* Add wasm browser example tests with cypress

Fix broken browser examples, doc strings.
Update example CI job with browser tests.
Temporarily enable examples CI job on PR for testing.

* Disable examples CI on PR

Update yarn.lock to include cypress

* improve node tests reliability

* improve browser test reliability

Co-authored-by: Abdulrahim Al Methiab <abdulrahim.almethiab@iota.org>
  • Loading branch information
cycraig and Abdulrahim Al Methiab committed Aug 5, 2021
1 parent 9f6b692 commit 2d9eca2
Show file tree
Hide file tree
Showing 32 changed files with 3,064 additions and 140 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/audit.yml
Expand Up @@ -2,7 +2,7 @@ name: Audit

on:
schedule:
- cron: '0 0 * * *'
- cron: '0 0 * * *' # run at midnight every day
push:
branches:
- main
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/build-and-test.yml
Expand Up @@ -359,6 +359,10 @@ jobs:
command: test
args: --manifest-path ./bindings/wasm/Cargo.toml --release

- name: Run wasm-bindgen tests # tests annotated with #[wasm_bindgen_test]
run: wasm-pack test --node
working-directory: bindings/wasm

- name: Print sccache stats
run: sccache --show-stats

Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/examples.yml
@@ -0,0 +1,56 @@
name: Wasm Examples

on:
schedule:
- cron: '0 0 * * *' # run at midnight every day
workflow_dispatch:
inputs:
ref:
description: "Branch/ref (default dev branch)"
required: true
default: 'dev' # checkout dev branch by default, not main

jobs:
build-and-test-examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.ref }}

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 15.x

- name: Install txm # for running README.md examples https://github.com/anko/txm
run: yarn global add txm

- name: Install wasm-pack
run: yarn global add wasm-pack

- name: Install JS dependencies
run: yarn
working-directory: bindings/wasm

- name: Build Wasm bindings
run: yarn build
working-directory: bindings/wasm

- name: Test Wasm examples (node)
run: yarn run test:node
working-directory: bindings/wasm

- name: Test Wasm examples (browser)
run: yarn run test:browser
working-directory: bindings/wasm

- name: Test Wasm README.md examples
run: yarn run test:readme
working-directory: bindings/wasm
22 changes: 15 additions & 7 deletions bindings/wasm/.gitignore
@@ -1,11 +1,19 @@
/target
# Rust
target/
Cargo.lock

node_modules
wasm-web
wasm-node
web
node
pkg
# npm / yarn
node_modules/

# Build artifacts
wasm-web/
wasm-node/
web/
node/
pkg/

# cypress
cypress/screenshots/
cypress/videos/

.DS_STORE
44 changes: 31 additions & 13 deletions bindings/wasm/README.md
Expand Up @@ -24,7 +24,7 @@ $ yarn add @iota/identity-wasm@dev

Alternatively, you can build the bindings if you have Rust installed. If not, refer to [rustup.rs](https://rustup.rs) for the installation. Then install the necessary dependencies using:

```npm install```
```yarn``` or ```npm install```

and then build the bindings for `node.js` with

Expand All @@ -34,28 +34,46 @@ or for the `web` with

```npm run build:web```

## NodeJS Setup

```js
## NodeJS Usage
<!--
Test this example using https://github.com/anko/txm: `txm README.md`
Replace imports with local paths for txm:
!test program
cat \
| sed -e "s#require('@iota/identity-wasm/node')#require('./node/identity_wasm.js')#" \
| node
-->
<!-- !test check Nodejs Example -->
```javascript
const identity = require('@iota/identity-wasm/node')

// Generate a new KeyPair
const key = new identity.KeyPair(identity.KeyType.Ed25519)

// Create a new DID Document with the KeyPair as the default authentication method
const doc = identity.Document.fromKeyPair(key)
// const doc = identity.Document.fromKeyPair(key, "test") // if using the testnet

// Sign the DID Document with the sceret key
// Sign the DID Document with the private key
doc.sign(key)

// Create a default client instance for the mainnet
const config = identity.Config.fromNetwork(identity.Network.mainnet())
// const config = identity.Config.fromNetwork(identity.Network.testnet()); // if using the testnet
const client = identity.Client.fromConfig(config)

// Publish the DID Document to the IOTA Tangle
identity.publish(doc.toJSON(), { node: "https://nodes.thetangle.org:443" })
.then((message) => {
console.log("Tangle Message Id: ", message)
console.log("Tangle Message Url", `https://explorer.iota.org/mainnet/transaction/${message}`)
}).catch((error) => {
console.error("Error: ", error)
})
// The message can be viewed at https://explorer.iota.org/<mainnet|testnet>/transaction/<messageId>
client.publishDocument(doc.toJSON())
.then((receipt) => {
console.log("Tangle Message Receipt: ", receipt)
console.log("Tangle Message Url:", doc.id.network.messageURL(receipt.messageId))
})
.catch((error) => {
console.error("Error: ", error)
throw error
})
```

## Web Setup
Expand Down Expand Up @@ -114,7 +132,7 @@ new CopyWebPlugin({
}),
```

### Usage
### Web Usage

```js
import * as identity from "@iota/identity-wasm/web";
Expand Down
4 changes: 4 additions & 0 deletions bindings/wasm/cypress.json
@@ -0,0 +1,4 @@
{
"screenshotOnRunFailure": false,
"video": false
}
Empty file.
85 changes: 85 additions & 0 deletions bindings/wasm/cypress/integration/browser_test.js
@@ -0,0 +1,85 @@
import { defaultClientConfig, initIdentity } from "../../examples/browser/utils";
import { createIdentity } from "../../examples/browser/create_did.js";
import { createVC } from "../../examples/browser/create_vc.js";
import { manipulateIdentity } from "../../examples/browser/mainpulate_did.js";
import { resolveIdentity } from "../../examples/browser/resolve.js";
import { createVP } from "../../examples/browser/create_vp.js";
import { revoke } from "../../examples/browser/revocation.js";
import { merkleKey } from "../../examples/browser/merkle_key.js";

// Test that the browser examples do not throw uncaught exceptions twice, including syntax errors etc.
describe(
"Test browser examples",
{
defaultCommandTimeout: 120000, // 2 minutes to account for spurious network delays
},
() => {
beforeEach(async () => {
// The working directory is under __cypress at test runtime, so we need to go up one more level than usual
await initIdentity("../../../web/identity_wasm_bg.wasm", false);

// NOTE: `cy.wrap(defaultClientConfig()).as('config')` does not always work to make the config available
// from the shared context as `this.config` because it has a race condition with initializing the wasm.
// So call `defaultClientConfig()` manually for now.
});

it("create identity", async function () {
let identityResult;
try {
identityResult = await createIdentity(defaultClientConfig(), false);
} catch (e) {
identityResult = await createIdentity(defaultClientConfig(), false);
}
// example of testing the output, can remove if needed
expect(identityResult).to.have.all.keys("key", "doc", "receipt", "explorerUrl");
});

it("manipulate identity", async function () {
try {
await manipulateIdentity(defaultClientConfig(), false);
} catch (e) {
await manipulateIdentity(defaultClientConfig(), false);
}
});

it("resolve identity", async function () {
try {
await resolveIdentity(defaultClientConfig(), false, false);
} catch (e) {
await resolveIdentity(defaultClientConfig(), false, false);
}
});

it("create verifiable credential", async function () {
try {
await createVC(defaultClientConfig(), false);
} catch (e) {
await createVC(defaultClientConfig(), false);
}
});

it("revoke verifiable credential", async function () {
try {
await revoke(defaultClientConfig(), false);
} catch (e) {
await revoke(defaultClientConfig(), false);
}
});

it("create verifiable presentation", async function () {
try {
await createVP(defaultClientConfig(), false);
} catch (e) {
await createVP(defaultClientConfig(), false);
}
});

it("merkle key", async function () {
try {
await merkleKey(defaultClientConfig(), false);
} catch (e) {
await merkleKey(defaultClientConfig(), false);
}
});
}
);
Empty file.
Empty file.
6 changes: 3 additions & 3 deletions bindings/wasm/examples/browser/create_did.js
Expand Up @@ -6,15 +6,15 @@ import {
getExplorerUrl,
} from "./utils.js";

/*
/**
This example shows a basic introduction on how to create a basic DID Document and upload it to the Tangle.
A ED25519 Keypair is generated, from which the public key is hashed, becoming the DID.
The keypair becomes part of the DID Document in order to prove a link between the DID and the published DID Document.
That same keypair should be used to sign the original DID Document.
@param {{network: string, node: string}} clientConfig
@param {{defaultNodeURL: string, explorerURL: string, network: Network}} clientConfig
@param {boolean} log log the events to the output window
*/
**/
export async function createIdentity(clientConfig, log = true) {
if (log) logToScreen("Identity creation started...");
if (log)
Expand Down
9 changes: 5 additions & 4 deletions bindings/wasm/examples/browser/create_vc.js
Expand Up @@ -2,15 +2,16 @@ import * as identity from "../../web/identity_wasm.js";
import { manipulateIdentity } from "./mainpulate_did.js";
import { createIdentity } from "./create_did.js";
import { logObjectToScreen, logToScreen } from "./utils.js";
/*

/**
This example shows how to create a Verifiable Credential and validate it.
In this example, alice takes the role of the subject, while we also have an issuer.
The issuer signs a UniversityDegreeCredential type verifiable credential with Alice's name and DID.
This Verifiable Credential can be verified by anyone, allowing Alice to take control of it and share it with whoever they please.
@param {{network: string, node: string}} clientConfig
@param {boolean} log log the events to the output window
*/
@param {{defaultNodeURL: string, explorerURL: string, network: Network}} clientConfig
@param {boolean} log log the events to the output window
**/
export async function createVC(clientConfig, log = true) {
if (log) logToScreen("Verifiable Credential creation started...");

Expand Down
6 changes: 3 additions & 3 deletions bindings/wasm/examples/browser/create_vp.js
Expand Up @@ -2,14 +2,14 @@ import * as identity from "../../web/identity_wasm.js";
import { createVC } from "./create_vc.js";
import { logObjectToScreen, logToScreen } from "./utils.js";

/*
/**
This example shows how to create a Verifiable Presentation and validate it.
A Verifiable Presentation is the format in which a (collection of) Verifiable Credential(s) gets shared.
It is signed by the subject, to prove control over the Verifiable Credential with a nonce or timestamp.
@param {{network: string, node: string}} clientConfig
@param {{defaultNodeURL: string, explorerURL: string, network: Network}} clientConfig
@param {boolean} log log the events to the output window
*/
**/
export async function createVP(clientConfig, log = true) {
if (log) logToScreen("creating Verifiable Presentation...");

Expand Down

0 comments on commit 2d9eca2

Please sign in to comment.