From b18fe3452888552ed4476d9a2ef8f99f704abd95 Mon Sep 17 00:00:00 2001 From: tsukino <87639218+0xtsukino@users.noreply.github.com> Date: Thu, 28 Mar 2024 07:17:21 -0400 Subject: [PATCH] chore: fix test in github action (#41) * Typescript tests in chrome * increase timeout to 5min * close page * force close browser * force exit process * refactor: clean up test code * chore: update readme for adding a new test --- .github/workflows/test.yaml | 19 +- .mocharc.json | 2 +- package.json | 10 +- readme.md | 7 + test/simple-verify.spec.ts | 23 --- .../full-integration-swapi.spec.ts | 20 +- test/specs/simple-verify.spec.ts | 34 ++++ test/testRunner.ts | 184 ++++++++++++++---- test/utils.ts | 3 + utils/.gitignore | 1 + utils/build-tlsn-binaries.sh | 32 +++ {.cargo => wasm/.cargo}/config.toml | 0 webpack.web.dev.config.js | 7 +- 13 files changed, 260 insertions(+), 82 deletions(-) delete mode 100644 test/simple-verify.spec.ts rename test/{ => specs}/full-integration-swapi.spec.ts (61%) create mode 100644 test/specs/simple-verify.spec.ts create mode 100644 test/utils.ts create mode 100644 utils/.gitignore create mode 100755 utils/build-tlsn-binaries.sh rename {.cargo => wasm/.cargo}/config.toml (100%) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5603fa0..497e979 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,7 +7,8 @@ on: branches: [ main ] env: - LOCAL: true + LOCAL-NOTARY: true + LOCAL-WS: false HEADLESS: true PUPPETEER_SKIP_DOWNLOAD: true @@ -31,6 +32,15 @@ jobs: with: workspaces: wasm/prover + - name: Install Chrome + uses: browser-actions/setup-chrome@v1 + id: setup-chrome + with: + chrome-version: 121.0.6167.85 + + - name: Set CHROME_PATH environment variable + run: echo "CHROME_PATH=${{ steps.setup-chrome.outputs['chrome-path'] }}" >> $GITHUB_ENV + - name: Install Node.js uses: actions/setup-node@v4 with: @@ -50,7 +60,7 @@ jobs: - uses: actions/cache@v4 name: Setup pnpm cache with: - path: ${{ env.STORE_PATH }} + path: ${STORE_PATH} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- @@ -67,9 +77,8 @@ jobs: - name: Build WASM run: npm run build:wasm - - name: Test WASM - run: npm run test:wasm + - name: Build Test dependencies + run: npm run build:tlsn-binaries - name: Test - if: false run: npm run test \ No newline at end of file diff --git a/.mocharc.json b/.mocharc.json index 4d570b0..1f31d44 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -6,5 +6,5 @@ "source-map-support/register" ], "recursive": true, - "timeout": 60000 + "timeout": 300000 } \ No newline at end of file diff --git a/package.json b/package.json index 4d09872..983e7c1 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "update:wasm": "sh utils/check-wasm.sh -f", "test:wasm": "cd wasm/prover; wasm-pack test --firefox --release --headless", "build:wasm": "wasm-pack build --target web wasm/prover", + "build:tlsn-binaries": "sh utils/build-tlsn-binaries.sh", "watch:dev": "webpack --config webpack.web.dev.config.js --watch", "predev": "sh utils/check-wasm.sh", "lint:wasm": "cd wasm/prover; cargo clippy --target wasm32-unknown-unknown", @@ -28,7 +29,7 @@ "lint:tsc": "tsc --noEmit", "lint": "concurrently npm:lint:tsc npm:lint:eslint", "run:test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/testRunner.ts'", - "test": "npm run build:test && npm run run:test" + "test": "npm run build:tlsn-binaries && npm run build:test && npm run run:test" }, "dependencies": { "comlink": "^4.4.1" @@ -38,6 +39,7 @@ "@types/mocha": "^10.0.6", "@types/serve-handler": "^6.1.4", "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/parser": "7.0.2", "browserify": "^17.0.0", "concurrently": "^5.1.0", "constants-browserify": "^1.0.0", @@ -50,6 +52,7 @@ "html-webpack-plugin": "~5.3.2", "https-browserify": "^1.0.0", "image-webpack-loader": "^6.0.0", + "js-yaml": "^4.1.0", "mocha": "^10.2.0", "node-loader": "^0.6.0", "prettier": "^3.0.2", @@ -65,11 +68,12 @@ "webpack": "^5.75.0", "webpack-cli": "^5.0.0", "webpack-dev-server": "^4.11.1", - "webpack-node-externals": "^3.0.0" + "webpack-node-externals": "^3.0.0", + "wtfnode": "^0.9.1" }, "author": "", "license": "ISC", "engines": { "node": ">= 16.20.2" } -} \ No newline at end of file +} diff --git a/readme.md b/readme.md index b4d36ec..99dc831 100644 --- a/readme.md +++ b/readme.md @@ -63,3 +63,10 @@ npm run dev npm install npm run build ``` + +## Adding a new test +1. Create a new `new-test.spec.ts` file in the `test/` directory +2. Add your spec file to the entry object fin `webpack.web.dev.config.js` +3. Add a new `div` block to `test/test.ejs` like this: `
Testing "new-test":
`. The div id must be the same as the filename. + + diff --git a/test/simple-verify.spec.ts b/test/simple-verify.spec.ts deleted file mode 100644 index d660dba..0000000 --- a/test/simple-verify.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { verify } from '../src'; -import simple_proof_redacted from './assets/simple_proof_redacted.json'; - -(async function verify_simple() { - try { - const pem = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr\ncRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==\n-----END PUBLIC KEY-----`; - const proof = { - ...simple_proof_redacted, - notaryUrl: 'http://localhost:7047', - }; - console.log(proof); - console.time('verify'); - const result = await verify(proof, pem); - console.timeEnd('verify'); - - // @ts-ignore - document.getElementById('simple-verify').textContent = - JSON.stringify(result); - } catch (err) { - console.log('caught error from wasm'); - console.error(err); - } -})(); diff --git a/test/full-integration-swapi.spec.ts b/test/specs/full-integration-swapi.spec.ts similarity index 61% rename from test/full-integration-swapi.spec.ts rename to test/specs/full-integration-swapi.spec.ts index 614c67f..f4a7e8d 100644 --- a/test/full-integration-swapi.spec.ts +++ b/test/specs/full-integration-swapi.spec.ts @@ -1,4 +1,5 @@ -import { prove, verify } from '../src'; +import { prove, verify } from '../../src'; +import { assert } from '../utils'; (async function () { try { @@ -9,10 +10,10 @@ import { prove, verify } from '../src'; method: 'GET', headers: { secret: 'test_secret' }, maxTranscriptSize: 16384, - notaryUrl: process.env.LOCAL + notaryUrl: process.env.LOCAL_NOTARY ? 'http://localhost:7047' : 'https://notary.pse.dev', - websocketProxyUrl: process.env.LOCAL + websocketProxyUrl: process.env.LOCAL_WS ? 'ws://localhost:55688' : 'wss://notary.pse.dev/proxy?token=swapi.dev', secretHeaders: ['test_secret'], @@ -27,11 +28,20 @@ import { prove, verify } from '../src'; console.timeEnd('verify'); console.log(result); + + assert(result.sent.includes('host: swapi.dev')); + assert(result.sent.includes('secret: XXXXXXXXXXX')); + assert(result.recv.includes('Luke Skywalker')); + assert(result.recv.includes('"hair_color":"XXXXX"')); + assert(result.recv.includes('"skin_color":"XXXX"')); + // @ts-ignore - document.getElementById('full-integration-swapi').textContent = - JSON.stringify(result); + document.getElementById('full-integration-swapi').textContent = 'OK'; } catch (err) { console.log('caught error from wasm'); console.error(err); + + // @ts-ignore + document.getElementById('full-integration-swapi').textContent = err.message; } })(); diff --git a/test/specs/simple-verify.spec.ts b/test/specs/simple-verify.spec.ts new file mode 100644 index 0000000..c94b4e8 --- /dev/null +++ b/test/specs/simple-verify.spec.ts @@ -0,0 +1,34 @@ +import { verify } from '../../src'; +import simple_proof_redacted from '../assets/simple_proof_redacted.json'; +import { assert } from '../utils'; + +(async function verify_simple() { + try { + const pem = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr\ncRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==\n-----END PUBLIC KEY-----`; + const proof = { + ...simple_proof_redacted, + notaryUrl: 'http://127.0.0.1:7047', + }; + console.log(proof); + console.time('verify'); + const result = await verify(proof, pem); + console.timeEnd('verify'); + + assert( + result.sent.includes( + 'user-agent: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + ), + ); + assert(result.recv.includes('

XXXXXXXXXXXXXX

')); + assert(result); + + // @ts-ignore + document.getElementById('simple-verify').textContent = 'OK'; + } catch (err) { + console.log('caught error from wasm'); + console.error(err); + + // @ts-ignore + document.getElementById('simple-verify').textContent = err.message; + } +})(); diff --git a/test/testRunner.ts b/test/testRunner.ts index 173973d..2a71281 100644 --- a/test/testRunner.ts +++ b/test/testRunner.ts @@ -1,67 +1,167 @@ -import puppeteer from 'puppeteer'; +import puppeteer, { Browser, Page, PuppeteerLaunchOptions } from 'puppeteer'; import { describe, it, before, after } from 'mocha'; const assert = require('assert'); -const { exec } = require('node:child_process'); +import { exec, ChildProcess } from 'node:child_process'; +import * as fs from 'fs'; +import path from 'path'; +const yaml = require('js-yaml'); + +const timeout = 300000; // puppeteer options -const opts = { - headless: !!process.env.HEADLESS, +let opts: PuppeteerLaunchOptions = { + headless: !!process.env.HEADLESS ? 'new' : false, slowMo: 100, - timeout: 60000, + timeout: timeout, }; -let browser: any, page: any, server: any; +if (process.env.CHROME_PATH) { + opts = { + ...opts, + executablePath: process.env.CHROME_PATH, + }; +} + +let browser: Browser; +let page: Page; +let server: ChildProcess; + +let tlsnServerFixture: ChildProcess; +const spawnTlsnServerFixture = () => { + const tlsnServerFixturePath = './utils/tlsn/tlsn/tlsn-server-fixture/'; + // Spawn the server process + // tlsnServerFixture = spawn(tlsnServerFixturePath, []); + tlsnServerFixture = exec(`../target/release/main`, { + cwd: tlsnServerFixturePath, + }); + + tlsnServerFixture.stdout?.on('data', (data) => { + console.log(`Server: ${data}`); + }); + + tlsnServerFixture.stderr?.on('data', (data) => { + console.error(`Server Error: ${data}`); + }); +}; + +let localNotaryServer: ChildProcess; +const spawnLocalNotaryServer = async () => { + const localNotaryServerPath = './utils/tlsn/notary-server/'; + localNotaryServer = exec(`target/release/notary-server`, { + cwd: localNotaryServerPath, + }); + localNotaryServer.stdout?.on('data', (data) => { + console.log(`Server: ${data}`); + }); + + localNotaryServer.stderr?.on('data', (data) => { + console.error(`Server Error: ${data}`); + }); + + // wait for the notary server to be ready + while (true) { + try { + const response = await fetch('http://127.0.0.1:7047/info'); + if (response.ok) { + return; + } + } catch (error) { + console.error('Waiting for local notary server...', error); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } +}; + +const configureNotarySerer = () => { + try { + const configPath = './utils/tlsn/notary-server/config/config.yaml'; + const fileContents = fs.readFileSync(configPath, 'utf8'); + const data = yaml.load(fileContents) as any; + data.tls.enabled = false; + data.server.host = '127.0.0.1'; + const newYaml = yaml.dump(data); + fs.writeFileSync(configPath, newYaml, 'utf8'); + console.log('YAML file has been updated.'); + } catch (error) { + console.error('Error reading or updating the YAML file:', error); + } +}; // expose variables before(async function () { server = exec('serve --config ../serve.json ./test-build -l 3001'); + + spawnTlsnServerFixture(); + configureNotarySerer(); + await spawnLocalNotaryServer(); browser = await puppeteer.launch(opts); page = await browser.newPage(); - await page.goto('http://localhost:3001'); + await page.goto('http://127.0.0.1:3001'); }); // close browser and reset global variables after(async function () { - await server.kill(); - // @ts-ignore - await browser.close(); + console.log('Cleaning up:'); + + try { + tlsnServerFixture.kill(); + console.log('* Stopped TLSN Server Fixture ✅'); + + localNotaryServer.kill(); + console.log('* Stopped Notary Server ✅'); + + server.kill(); + console.log('* Stopped Test Web Server ✅'); + + await page.close(); + await browser.close(); + const childProcess = browser.process(); + if (childProcess) { + childProcess.kill(9); + } + console.log('* Closed browser ✅'); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(0); + } }); describe('tlsn-js test suite', function () { - it('should prove and verify swapi.dev', async function () { - const content = await check('full-integration-swapi'); - const result = safeParseJson(content); - assert(result.sent.includes('host: swapi.dev')); - assert(result.sent.includes('secret: XXXXXXXXXXX')); - assert(result.recv.includes('Luke Skywalker')); - assert(result.recv.includes('"hair_color":"XXXXX"')); - assert(result.recv.includes('"skin_color":"XXXX"')); - }); - - it('should verify', async function () { - const content = await check('simple-verify'); - const result = safeParseJson(content); - assert( - result.sent.includes( - 'user-agent: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', - ), - ); - assert(result.recv.includes('

XXXXXXXXXXXXXX

')); - assert(result); + fs.readdirSync(path.join(__dirname, 'specs')).forEach((file) => { + const [id] = file.split('.'); + it(`Test ID: ${id}`, async function () { + const content = await check(id); + assert(content === 'OK'); + }); }); + // it('should prove and verify data from the local tlsn-server-fixture', async function () { + // const content = await check('full-integration-swapi'); + // assert(content === 'OK'); + // }); + // + // it('should verify', async function () { + // const content = await check('simple-verify'); + // assert(content === 'OK'); + // }); }); async function check(testId: string): Promise { - const content = await page.$eval('#' + testId, (n: any) => n.innerText); - if (content) return content; - await new Promise((r) => setTimeout(r, 1000)); - return check(testId); -} - -function safeParseJson(data: string): any | null { - try { - return JSON.parse(data); - } catch (e) { - return null; - } + const startTime = Date.now(); + const attemptFetchContent = async (): Promise => { + const content = await page.$eval( + `#${testId}`, + (el: any) => el.textContent || '', + ); + if (content) return content; + const elapsedTime = Date.now() - startTime; + if (elapsedTime >= timeout) { + throw new Error( + `Timeout: Failed to retrieve content for '#${testId}' within ${timeout} ms.`, + ); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + return attemptFetchContent(); + }; + return attemptFetchContent(); } diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..3ddb6a6 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,3 @@ +export function assert(expr: any, msg = 'unknown assertion error') { + if (!Boolean(expr)) throw new Error(msg); +} diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..0285576 --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1 @@ +tlsn diff --git a/utils/build-tlsn-binaries.sh b/utils/build-tlsn-binaries.sh new file mode 100755 index 0000000..9504051 --- /dev/null +++ b/utils/build-tlsn-binaries.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Run tlsn Server fixture + +# Set the directory to the location of the script +cd "$(dirname "$0")" + +# Name of the directory where the repo will be cloned +REPO_DIR="tlsn" + +# Check if the directory exists +if [ ! -d "$REPO_DIR" ]; then + # Clone the repository if it does not exist + git clone https://github.com/tlsnotary/tlsn.git "$REPO_DIR" + cd "$REPO_DIR" +else + # If the directory exists, just change to it + cd "$REPO_DIR" + # Fetch the latest changes in the repo without checkout + git fetch +fi + +# Checkout the specific tag +git checkout "v0.1.0-alpha.4" + +for dir in "tlsn/tlsn-server-fixture/" "notary-server"; do + # Change to the specific subdirectory + cd ${dir} + + # Build the project + cargo build --release + cd - +done diff --git a/.cargo/config.toml b/wasm/.cargo/config.toml similarity index 100% rename from .cargo/config.toml rename to wasm/.cargo/config.toml diff --git a/webpack.web.dev.config.js b/webpack.web.dev.config.js index 8a17d39..08bbba2 100644 --- a/webpack.web.dev.config.js +++ b/webpack.web.dev.config.js @@ -7,7 +7,8 @@ const isProd = process.env.NODE_ENV === 'production'; const envPlugin = new webpack.EnvironmentPlugin({ NODE_ENV: 'development', - LOCAL: false, + LOCAL_NOTARY: true, + LOCAL_WS: false, HEADLESS: false, }); @@ -37,8 +38,8 @@ module.exports = [ target: 'web', mode: isProd ? 'production' : 'development', entry: { - 'full-integration-swapi.spec': path.join(__dirname, 'test', 'full-integration-swapi.spec.ts'), - 'simple-verify': path.join(__dirname, 'test', 'simple-verify.spec.ts'), + 'full-integration-swapi.spec': path.join(__dirname, 'test', 'specs', 'full-integration-swapi.spec.ts'), + 'simple-verify': path.join(__dirname, 'test', 'specs', 'simple-verify.spec.ts'), }, output: { path: __dirname + '/test-build',