UNPKG

@podium/test-utils

Version:

Misc common test utils for Podium

396 lines (347 loc) 12.9 kB
import { HttpIncoming } from '@podium/utils'; import Podlet from '@podium/podlet'; import enableDestroy from 'server-destroy'; import EventEmitter from 'events'; import http from 'http'; import url from 'url'; /** * @typedef {object} PodletServerOptions * @property {string} [manifest="/manifest.json"] * @property {string} [fallback="/fallback.html"] * @property {string} [content="/index.html"] * @property {string} [version="1.0.0"] * @property {string} [pathname="/"] * @property {string} [name="component"] * @property {{ js?: string; css?: string; }} [assets] * @property {Record<string, string>} [proxy] */ export class PodletServer extends EventEmitter { /** * @constructor * @param {PodletServerOptions} options */ constructor({ manifest = '/manifest.json', fallback = '/fallback.html', content = '/index.html', version = '1.0.0', pathname = '/', name = 'component', assets = {}, proxy = {}, } = {}) { super(); // Private this._server = undefined; this._manifest = {}; this._podlet = new Podlet({ manifest, fallback, content, pathname, version, name, }); // TODO: Make it so that "assets" is not set through constructor if (assets.js) { this._podlet.js({ value: assets.js }); } if (assets.css) { this._podlet.css({ value: assets.css }); } // TODO: Make it so that "proxy" is not set through constructor Object.keys(proxy).forEach((key) => { this._podlet.proxy({ target: proxy[key], name: key }); }); // /podium-resource/component/localApi this._podlet.proxy({ target: '/public', name: 'localApi' }); this._routeManifest = this._podlet.manifest(); this._routeContent = this._podlet.content(); this._routeFallback = this._podlet.fallback(); this._routeError = '/error'; this._headersManifest = {}; this._headersContent = {}; this._statusCode = 200; this._headersFallback = {}; this._bodyManifest = JSON.stringify(this._podlet); this._bodyContent = `<p>content ${this._podlet.name}</p>`; this._bodyFallback = `<p>fallback ${this._podlet.name}</p>`; this._metrics = { manifest: 0, fallback: 0, content: 0, error: 0, }; // Public Object.defineProperty(this, 'metrics', { get: () => this._metrics, set: () => { throw new Error('Cannot set read-only property.'); }, }); Object.defineProperty(this, 'manifest', { get: () => JSON.parse(this._bodyManifest), set: () => { throw new Error('Cannot set read-only property.'); }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'version', { get: () => this._podlet.version, set: (value) => { this._manifest.version = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'content', { get: () => this._podlet.content(), set: () => { throw new Error('Cannot set read-only property.'); }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'fallback', { get: () => this._podlet.fallback(), set: () => { throw new Error('Cannot set read-only property.'); }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'assets', { get: () => // TODO: remove workaround ({ css: this._podlet.css([]), js: this._podlet.js([]), }), set: (value) => { // TODO: does probably not work as is (not in use since no tests break) if (!this._manifest.assets) { this._manifest.assets = {}; } Object.assign(this._manifest.assets, value); }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'headersManifest', { get: () => this._headersManifest, set: (value) => { this._headersManifest = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'headersContent', { get: () => this._headersContent, set: (value) => { this._headersContent = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'statusCode', { get: () => this._statusCode, set: (value) => { this._statusCode = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'headersFallback', { get: () => this._headersFallback, set: (value) => { this._headersFallback = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'manifestBody', { get: () => this._bodyManifest, set: (value) => { this._bodyManifest = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'contentBody', { get: () => this._bodyContent, set: (value) => { this._bodyContent = value; }, configurable: true, enumerable: true, }); Object.defineProperty(this, 'fallbackBody', { get: () => this._bodyFallback, set: (value) => { this._bodyFallback = value; }, configurable: true, enumerable: true, }); this._app = http.createServer(async (req, res) => { const incoming = new HttpIncoming(req, res); const inc = await this._podlet.process(incoming); // Error route if (inc.url.pathname.startsWith(this._routeError)) { this._metrics.error++; this.emit('req:error', this._metrics.error, req); res.statusCode = 500; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('podlet-version', this._podlet.version); res.end('Internal server error'); return; } // Content route if (inc.url.pathname.startsWith(this._podlet.content())) { this._metrics.content++; this.emit('req:content', this._metrics.content, req); res.statusCode = this._statusCode || 200; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('podlet-version', this._podlet.version); Object.keys(this._headersContent).forEach((key) => { res.setHeader(key, this._headersContent[key]); }); res.end(this._podlet.render(inc, this._bodyContent)); return; } // Fallback route if (inc.url.pathname.startsWith(this._podlet.fallback())) { this._metrics.fallback++; this.emit('req:fallback', this._metrics.fallback, req); res.statusCode = 200; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('podlet-version', this._podlet.version); Object.keys(this._headersFallback).forEach((key) => { res.setHeader(key, this._headersFallback[key]); }); res.end(this._podlet.render(inc, this._bodyFallback)); return; } // Manifest route if (inc.url.pathname.startsWith(this._podlet.manifest())) { this._metrics.manifest++; this.emit('req:manifest', this._metrics.manifest, req); res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.setHeader('podlet-version', this._podlet.version); Object.keys(this._headersManifest).forEach((key) => { res.setHeader(key, this._headersManifest[key]); }); res.end(this._bodyManifest); return; } // Dummy endpoints for proxying if ( inc.url.pathname.startsWith('/public') && req.method === 'GET' ) { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.setHeader('podlet-version', this._podlet.version); res.end( JSON.stringify({ status: 200, body: 'GET proxy target', }), ); return; } if ( inc.url.pathname.startsWith('/public') && req.method === 'POST' ) { const payload = []; req.on('data', (chunk) => { payload.push(chunk); }).on('end', () => { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.setHeader('podlet-version', this._podlet.version); res.end( JSON.stringify({ payload: Buffer.concat(payload).toString(), status: 200, body: 'POST proxy target', }), ); }); return; } if ( inc.url.pathname.startsWith('/public') && req.method === 'PUT' ) { const payload = []; req.on('data', (chunk) => { payload.push(chunk); }).on('end', () => { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.setHeader('podlet-version', this._podlet.version); res.end( JSON.stringify({ payload: Buffer.concat(payload).toString(), status: 200, body: 'PUT proxy target', }), ); }); return; } res.statusCode = 404; res.setHeader('Content-Type', 'text/plain'); res.end('Not found XXX'); }); } listen(host = 'http://0.0.0.0:0') { const addr = url.parse(host); return new Promise((resolve) => { this._server = this._app.listen( { host: addr.hostname, port: parseInt(addr.port, 10) }, () => { const address = `http://${this._server.address().address}:${ this._server.address().port }`; const manifest = `${address}${this._routeManifest}`; const content = `${address}${this._routeContent}`; const error = `${address}${this._routeError}`; const options = { uri: manifest, name: this._podlet.name, }; resolve({ manifest, content, error, address, options, }); }, ); enableDestroy(this._server); }); } close() { if (this._server) { return new Promise((resolve, reject) => { this._server.destroy((err) => { if (err) { reject(err); } else { resolve(); } }); }); } return Promise.resolve(); } static makeVersion(index = 1) { return `1.0.0-beta.${index.toString()}`; } }