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
This commit is contained in:
tsukino
2024-03-28 07:17:21 -04:00
committed by GitHub
parent 1ebbde57cc
commit b18fe34528
13 changed files with 260 additions and 82 deletions

View File

@@ -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

View File

@@ -6,5 +6,5 @@
"source-map-support/register"
],
"recursive": true,
"timeout": 60000
"timeout": 300000
}

View File

@@ -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"
}
}
}

View File

@@ -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: `<div>Testing "new-test":<div id="new-test"></div></div>`. The div id must be the same as the filename.

View File

@@ -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);
}
})();

View File

@@ -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;
}
})();

View File

@@ -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('<h1>XXXXXXXXXXXXXX</h1>'));
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;
}
})();

View File

@@ -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('<h1>XXXXXXXXXXXXXX</h1>'));
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<string> {
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<string> => {
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();
}

3
test/utils.ts Normal file
View File

@@ -0,0 +1,3 @@
export function assert(expr: any, msg = 'unknown assertion error') {
if (!Boolean(expr)) throw new Error(msg);
}

1
utils/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
tlsn

32
utils/build-tlsn-binaries.sh Executable file
View File

@@ -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

View File

@@ -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',