Skip to content

Commit

Permalink
Add test cases that use real browser via karma. (#80)
Browse files Browse the repository at this point in the history
* Add test cases that use real browser via karma.

Signed-off-by: Hiroaki KAWAI <[email protected]>

* Use IPv4 127.0.0.1 in github actions for localhost

localhost was resolved to IPv6 ::1

Signed-off-by: Hiroaki KAWAI <[email protected]>

---------

Signed-off-by: Hiroaki KAWAI <[email protected]>
  • Loading branch information
hkwi authored Oct 25, 2023
1 parent 41c5e7f commit dff0dfa
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 0 deletions.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,26 @@
"c8": "8.0.1",
"chai": "4.3.6",
"chai-as-promised": "7.1.1",
"crypto-browserify": "^3.12.0",
"esbuild": "0.16.17",
"eslint": "8.33.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-mocha": "10.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-todo-plz": "^1.3.0",
"http-proxy": "^1.18.1",
"husky": "^8.0.0",
"karma": "^6.4.2",
"karma-chrome-launcher": "^3.2.0",
"karma-esbuild": "^2.2.5",
"karma-mocha": "^2.0.1",
"lint-staged": "^14.0.1",
"mocha": "^10.2.0",
"prettier": "3.0.3",
"puppeteer": "^21.4.0",
"sinon": "16.1.0",
"stream-browserify": "^3.0.0",
"supertest": "6.3.3",
"typescript": "^5.1.6"
},
Expand Down
196 changes: 196 additions & 0 deletions tests/cors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { expect } from 'chai';
import * as http from 'http';
import { default as httpProxy } from 'http-proxy';
import { default as karma } from 'karma';
import type { AddressInfo } from 'net';
import { executablePath } from 'puppeteer';

import { config as defaultConfig } from '../src/config.js';
import { DwnServer } from '../src/dwn-server.js';
import { clear as clearDwn, dwn } from './test-dwn.js';

let noBrowser;
try {
process.env.CHROME_BIN = executablePath();
} catch (e) {
noBrowser = e;
}

class CorsProxySetup {
server = null;
proxy = null;
dwnServer = null;
karmaPort = 9876;
proxyPort = 9875;

public async start(): Promise<void> {
const dwnServer = new DwnServer({
dwn: dwn,
config: {
...defaultConfig,
port: 0, // UNSPEC to obtain test specific free port
},
});
const dwnPort = await new Promise((resolve) => {
dwnServer.start(() => {
const port = (dwnServer.httpServer.address() as AddressInfo).port;
resolve(port);
});
});
// setup proxy server
const proxy = httpProxy.createProxyServer({});
const server = http.createServer((req, res) => {
const [host] = req.headers.host.split(':', 2);
if (host == 'dwn.localhost') {
proxy.web(req, res, { target: `http://127.0.0.1:${dwnPort}` });
} else if (host == 'app.localhost') {
proxy.web(req, res, { target: `http://127.0.0.1:${this.karmaPort}` });
} else {
res.write('unexpected');
}
});
await new Promise((done) => {
server.listen(0, () => {
this.proxyPort = (server.address() as AddressInfo).port;
done(null);
});
});

this.dwnServer = dwnServer;
this.proxy = proxy;
this.server = server;
}
public async stop(): Promise<void> {
const server = this.server;
const dwnServer = this.dwnServer;
const proxy = this.proxy;

// shutdown proxy server
proxy.close();
await new Promise((resolve) => {
server.close(() => {
server.closeAllConnections();
resolve(null);
});
});
// shutdown dwn
await new Promise((resolve) => {
dwnServer.stop(resolve);
});
await clearDwn();
}
}

async function karmaRun(proxy, specfile): Promise<void> {
const runResults: any = {};
const browserErrors = [];
const specResults = [];
await new Promise((karmaRunDone) => {
function karmaResultCapture(config): void {
proxy.karmaPort = config.port; // karma port may change on startup
this.onRunComplete = (browsers, results): void => {
Object.assign(runResults, results);
};
this.onSpecComplete = (browser, result): void => {
specResults.push(result);
};
this.onBrowserError = (browser, error): void => {
browserErrors.push(error);
};
this.onBrowserLog = (browser, log): void => {
console.log(log);
};
}
karmaResultCapture.$inject = ['config'];

const conf = karma.config.parseConfig(
null,
{
logLevel: karma.constants.LOG_WARN,
singleRun: true,
autoWatch: false,
files: [specfile],
preprocessors: { [specfile]: ['esbuild'] },
plugins: [
'karma-mocha',
'karma-esbuild',
'karma-chrome-launcher',
{ 'reporter:capture': ['type', karmaResultCapture] },
],
frameworks: ['mocha'],
customLaunchers: {
ChromeHeadless_with_proxy: {
base: 'ChromeHeadless',
flags: [
`--proxy-server=http=127.0.0.1:${proxy.proxyPort}`,
'--proxy-bypass-list=<-loopback>',
],
},
},
browsers: ['ChromeHeadless_with_proxy'],
reporters: ['capture'],
upstreamProxy: {
hostname: 'app.localhost',
},
esbuild: {
target: 'chrome80',
define: {
global: 'window',
},
alias: {
crypto: 'crypto-browserify',
stream: 'stream-browserify',
},
},
},
{ throwErrors: true },
);

const kserver = new karma.Server(conf, () => {
// avoid process.exit call
// Use mocha --exit flag because
// esbuild service process still runs in background
karmaRunDone(null);
});
kserver.start();
});
for (const error of browserErrors) {
throw error;
}
for (const result of specResults) {
if (!result.success) {
throw new Error(result.log.join(''));
}
}
expect(runResults.error).to.be.false;
expect(runResults.failed).to.be.equal(0);
expect(runResults.success).to.be.above(0);
}

describe('CORS setup', function () {
// create proxy server to create cross-origin hostnames.
// mocha test app runs on app.localhost
// dwn-server runs on dwn.localhost
const proxy = new CorsProxySetup();
before(async () => {
await proxy.start();
});
after(async () => {
await proxy.stop();
});
this.timeout(5000);
it('should run blank browser karma test', async function () {
if (noBrowser) {
this.skip();
} else {
await karmaRun(proxy, 'dist/esm/tests/cors/ping.browser.js');
}
});
it('should run http-api browser karma test', async function () {
if (noBrowser) {
this.skip();
} else {
await karmaRun(proxy, 'dist/esm/tests/cors/http-api.browser.js');
}
});
});
74 changes: 74 additions & 0 deletions tests/cors/http-api.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
DidKeyResolver,
RecordsRead,
RecordsWrite,
Jws,
} from '@tbd54566975/dwn-sdk-js';

import { expect } from 'chai';

describe('http-api', () => {
it('sends dwn-response header', async function () {
// Some crypto functions used in key generation and signing,
// work only under secure context.
// Test code runs on secure context of http://dwn.localhost
// by cors setup.
const alice = await DidKeyResolver.generate();
const encoder = new TextEncoder();
const data = encoder.encode('Hello, World!');
const recordsWrite = (
await RecordsWrite.create({
data,
dataFormat: 'text/plalin',
published: true,
authorizationSigner: Jws.createSigner(alice),
})
).toJSON();
const recordsRead = (
await RecordsRead.create({
filter: {
recordId: recordsWrite.recordId,
},
authorizationSigner: Jws.createSigner(alice),
})
).toJSON();

// Records Write
const recordsWriteResponse = await fetch('http://dwn.localhost', {
method: 'POST',
headers: {
'dwn-request': JSON.stringify({
method: 'dwn.processMessage',
params: {
target: alice.did,
message: recordsWrite,
},
}),
},
body: data,
});
expect(recordsWriteResponse.status).to.equal(200);
const recordsWriteResponseJson = await recordsWriteResponse.json();
expect(recordsWriteResponseJson.result?.reply?.status?.code).to.equal(202);

// Records Read
const recordsReadResponse = await fetch('http://dwn.localhost', {
method: 'POST',
headers: {
'dwn-request': JSON.stringify({
method: 'dwn.processMessage',
params: {
target: alice.did,
message: recordsRead,
},
}),
},
});
expect(recordsReadResponse.status).to.equal(200);
const recordsReadResponseJson = JSON.parse(
recordsReadResponse.headers.get('dwn-response'),
);
expect(recordsReadResponseJson.result?.reply?.status?.code).to.equal(200);
expect(await recordsReadResponse.text()).to.equal('Hello, World!');
});
});
7 changes: 7 additions & 0 deletions tests/cors/ping.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expect } from 'chai';

describe('cors-test', () => {
it('always true', () => {
expect(true).to.true;
});
});

0 comments on commit dff0dfa

Please sign in to comment.