UNPKG

fusion-plugin-rpc

Version:

Fetch data on the server and client with an RPC style interface.

821 lines (818 loc) 89 kB
"use strict"; var _events = _interopRequireDefault(require("events")); var _mockReq = _interopRequireDefault(require("mock-req")); var _formData = _interopRequireDefault(require("form-data")); var _fusionCore = _interopRequireWildcard(require("fusion-core")); var _fusionTestUtils = require("fusion-test-utils"); var _fusionPluginUniversalEvents = require("fusion-plugin-universal-events"); var _tokens = require("../tokens"); var _server = _interopRequireDefault(require("../server")); var _mock = _interopRequireDefault(require("../mock")); var _responseError = _interopRequireDefault(require("../response-error")); var _createMockEmitter = _interopRequireDefault(require("./create-mock-emitter")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** Copyright (c) 2018 Uber Technologies, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ const MOCK_JSON_PARAMS = { test: 'test-args' }; const mockService = (0, _fusionTestUtils.getService)(() => { const app = new _fusionCore.default('content', el => el); // @ts-ignore const mockEmitter = new _events.default(); mockEmitter.from = () => mockEmitter; const mockEmitterPlugin = (0, _fusionCore.createPlugin)({ provides: () => mockEmitter }); app.register(_fusionPluginUniversalEvents.UniversalEventsToken, mockEmitterPlugin); app.register(_tokens.RPCHandlersToken, (0, _fusionCore.createPlugin)({ provides: () => ({}) })); return app; }, _mock.default); /* Test fixtures */ function createTestFixture() { const mockHandlers = {}; // @ts-ignore const mockEmitter = new _events.default(); mockEmitter.from = () => mockEmitter; const mockEmitterPlugin = (0, _fusionCore.createPlugin)({ provides: () => mockEmitter }); const app = new _fusionCore.default('content', el => el); app.register(_fusionPluginUniversalEvents.UniversalEventsToken, mockEmitterPlugin); app.register(_tokens.RPCHandlersToken, mockHandlers); app.register(_tokens.RPCToken, _server.default); return app; } function mockRequest() { const req = new _mockReq.default({ method: 'POST', url: '/api/test', headers: { Accept: 'text/plain' } }); req.write(MOCK_JSON_PARAMS); req.end(); return req; } test('FusionApp - service resolved', () => { const app = createTestFixture(); let wasResolved = false; (0, _fusionTestUtils.getSimulator)(app, (0, _fusionCore.createPlugin)({ deps: { rpcFactory: _tokens.RPCToken }, provides: ({ rpcFactory }) => { expect(rpcFactory).toBeTruthy(); wasResolved = true; } })); expect(wasResolved).toBeTruthy(); }); test('service - requires ctx', () => { const app = createTestFixture(); let wasResolved = false; (0, _fusionTestUtils.getSimulator)(app, (0, _fusionCore.createPlugin)({ deps: { rpcFactory: _tokens.RPCToken }, provides: ({ rpcFactory }) => { // @ts-ignore expect(() => rpcFactory()).toThrow(); wasResolved = true; } })); expect(wasResolved).toBeTruthy(); }); test('service - request api', done => { const mockCtx = { headers: {}, memoized: new Map() }; const mockHandlers = { test(args, ctx) { expect(args).toBe('test-args'); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _fusionCore.createPlugin)({ provides: () => (0, _createMockEmitter.default)({ emit: (type, payload) => { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); }, from() { return this; } }) }); const appCreator = () => { const app = new _fusionCore.default('content', el => el); app.register(_fusionPluginUniversalEvents.UniversalEventsToken, mockEmitter); app.register(_tokens.RPCToken, _server.default); app.register(_tokens.RPCHandlersToken, mockHandlers); return app; }; const sim = (0, _fusionTestUtils.getSimulator)(appCreator()); const rpcFactory = sim.getService(_tokens.RPCToken); const routeTags = sim.getService(_fusionCore.RouteTagsToken); // @ts-ignore const rpc = rpcFactory.from(mockCtx); expect(typeof rpc.request).toBe('function'); try { const p = rpc.request('test', 'test-args'); expect(p instanceof Promise).toBeTruthy(); p.then(res => { expect(res).toBe(1); expect(routeTags.from(mockCtx).name).toBe('unknown_route'); // 'does not overwrite the name tag on SSR' done(); }); } catch (e) { // $FlowFixMe done.fail(e); } }); test('service - request api with failing request', async () => { const mockCtx = { headers: {}, memoized: new Map() }; const e = new Error('fail'); const mockHandlers = { test() { return Promise.reject(e); } }; const mockEmitter = (0, _fusionCore.createPlugin)({ provides: () => (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.status).toBe('failure'); expect(typeof payload.timing).toBe('number'); expect(payload.error).toBe(e); }, from() { return this; } }) }); const appCreator = () => { const app = new _fusionCore.default('content', el => el); app.register(_fusionPluginUniversalEvents.UniversalEventsToken, mockEmitter); app.register(_tokens.RPCHandlersToken, mockHandlers); return app; }; const rpcFactory = (0, _fusionTestUtils.getService)(appCreator, _server.default); // @ts-ignore const rpc = rpcFactory.from(mockCtx); expect(typeof rpc.request).toBe('function'); const p = rpc.request('test', 'test-args'); expect(p instanceof Promise).toBeTruthy(); await expect(p).rejects.toThrow(e); }); test('service - request api with invalid endpoint', async () => { const mockCtx = { headers: {}, memoized: new Map() }; const mockHandlers = {}; const mockEmitter = (0, _fusionCore.createPlugin)({ provides: () => (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:error'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('server'); expect(payload.error.message).toBe('Missing RPC handler for test'); }, from() { return this; } }) }); const appCreator = () => { const app = new _fusionCore.default('content', el => el); app.register(_fusionPluginUniversalEvents.UniversalEventsToken, mockEmitter); app.register(_tokens.RPCHandlersToken, mockHandlers); return app; }; const rpcFactory = (0, _fusionTestUtils.getService)(appCreator, _server.default); // @ts-ignore const rpc = rpcFactory.from(mockCtx); expect(typeof rpc.request).toBe('function'); const p = rpc.request('test', 'test-args'); expect(p instanceof Promise).toBeTruthy(); await expect(p).rejects.toThrowError('Missing RPC handler for test'); }); test('FusionJS - middleware resolves', async () => { const app = createTestFixture(); let wasResolved = false; const testPlugin = (0, _fusionCore.createPlugin)({ deps: { rpcFactory: _tokens.RPCToken }, middleware: ({ rpcFactory }) => { expect(rpcFactory).toBeTruthy(); wasResolved = true; return async () => {}; } }); app.register(testPlugin); (0, _fusionTestUtils.getSimulator)(app); expect(wasResolved).toBeTruthy(); }); test('middleware - invalid endpoint', done => { const mockCtx = { headers: {}, prefix: '', path: '/api/valueOf', method: 'POST', body: {}, request: { body: {} }, memoized: new Map() }; const mockHandlers = { something: () => {}, other: () => {} }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:error'); expect(payload.method).toBe('valueOf'); expect(payload.origin).toBe('browser'); expect(payload.error.message).toBe('Missing RPC handler for valueOf'); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, () => Promise.resolve()).then(() => { // @ts-ignore expect(mockCtx.body.data.message).toBe('Missing RPC handler for valueOf'); // @ts-ignore expect(mockCtx.body.data.code).toBe('ERR_MISSING_HANDLER'); // @ts-ignore expect(mockCtx.body.status).toBe('failure'); expect(mockCtx.status).toBe(404); done(); }); } catch (e) { done.fail(e); } }); test('middleware - valid endpoint', done => { const mockCtx = { headers: {}, prefix: '', path: '/api/test', method: 'POST', body: {}, request: { body: 'test-args' } }; let executedHandler = false; const mockHandlers = { test(args, ctx) { executedHandler = true; expect(args).toBe('test-args'); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); } }); const tags = { name: 'unknown_route' }; const middleware = _server.default.middleware && _server.default.middleware({ RouteTags: { from: () => tags }, emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, async () => { expect(executedHandler).toBe(false); Promise.resolve(); }).then(() => { expect(tags.name).toBe('test'); expect(executedHandler).toBe(true); // @ts-ignore expect(mockCtx.body.data).toBe(1); // @ts-ignore expect(mockCtx.body.status).toBe('success'); done(); }); } catch (e) { done.fail(e); } }); test('middleware - valid endpoint (custom api path)', done => { const mockCtx = { headers: {}, prefix: '', path: '/test/api/long/test', method: 'POST', body: {}, request: { body: 'test-args' } }; let executedHandler = false; const mockHandlers = { test(args, ctx) { executedHandler = true; expect(args).toBe('test-args'); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers, rpcConfig: { apiPath: 'test/api/long' } }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, async () => { expect(executedHandler).toBe(false); Promise.resolve(); }).then(() => { expect(executedHandler).toBe(true); // @ts-ignore expect(mockCtx.body.data).toBe(1); // @ts-ignore expect(mockCtx.body.status).toBe('success'); done(); }); } catch (e) { done.fail(e); } }); test('middleware - valid endpoint (custom api path including slashes)', done => { const mockCtx = { headers: {}, prefix: '', path: '/test/api/long/test', method: 'POST', body: {}, request: { body: 'test-args' } }; let executedHandler = false; const mockHandlers = { test(args, ctx) { executedHandler = true; expect(args).toBe('test-args'); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers, rpcConfig: { apiPath: '/test///api/long////' } }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, async () => { expect(executedHandler).toBe(false); Promise.resolve(); }).then(() => { expect(executedHandler).toBe(true); // @ts-ignore expect(mockCtx.body.data).toBe(1); // @ts-ignore expect(mockCtx.body.status).toBe('success'); done(); }); } catch (e) { done.fail(e); } }); test('middleware - valid endpoint with route prefix', done => { const mockCtx = { headers: {}, prefix: '/lol', path: '/api/test', method: 'POST', body: {}, request: { body: 'test-args' } }; const mockHandlers = { test(args, ctx) { expect(args).toBe('test-args'); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, () => Promise.resolve()).then(() => { // @ts-ignore expect(mockCtx.body.data).toBe(1); // @ts-ignore expect(mockCtx.body.status).toBe('success'); done(); }); } catch (e) { done.fail(e); } }); test('middleware - valid endpoint failure with ResponseError', done => { const mockCtx = { headers: {}, prefix: '', path: '/api/test', method: 'POST', body: {}, request: { body: 'test-args' }, memoized: new Map() }; const e = new _responseError.default('Test Failure'); // $FlowFixMe e.code = 'ERR_CODE_TEST'; // $FlowFixMe e.meta = { hello: 'world' }; const mockHandlers = { test() { return Promise.reject(e); } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('failure'); expect(typeof payload.timing).toBe('number'); expect(payload.error).toBe(e); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, () => Promise.resolve()).then(() => { // @ts-ignore expect(mockCtx.body.data.message).toBe(e.message); // @ts-ignore expect(mockCtx.body.data.code).toBe(e.code); // @ts-ignore expect(mockCtx.body.data.meta).toBe(e.meta); // @ts-ignore expect(mockCtx.body.status).toBe('failure'); // @ts-ignore expect(Object.keys(mockCtx.body).length).toBe(2); // @ts-ignore expect(Object.keys(mockCtx.body.data).length).toBe(3); done(); }); } catch (e) { done.fail(e); } }); test('middleware - valid endpoint failure with standard error', done => { const mockCtx = { headers: {}, prefix: '', path: '/api/test', method: 'POST', body: {}, request: { body: 'test-args' }, memoized: new Map() }; const e = new Error('Test Failure'); // @ts-ignore e.code = 'ERR_CODE_TEST'; // @ts-ignore e.meta = { hello: 'world' }; const mockHandlers = { test() { return Promise.reject(e); } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('failure'); expect(typeof payload.timing).toBe('number'); expect(payload.error).toBe(e); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, () => Promise.resolve()).then(() => { expect( // @ts-ignore mockCtx.body.data.message).toBe('UnknownError - Use ResponseError from fusion-plugin-rpc (or fusion-plugin-rpc-redux-react if you are using React) package for more detailed error messages'); // @ts-ignore expect(mockCtx.body.data.code).toBe(undefined); // @ts-ignore expect(mockCtx.body.data.meta).toBe(undefined); // @ts-ignore expect(mockCtx.body.status).toBe('failure'); // @ts-ignore expect(Object.keys(mockCtx.body).length).toBe(2); // @ts-ignore expect(Object.keys(mockCtx.body.data).length).toBe(3); done(); }); } catch (e) { done.fail(e); } }); test('throws when not passed ctx', done => { const app = createTestFixture(); expect.assertions(1); (0, _fusionTestUtils.getSimulator)(app, (0, _fusionCore.createPlugin)({ deps: { rpcFactory: _tokens.RPCToken }, middleware: ({ rpcFactory }) => async () => { // @ts-ignore expect(() => rpcFactory.from()).toThrow(); done(); } })).request('/'); }); test('middleware - bodyparser options with very small jsonLimit', done => { const mockCtx = { req: mockRequest(), headers: {}, prefix: '/lol', path: '/api/test', method: 'POST', request: { is: mineTypes => mineTypes.some(mineType => mineType.includes('json')) } }; let executedHandler = false; const mockHandlers = { test(args, ctx) { executedHandler = true; expect(args).toEqual(MOCK_JSON_PARAMS); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('failure'); expect(typeof payload.timing).toBe('number'); } }); const mockBodyParserOptions = { jsonLimit: '1b' }; const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers, bodyParserOptions: mockBodyParserOptions }, mockService); if (!middleware) { done.fail(); done(); return; } try { middleware(mockCtx, async () => { expect(executedHandler).toBe(false); Promise.resolve(); }).then(() => { expect(executedHandler).toBe(false); // @ts-ignore expect(mockCtx.body.status).toBe('failure'); // @ts-ignore expect(mockCtx.body.data.code).toBe('entity.too.large'); done(); }); } catch (e) { done.fail(e); } }); test('middleware - bodyparser options with default jsonLimit', done => { const mockCtx = { req: mockRequest(), headers: {}, prefix: '/lol', path: '/api/test', method: 'POST', request: { is: mineTypes => mineTypes.some(mineType => mineType.includes('json')) } }; const mockHandlers = { test(args, ctx) { expect(args).toEqual(MOCK_JSON_PARAMS); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { // $FlowFixMe done.fail(); done(); return; } try { middleware(mockCtx, () => Promise.resolve()).then(() => done()); } catch (e) { // $FlowFixMe done.fail(e); } }); test('middleware - parse formData', done => { const form = new _formData.default(); form.append('name', 'test'); const req = new _mockReq.default({ method: 'POST', url: '/api/test', headers: { ...form.getHeaders(), 'content-length': 3 } }); form.pipe(req); req.end(); const mockCtx = { req, headers: { 'content-type': 'multipart/form-data' }, prefix: '/lol', path: '/api/test', method: 'POST', request: { is: mineTypes => mineTypes.some(mineType => mineType.includes('json')) } }; const mockHandlers = { test(args, ctx) { expect(args).toEqual({ name: 'test' }); expect(ctx).toBe(mockCtx); return 1; } }; const mockEmitter = (0, _createMockEmitter.default)({ emit(type, payload) { expect(type).toBe('rpc:method'); expect(payload.method).toBe('test'); expect(payload.origin).toBe('browser'); expect(payload.status).toBe('success'); expect(typeof payload.timing).toBe('number'); } }); const middleware = _server.default.middleware && _server.default.middleware({ emitter: mockEmitter, handlers: mockHandlers }, mockService); if (!middleware) { // $FlowFixMe done.fail(); done(); return; } try { middleware(mockCtx, () => Promise.resolve()).then(() => done()); } catch (e) { // $FlowFixMe done.fail(e); } }); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJNT0NLX0pTT05fUEFSQU1TIiwidGVzdCIsIm1vY2tTZXJ2aWNlIiwiZ2V0U2VydmljZSIsImFwcCIsIkFwcCIsImVsIiwibW9ja0VtaXR0ZXIiLCJNb2NrRW1pdHRlciIsImZyb20iLCJtb2NrRW1pdHRlclBsdWdpbiIsImNyZWF0ZVBsdWdpbiIsInByb3ZpZGVzIiwicmVnaXN0ZXIiLCJVbml2ZXJzYWxFdmVudHNUb2tlbiIsIlJQQ0hhbmRsZXJzVG9rZW4iLCJNb2NrUlBDUGx1Z2luIiwiY3JlYXRlVGVzdEZpeHR1cmUiLCJtb2NrSGFuZGxlcnMiLCJSUENUb2tlbiIsIlJQQ1BsdWdpbiIsIm1vY2tSZXF1ZXN0IiwicmVxIiwiTW9ja1JlcSIsIm1ldGhvZCIsInVybCIsImhlYWRlcnMiLCJBY2NlcHQiLCJ3cml0ZSIsImVuZCIsIndhc1Jlc29sdmVkIiwiZ2V0U2ltdWxhdG9yIiwiZGVwcyIsInJwY0ZhY3RvcnkiLCJleHBlY3QiLCJ0b0JlVHJ1dGh5IiwidG9UaHJvdyIsImRvbmUiLCJtb2NrQ3R4IiwibWVtb2l6ZWQiLCJNYXAiLCJhcmdzIiwiY3R4IiwidG9CZSIsImNyZWF0ZU1vY2tFbWl0dGVyIiwiZW1pdCIsInR5cGUiLCJwYXlsb2FkIiwic3RhdHVzIiwidGltaW5nIiwiYXBwQ3JlYXRvciIsInNpbSIsInJvdXRlVGFncyIsIlJvdXRlVGFnc1Rva2VuIiwicnBjIiwicmVxdWVzdCIsInAiLCJQcm9taXNlIiwidGhlbiIsInJlcyIsIm5hbWUiLCJlIiwiZmFpbCIsIkVycm9yIiwicmVqZWN0IiwiZXJyb3IiLCJyZWplY3RzIiwib3JpZ2luIiwibWVzc2FnZSIsInRvVGhyb3dFcnJvciIsInRlc3RQbHVnaW4iLCJtaWRkbGV3YXJlIiwicHJlZml4IiwicGF0aCIsImJvZHkiLCJzb21ldGhpbmciLCJvdGhlciIsImVtaXR0ZXIiLCJoYW5kbGVycyIsInJlc29sdmUiLCJkYXRhIiwiY29kZSIsImV4ZWN1dGVkSGFuZGxlciIsInRhZ3MiLCJSb3V0ZVRhZ3MiLCJycGNDb25maWciLCJhcGlQYXRoIiwiUmVzcG9uc2VFcnJvciIsIm1ldGEiLCJoZWxsbyIsIk9iamVjdCIsImtleXMiLCJsZW5ndGgiLCJ1bmRlZmluZWQiLCJhc3NlcnRpb25zIiwiaXMiLCJtaW5lVHlwZXMiLCJzb21lIiwibWluZVR5cGUiLCJpbmNsdWRlcyIsInRvRXF1YWwiLCJtb2NrQm9keVBhcnNlck9wdGlvbnMiLCJqc29uTGltaXQiLCJib2R5UGFyc2VyT3B0aW9ucyIsImZvcm0iLCJGb3JtRGF0YSIsImFwcGVuZCIsImdldEhlYWRlcnMiLCJwaXBlIl0sInNvdXJjZXMiOlsic3JjL19fdGVzdHNfXy9pbmRleC5ub2RlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKiBDb3B5cmlnaHQgKGMpIDIwMTggVWJlciBUZWNobm9sb2dpZXMsIEluYy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKi9cblxuaW1wb3J0IE1vY2tFbWl0dGVyIGZyb20gJ2V2ZW50cyc7XG5pbXBvcnQgTW9ja1JlcSBmcm9tICdtb2NrLXJlcSc7XG5pbXBvcnQgRm9ybURhdGEgZnJvbSAnZm9ybS1kYXRhJztcblxuaW1wb3J0IEFwcCwge2NyZWF0ZVBsdWdpbiwgUm91dGVUYWdzVG9rZW59IGZyb20gJ2Z1c2lvbi1jb3JlJztcbmltcG9ydCB0eXBlIHtDb250ZXh0fSBmcm9tICdmdXNpb24tY29yZSc7XG5pbXBvcnQge2dldFNpbXVsYXRvciwgZ2V0U2VydmljZX0gZnJvbSAnZnVzaW9uLXRlc3QtdXRpbHMnO1xuaW1wb3J0IHtVbml2ZXJzYWxFdmVudHNUb2tlbn0gZnJvbSAnZnVzaW9uLXBsdWdpbi11bml2ZXJzYWwtZXZlbnRzJztcbmltcG9ydCB7UlBDSGFuZGxlcnNUb2tlbiwgUlBDVG9rZW59IGZyb20gJy4uL3Rva2Vucyc7XG5pbXBvcnQgUlBDUGx1Z2luIGZyb20gJy4uL3NlcnZlcic7XG5pbXBvcnQgdHlwZSB7SUVtaXR0ZXIsIFJQQ1NlcnZpY2VUeXBlfSBmcm9tICcuLi90eXBlcyc7XG5pbXBvcnQgTW9ja1JQQ1BsdWdpbiBmcm9tICcuLi9tb2NrJztcbmltcG9ydCBSZXNwb25zZUVycm9yIGZyb20gJy4uL3Jlc3BvbnNlLWVycm9yJztcbmltcG9ydCBjcmVhdGVNb2NrRW1pdHRlciBmcm9tICcuL2NyZWF0ZS1tb2NrLWVtaXR0ZXInO1xuXG5jb25zdCBNT0NLX0pTT05fUEFSQU1TID0ge3Rlc3Q6ICd0ZXN0LWFyZ3MnfTtcblxuY29uc3QgbW9ja1NlcnZpY2U6IFJQQ1NlcnZpY2VUeXBlID0gZ2V0U2VydmljZSgoKSA9PiB7XG4gIGNvbnN0IGFwcCA9IG5ldyBBcHAoJ2NvbnRlbnQnLCAoZWwpID0+IGVsKTtcbiAgLy8gQHRzLWlnbm9yZVxuICBjb25zdCBtb2NrRW1pdHRlcjogSUVtaXR0ZXIgPSBuZXcgTW9ja0VtaXR0ZXIoKSBhcyBhbnk7XG4gIG1vY2tFbWl0dGVyLmZyb20gPSAoKSA9PiBtb2NrRW1pdHRlcjtcbiAgY29uc3QgbW9ja0VtaXR0ZXJQbHVnaW4gPSBjcmVhdGVQbHVnaW4oe1xuICAgIHByb3ZpZGVzOiAoKSA9PiBtb2NrRW1pdHRlcixcbiAgfSk7XG4gIGFwcC5yZWdpc3RlcihVbml2ZXJzYWxFdmVudHNUb2tlbiwgbW9ja0VtaXR0ZXJQbHVnaW4pO1xuICBhcHAucmVnaXN0ZXIoUlBDSGFuZGxlcnNUb2tlbiwgY3JlYXRlUGx1Z2luKHtwcm92aWRlczogKCkgPT4gKHt9KX0pKTtcbiAgcmV0dXJuIGFwcDtcbn0sIE1vY2tSUENQbHVnaW4pO1xuXG4vKiBUZXN0IGZpeHR1cmVzICovXG5mdW5jdGlvbiBjcmVhdGVUZXN0Rml4dHVyZSgpIHtcbiAgY29uc3QgbW9ja0hhbmRsZXJzID0ge307XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgbW9ja0VtaXR0ZXI6IElFbWl0dGVyID0gbmV3IE1vY2tFbWl0dGVyKCkgYXMgYW55O1xuICBtb2NrRW1pdHRlci5mcm9tID0gKCkgPT4gbW9ja0VtaXR0ZXI7XG4gIGNvbnN0IG1vY2tFbWl0dGVyUGx1Z2luID0gY3JlYXRlUGx1Z2luKHtcbiAgICBwcm92aWRlczogKCkgPT4gbW9ja0VtaXR0ZXIsXG4gIH0pO1xuXG4gIGNvbnN0IGFwcCA9IG5ldyBBcHAoJ2NvbnRlbnQnLCAoZWwpID0+IGVsKTtcbiAgYXBwLnJlZ2lzdGVyKFVuaXZlcnNhbEV2ZW50c1Rva2VuLCBtb2NrRW1pdHRlclBsdWdpbik7XG4gIGFwcC5yZWdpc3RlcihSUENIYW5kbGVyc1Rva2VuLCBtb2NrSGFuZGxlcnMpO1xuICBhcHAucmVnaXN0ZXIoUlBDVG9rZW4sIFJQQ1BsdWdpbik7XG4gIHJldHVybiBhcHA7XG59XG5cbmZ1bmN0aW9uIG1vY2tSZXF1ZXN0KCkge1xuICBjb25zdCByZXEgPSBuZXcgTW9ja1JlcSh7XG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgdXJsOiAnL2FwaS90ZXN0JyxcbiAgICBoZWFkZXJzOiB7XG4gICAgICBBY2NlcHQ6ICd0ZXh0L3BsYWluJyxcbiAgICB9LFxuICB9KTtcbiAgcmVxLndyaXRlKE1PQ0tfSlNPTl9QQVJBTVMpO1xuICByZXEuZW5kKCk7XG5cbiAgcmV0dXJuIHJlcTtcbn1cblxudGVzdCgnRnVzaW9uQXBwIC0gc2VydmljZSByZXNvbHZlZCcsICgpID0+IHtcbiAgY29uc3QgYXBwID0gY3JlYXRlVGVzdEZpeHR1cmUoKTtcblxuICBsZXQgd2FzUmVzb2x2ZWQgPSBmYWxzZTtcbiAgZ2V0U2ltdWxhdG9yKFxuICAgIGFwcCxcbiAgICBjcmVhdGVQbHVnaW4oe1xuICAgICAgZGVwczoge3JwY0ZhY3Rvcnk6IFJQQ1Rva2VufSxcbiAgICAgIHByb3ZpZGVzOiAoe3JwY0ZhY3Rvcnl9KSA9PiB7XG4gICAgICAgIGV4cGVjdChycGNGYWN0b3J5KS50b0JlVHJ1dGh5KCk7XG4gICAgICAgIHdhc1Jlc29sdmVkID0gdHJ1ZTtcbiAgICAgIH0sXG4gICAgfSlcbiAgKTtcbiAgZXhwZWN0KHdhc1Jlc29sdmVkKS50b0JlVHJ1dGh5KCk7XG59KTtcblxudGVzdCgnc2VydmljZSAtIHJlcXVpcmVzIGN0eCcsICgpID0+IHtcbiAgY29uc3QgYXBwID0gY3JlYXRlVGVzdEZpeHR1cmUoKTtcblxuICBsZXQgd2FzUmVzb2x2ZWQgPSBmYWxzZTtcbiAgZ2V0U2ltdWxhdG9yKFxuICAgIGFwcCxcbiAgICBjcmVhdGVQbHVnaW4oe1xuICAgICAgZGVwczoge3JwY0ZhY3Rvcnk6IFJQQ1Rva2VufSxcbiAgICAgIHByb3ZpZGVzOiAoe3JwY0ZhY3Rvcnl9KSA9PiB7XG4gICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgZXhwZWN0KCgpID0+IHJwY0ZhY3RvcnkoKSkudG9UaHJvdygpO1xuICAgICAgICB3YXNSZXNvbHZlZCA9IHRydWU7XG4gICAgICB9LFxuICAgIH0pXG4gICk7XG4gIGV4cGVjdCh3YXNSZXNvbHZlZCkudG9CZVRydXRoeSgpO1xufSk7XG5cbnRlc3QoJ3NlcnZpY2UgLSByZXF1ZXN0IGFwaScsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgbWVtb2l6ZWQ6IG5ldyBNYXAoKSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KGFyZ3MsIGN0eCkge1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvQmUoJ3Rlc3QtYXJncycpO1xuICAgICAgZXhwZWN0KGN0eCkudG9CZShtb2NrQ3R4KTtcbiAgICAgIHJldHVybiAxO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlUGx1Z2luKHtcbiAgICBwcm92aWRlczogKCkgPT5cbiAgICAgIGNyZWF0ZU1vY2tFbWl0dGVyKHtcbiAgICAgICAgZW1pdDogKHR5cGU6IHVua25vd24sIHBheWxvYWQ6IGFueSkgPT4ge1xuICAgICAgICAgIGV4cGVjdCh0eXBlKS50b0JlKCdycGM6bWV0aG9kJyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQuc3RhdHVzKS50b0JlKCdzdWNjZXNzJyk7XG4gICAgICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgICAgIH0sXG4gICAgICAgIGZyb20oKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgfSk7XG5cbiAgY29uc3QgYXBwQ3JlYXRvciA9ICgpID0+IHtcbiAgICBjb25zdCBhcHAgPSBuZXcgQXBwKCdjb250ZW50JywgKGVsKSA9PiBlbCk7XG4gICAgYXBwLnJlZ2lzdGVyKFVuaXZlcnNhbEV2ZW50c1Rva2VuLCBtb2NrRW1pdHRlcik7XG4gICAgYXBwLnJlZ2lzdGVyKFJQQ1Rva2VuLCBSUENQbHVnaW4pO1xuICAgIGFwcC5yZWdpc3RlcihSUENIYW5kbGVyc1Rva2VuLCBtb2NrSGFuZGxlcnMpO1xuICAgIHJldHVybiBhcHA7XG4gIH07XG5cbiAgY29uc3Qgc2ltID0gZ2V0U2ltdWxhdG9yKGFwcENyZWF0b3IoKSk7XG5cbiAgY29uc3QgcnBjRmFjdG9yeSA9IHNpbS5nZXRTZXJ2aWNlKFJQQ1Rva2VuKTtcbiAgY29uc3Qgcm91dGVUYWdzID0gc2ltLmdldFNlcnZpY2UoUm91dGVUYWdzVG9rZW4pO1xuICAvLyBAdHMtaWdub3JlXG4gIGNvbnN0IHJwYyA9IHJwY0ZhY3RvcnkuZnJvbShtb2NrQ3R4KTtcblxuICBleHBlY3QodHlwZW9mIHJwYy5yZXF1ZXN0KS50b0JlKCdmdW5jdGlvbicpO1xuICB0cnkge1xuICAgIGNvbnN0IHAgPSBycGMucmVxdWVzdCgndGVzdCcsICd0ZXN0LWFyZ3MnKTtcbiAgICBleHBlY3QocCBpbnN0YW5jZW9mIFByb21pc2UpLnRvQmVUcnV0aHkoKTtcbiAgICBwLnRoZW4oKHJlcykgPT4ge1xuICAgICAgZXhwZWN0KHJlcykudG9CZSgxKTtcbiAgICAgIGV4cGVjdChyb3V0ZVRhZ3MuZnJvbShtb2NrQ3R4KS5uYW1lKS50b0JlKCd1bmtub3duX3JvdXRlJyk7XG4gICAgICAvLyAgJ2RvZXMgbm90IG92ZXJ3cml0ZSB0aGUgbmFtZSB0YWcgb24gU1NSJ1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgLy8gJEZsb3dGaXhNZVxuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ3NlcnZpY2UgLSByZXF1ZXN0IGFwaSB3aXRoIGZhaWxpbmcgcmVxdWVzdCcsIGFzeW5jICgpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBtZW1vaXplZDogbmV3IE1hcCgpLFxuICB9IGFzIGFueTtcbiAgY29uc3QgZSA9IG5ldyBFcnJvcignZmFpbCcpO1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgdGVzdCgpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdChlKTtcbiAgICB9LFxuICB9O1xuICBjb25zdCBtb2NrRW1pdHRlciA9IGNyZWF0ZVBsdWdpbih7XG4gICAgcHJvdmlkZXM6ICgpID0+XG4gICAgICBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgICAgIGV4cGVjdCh0eXBlKS50b0JlKCdycGM6bWV0aG9kJyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQuc3RhdHVzKS50b0JlKCdmYWlsdXJlJyk7XG4gICAgICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQuZXJyb3IpLnRvQmUoZSk7XG4gICAgICAgIH0sXG4gICAgICAgIGZyb20oKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgfSk7XG5cbiAgY29uc3QgYXBwQ3JlYXRvciA9ICgpID0+IHtcbiAgICBjb25zdCBhcHAgPSBuZXcgQXBwKCdjb250ZW50JywgKGVsKSA9PiBlbCk7XG4gICAgYXBwLnJlZ2lzdGVyKFVuaXZlcnNhbEV2ZW50c1Rva2VuLCBtb2NrRW1pdHRlcik7XG4gICAgYXBwLnJlZ2lzdGVyKFJQQ0hhbmRsZXJzVG9rZW4sIG1vY2tIYW5kbGVycyk7XG4gICAgcmV0dXJuIGFwcDtcbiAgfTtcblxuICBjb25zdCBycGNGYWN0b3J5ID0gZ2V0U2VydmljZShhcHBDcmVhdG9yLCBSUENQbHVnaW4pO1xuICAvLyBAdHMtaWdub3JlXG4gIGNvbnN0IHJwYyA9IHJwY0ZhY3RvcnkuZnJvbShtb2NrQ3R4KTtcblxuICBleHBlY3QodHlwZW9mIHJwYy5yZXF1ZXN0KS50b0JlKCdmdW5jdGlvbicpO1xuICBjb25zdCBwID0gcnBjLnJlcXVlc3QoJ3Rlc3QnLCAndGVzdC1hcmdzJyk7XG4gIGV4cGVjdChwIGluc3RhbmNlb2YgUHJvbWlzZSkudG9CZVRydXRoeSgpO1xuICBhd2FpdCBleHBlY3QocCkucmVqZWN0cy50b1Rocm93KGUpO1xufSk7XG5cbnRlc3QoJ3NlcnZpY2UgLSByZXF1ZXN0IGFwaSB3aXRoIGludmFsaWQgZW5kcG9pbnQnLCBhc3luYyAoKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgbWVtb2l6ZWQ6IG5ldyBNYXAoKSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHt9O1xuICBjb25zdCBtb2NrRW1pdHRlciA9IGNyZWF0ZVBsdWdpbih7XG4gICAgcHJvdmlkZXM6ICgpID0+XG4gICAgICBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgICAgIGV4cGVjdCh0eXBlKS50b0JlKCdycGM6ZXJyb3InKTtcbiAgICAgICAgICBleHBlY3QocGF5bG9hZC5tZXRob2QpLnRvQmUoJ3Rlc3QnKTtcbiAgICAgICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ3NlcnZlcicpO1xuICAgICAgICAgIGV4cGVjdChwYXlsb2FkLmVycm9yLm1lc3NhZ2UpLnRvQmUoJ01pc3NpbmcgUlBDIGhhbmRsZXIgZm9yIHRlc3QnKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnJvbSgpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcbiAgICAgIH0pLFxuICB9KTtcblxuICBjb25zdCBhcHBDcmVhdG9yID0gKCkgPT4ge1xuICAgIGNvbnN0IGFwcCA9IG5ldyBBcHAoJ2NvbnRlbnQnLCAoZWwpID0+IGVsKTtcbiAgICBhcHAucmVnaXN0ZXIoVW5pdmVyc2FsRXZlbnRzVG9rZW4sIG1vY2tFbWl0dGVyKTtcbiAgICBhcHAucmVnaXN0ZXIoUlBDSGFuZGxlcnNUb2tlbiwgbW9ja0hhbmRsZXJzKTtcbiAgICByZXR1cm4gYXBwO1xuICB9O1xuXG4gIGNvbnN0IHJwY0ZhY3RvcnkgPSBnZXRTZXJ2aWNlKGFwcENyZWF0b3IsIFJQQ1BsdWdpbik7XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgcnBjID0gcnBjRmFjdG9yeS5mcm9tKG1vY2tDdHgpO1xuXG4gIGV4cGVjdCh0eXBlb2YgcnBjLnJlcXVlc3QpLnRvQmUoJ2Z1bmN0aW9uJyk7XG4gIGNvbnN0IHAgPSBycGMucmVxdWVzdCgndGVzdCcsICd0ZXN0LWFyZ3MnKTtcbiAgZXhwZWN0KHAgaW5zdGFuY2VvZiBQcm9taXNlKS50b0JlVHJ1dGh5KCk7XG4gIGF3YWl0IGV4cGVjdChwKS5yZWplY3RzLnRvVGhyb3dFcnJvcignTWlzc2luZyBSUEMgaGFuZGxlciBmb3IgdGVzdCcpO1xufSk7XG5cbnRlc3QoJ0Z1c2lvbkpTIC0gbWlkZGxld2FyZSByZXNvbHZlcycsIGFzeW5jICgpID0+IHtcbiAgY29uc3QgYXBwID0gY3JlYXRlVGVzdEZpeHR1cmUoKTtcblxuICBsZXQgd2FzUmVzb2x2ZWQgPSBmYWxzZTtcblxuICBjb25zdCB0ZXN0UGx1Z2luID0gY3JlYXRlUGx1Z2luKHtcbiAgICBkZXBzOiB7cnBjRmFjdG9yeTogUlBDVG9rZW59LFxuICAgIG1pZGRsZXdhcmU6ICh7cnBjRmFjdG9yeX0pID0+IHtcbiAgICAgIGV4cGVjdChycGNGYWN0b3J5KS50b0JlVHJ1dGh5KCk7XG4gICAgICB3YXNSZXNvbHZlZCA9IHRydWU7XG5cbiAgICAgIHJldHVybiBhc3luYyAoKSA9PiB7fTtcbiAgICB9LFxuICB9KTtcbiAgYXBwLnJlZ2lzdGVyKHRlc3RQbHVnaW4pO1xuXG4gIGdldFNpbXVsYXRvcihhcHApO1xuICBleHBlY3Qod2FzUmVzb2x2ZWQpLnRvQmVUcnV0aHkoKTtcbn0pO1xuXG50ZXN0KCdtaWRkbGV3YXJlIC0gaW52YWxpZCBlbmRwb2ludCcsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgcHJlZml4OiAnJyxcbiAgICBwYXRoOiAnL2FwaS92YWx1ZU9mJyxcbiAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICBib2R5OiB7fSxcbiAgICByZXF1ZXN0OiB7XG4gICAgICBib2R5OiB7fSxcbiAgICB9LFxuICAgIG1lbW9pemVkOiBuZXcgTWFwKCksXG4gIH0gYXMgYW55O1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgc29tZXRoaW5nOiAoKSA9PiB7fSxcbiAgICBvdGhlcjogKCkgPT4ge30sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzplcnJvcicpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd2YWx1ZU9mJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLmVycm9yLm1lc3NhZ2UpLnRvQmUoJ01pc3NpbmcgUlBDIGhhbmRsZXIgZm9yIHZhbHVlT2YnKTtcbiAgICB9LFxuICB9KTtcblxuICBjb25zdCBtaWRkbGV3YXJlID1cbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZSAmJlxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlKFxuICAgICAge1xuICAgICAgICBlbWl0dGVyOiBtb2NrRW1pdHRlcixcbiAgICAgICAgaGFuZGxlcnM6IG1vY2tIYW5kbGVycyxcbiAgICAgIH0sXG4gICAgICBtb2NrU2VydmljZVxuICAgICk7XG4gIGlmICghbWlkZGxld2FyZSkge1xuICAgIGRvbmUuZmFpbCgpO1xuICAgIGRvbmUoKTtcbiAgICByZXR1cm47XG4gIH1cblxuICB0cnkge1xuICAgIG1pZGRsZXdhcmUobW9ja0N0eCwgKCkgPT4gUHJvbWlzZS5yZXNvbHZlKCkpLnRoZW4oKCkgPT4ge1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhLm1lc3NhZ2UpLnRvQmUoJ01pc3NpbmcgUlBDIGhhbmRsZXIgZm9yIHZhbHVlT2YnKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuZGF0YS5jb2RlKS50b0JlKCdFUlJfTUlTU0lOR19IQU5ETEVSJyk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnZmFpbHVyZScpO1xuICAgICAgZXhwZWN0KG1vY2tDdHguc3RhdHVzKS50b0JlKDQwNCk7XG4gICAgICBkb25lKCk7XG4gICAgfSk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBkb25lLmZhaWwoZSk7XG4gIH1cbn0pO1xuXG50ZXN0KCdtaWRkbGV3YXJlIC0gdmFsaWQgZW5kcG9pbnQnLCAoZG9uZSkgPT4ge1xuICBjb25zdCBtb2NrQ3R4OiBDb250ZXh0ID0ge1xuICAgIGhlYWRlcnM6IHt9LFxuICAgIHByZWZpeDogJycsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgYm9keToge30sXG4gICAgcmVxdWVzdDoge1xuICAgICAgYm9keTogJ3Rlc3QtYXJncycsXG4gICAgfSxcbiAgfSBhcyBhbnk7XG4gIGxldCBleGVjdXRlZEhhbmRsZXIgPSBmYWxzZTtcbiAgY29uc3QgbW9ja0hhbmRsZXJzID0ge1xuICAgIHRlc3QoYXJncywgY3R4KSB7XG4gICAgICBleGVjdXRlZEhhbmRsZXIgPSB0cnVlO1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvQmUoJ3Rlc3QtYXJncycpO1xuICAgICAgZXhwZWN0KGN0eCkudG9CZShtb2NrQ3R4KTtcbiAgICAgIHJldHVybiAxO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ3N1Y2Nlc3MnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgIH0sXG4gIH0pO1xuXG4gIGNvbnN0IHRhZ3MgPSB7XG4gICAgbmFtZTogJ3Vua25vd25fcm91dGUnLFxuICB9O1xuICBjb25zdCBtaWRkbGV3YXJlID1cbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZSAmJlxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlKFxuICAgICAge1xuICAgICAgICBSb3V0ZVRhZ3M6IHtcbiAgICAgICAgICBmcm9tOiAoKSA9PiB0YWdzLFxuICAgICAgICB9LFxuICAgICAgICBlbWl0dGVyOiBtb2NrRW1pdHRlcixcbiAgICAgICAgaGFuZGxlcnM6IG1vY2tIYW5kbGVycyxcbiAgICAgIH0sXG4gICAgICBtb2NrU2VydmljZVxuICAgICk7XG4gIGlmICghbWlkZGxld2FyZSkge1xuICAgIGRvbmUuZmFpbCgpO1xuICAgIGRvbmUoKTtcbiAgICByZXR1cm47XG4gIH1cblxuICB0cnkge1xuICAgIG1pZGRsZXdhcmUobW9ja0N0eCwgYXN5bmMgKCkgPT4ge1xuICAgICAgZXhwZWN0KGV4ZWN1dGVkSGFuZGxlcikudG9CZShmYWxzZSk7XG4gICAgICBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgIGV4cGVjdCh0YWdzLm5hbWUpLnRvQmUoJ3Rlc3QnKTtcbiAgICAgIGV4cGVjdChleGVjdXRlZEhhbmRsZXIpLnRvQmUodHJ1ZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEpLnRvQmUoMSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIHZhbGlkIGVuZHBvaW50IChjdXN0b20gYXBpIHBhdGgpJywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBwcmVmaXg6ICcnLFxuICAgIHBhdGg6ICcvdGVzdC9hcGkvbG9uZy90ZXN0JyxcbiAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICBib2R5OiB7fSxcbiAgICByZXF1ZXN0OiB7XG4gICAgICBib2R5OiAndGVzdC1hcmdzJyxcbiAgICB9LFxuICB9IGFzIGFueTtcbiAgbGV0IGV4ZWN1dGVkSGFuZGxlciA9IGZhbHNlO1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgdGVzdChhcmdzLCBjdHgpIHtcbiAgICAgIGV4ZWN1dGVkSGFuZGxlciA9IHRydWU7XG4gICAgICBleHBlY3QoYXJncykudG9CZSgndGVzdC1hcmdzJyk7XG4gICAgICBleHBlY3QoY3R4KS50b0JlKG1vY2tDdHgpO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfSxcbiAgfTtcbiAgY29uc3QgbW9ja0VtaXR0ZXIgPSBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgZW1pdCh0eXBlLCBwYXlsb2FkKSB7XG4gICAgICBleHBlY3QodHlwZSkudG9CZSgncnBjOm1ldGhvZCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICAgIHJwY0NvbmZpZzoge1xuICAgICAgICAgIGFwaVBhdGg6ICd0ZXN0L2FwaS9sb25nJyxcbiAgICAgICAgfSxcbiAgICAgIH0sXG4gICAgICBtb2NrU2VydmljZVxuICAgICk7XG4gIGlmICghbWlkZGxld2FyZSkge1xuICAgIGRvbmUuZmFpbCgpO1xuICAgIGRvbmUoKTtcbiAgICByZXR1cm47XG4gIH1cblxuICB0cnkge1xuICAgIG1pZGRsZXdhcmUobW9ja0N0eCwgYXN5bmMgKCkgPT4ge1xuICAgICAgZXhwZWN0KGV4ZWN1dGVkSGFuZGxlcikudG9CZShmYWxzZSk7XG4gICAgICBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgIGV4cGVjdChleGVjdXRlZEhhbmRsZXIpLnRvQmUodHJ1ZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEpLnRvQmUoMSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIHZhbGlkIGVuZHBvaW50IChjdXN0b20gYXBpIHBhdGggaW5jbHVkaW5nIHNsYXNoZXMpJywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBwcmVmaXg6ICcnLFxuICAgIHBhdGg6ICcvdGVzdC9hcGkvbG9uZy90ZXN0JyxcbiAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICBib2R5OiB7fSxcbiAgICByZXF1ZXN0OiB7XG4gICAgICBib2R5OiAndGVzdC1hcmdzJyxcbiAgICB9LFxuICB9IGFzIGFueTtcbiAgbGV0IGV4ZWN1dGVkSGFuZGxlciA9IGZhbHNlO1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgdGVzdChhcmdzLCBjdHgpIHtcbiAgICAgIGV4ZWN1dGVkSGFuZGxlciA9IHRydWU7XG4gICAgICBleHBlY3QoYXJncykudG9CZSgndGVzdC1hcmdzJyk7XG4gICAgICBleHBlY3QoY3R4KS50b0JlKG1vY2tDdHgpO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfSxcbiAgfTtcbiAgY29uc3QgbW9ja0VtaXR0ZXIgPSBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgZW1pdCh0eXBlLCBwYXlsb2FkKSB7XG4gICAgICBleHBlY3QodHlwZSkudG9CZSgncnBjOm1ldGhvZCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICAgIHJwY0NvbmZpZzoge1xuICAgICAgICAgIGFwaVBhdGg6ICcvdGVzdC8vL2FwaS9sb25nLy8vLycsXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgICAgbW9ja1NlcnZpY2VcbiAgICApO1xuICBpZiAoIW1pZGRsZXdhcmUpIHtcbiAgICBkb25lLmZhaWwoKTtcbiAgICBkb25lKCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBtaWRkbGV3YXJlKG1vY2tDdHgsIGFzeW5jICgpID0+IHtcbiAgICAgIGV4cGVjdChleGVjdXRlZEhhbmRsZXIpLnRvQmUoZmFsc2UpO1xuICAgICAgUHJvbWlzZS5yZXNvbHZlKCk7XG4gICAgfSkudGhlbigoKSA9PiB7XG4gICAgICBleHBlY3QoZXhlY3V0ZWRIYW5kbGVyKS50b0JlKHRydWUpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhKS50b0JlKDEpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5zdGF0dXMpLnRvQmUoJ3N1Y2Nlc3MnKTtcbiAgICAgIGRvbmUoKTtcbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ21pZGRsZXdhcmUgLSB2YWxpZCBlbmRwb2ludCB3aXRoIHJvdXRlIHByZWZpeCcsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgcHJlZml4OiAnL2xvbCcsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgYm9keToge30sXG4gICAgcmVxdWVzdDoge1xuICAgICAgYm9keTogJ3Rlc3QtYXJncycsXG4gICAgfSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KGFyZ3MsIGN0eCkge1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvQmUoJ3Rlc3QtYXJncycpO1xuICAgICAgZXhwZWN0KGN0eCkudG9CZShtb2NrQ3R4KTtcbiAgICAgIHJldHVybiAxO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ3N1Y2Nlc3MnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgIH0sXG4gIH0pO1xuXG4gIGNvbnN0IG1pZGRsZXdhcmUgPVxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlICYmXG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUoXG4gICAgICB7XG4gICAgICAgIGVtaXR0ZXI6IG1vY2tFbWl0dGVyLFxuICAgICAgICBoYW5kbGVyczogbW9ja0hhbmRsZXJzLFxuICAgICAgfSxcbiAgICAgIG1vY2tTZXJ2aWNlXG4gICAgKTtcbiAgaWYgKCFtaWRkbGV3YXJlKSB7XG4gICAgZG9uZS5mYWlsKCk7XG4gICAgZG9uZSgpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgbWlkZGxld2FyZShtb2NrQ3R4LCAoKSA9PiBQcm9taXNlLnJlc29sdmUoKSkudGhlbigoKSA9PiB7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEpLnRvQmUoMSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIHZhbGlkIGVuZHBvaW50IGZhaWx1cmUgd2l0aCBSZXNwb25zZUVycm9yJywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBwcmVmaXg6ICcnLFxuICAgIHBhdGg6ICcvYXBpL3Rlc3QnLFxuICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgIGJvZHk6IHt9LFxuICAgIHJlcXVlc3Q6IHtcbiAgICAgIGJvZHk6ICd0ZXN0LWFyZ3MnLFxuICAgIH0sXG4gICAgbWVtb2l6ZWQ6IG5ldyBNYXAoKSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IGUgPSBuZXcgUmVzcG9uc2VFcnJvcignVGVzdCBGYWlsdXJlJyk7XG4gIC8vICRGbG93Rml4TWVcbiAgZS5jb2RlID0gJ0VSUl9DT0RFX1RFU1QnO1xuICAvLyAkRmxvd0ZpeE1lXG4gIGUubWV0YSA9IHtoZWxsbzogJ3dvcmxkJ307XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KGUpO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ2ZhaWx1cmUnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgICAgZXhwZWN0KHBheWxvYWQuZXJyb3IpLnRvQmUoZSk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICB9LFxuICAgICAgbW9ja1NlcnZpY2VcbiAgICApO1xuICBpZiAoIW1pZGRsZXdhcmUpIHtcbiAgICBkb25lLmZhaWwoKTtcbiAgICBkb25lKCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBtaWRkbGV3YXJlKG1vY2tDdHgsICgpID0+IFByb21pc2UucmVzb2x2ZSgpKS50aGVuKCgpID0+IHtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuZGF0YS5tZXNzYWdlKS50b0JlKGUubWVzc2FnZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEuY29kZSkudG9CZShlLmNvZGUpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhLm1ldGEpLnRvQmUoZS5tZXRhKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuc3RhdHVzKS50b0JlKCdmYWlsdXJlJyk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QoT2JqZWN0LmtleXMobW9ja0N0eC5ib2R5KS5sZW5ndGgpLnRvQmUoMik7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QoT2JqZWN0LmtleXMobW9ja0N0eC5ib2R5LmRhdGEpLmxlbmd0aCkudG9CZSgzKTtcbiAgICAgIGRvbmUoKTtcbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ21pZGRsZXdhcmUgLSB2YWxpZCBlbmRwb2ludCBmYWlsdXJlIHdpdGggc3RhbmRhcmQgZXJyb3InLCAoZG9uZSkgPT4ge1xuICBjb25zdCBtb2NrQ3R4OiBDb250ZXh0ID0ge1xuICAgIGhlYWRlcnM6IHt9LFxuICAgIHByZWZpeDogJycsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgYm9keToge30sXG4gICAgcmVxdWVzdDoge1xuICAgICAgYm9keTogJ3Rlc3QtYXJncycsXG4gICAgfSxcbiAgICBtZW1vaXplZDogbmV3IE1hcCgpLFxuICB9IGFzIGFueTtcbiAgY29uc3QgZSA9IG5ldyBFcnJvcignVGVzdCBGYWlsdXJlJyk7XG4gIC8vIEB0cy1pZ25vcmVcbiAgZS5jb2RlID0gJ0VSUl9DT0RFX1RFU1QnO1xuICAvLyBAdHMtaWdub3JlXG4gIGUubWV0YSA9IHtoZWxsbzogJ3dvcmxkJ307XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KGUpO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ2ZhaWx1cmUnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgICAgZXhwZWN0KHBheWxvYWQuZXJyb3IpLnRvQmUoZSk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICB9LFxuICAgICAgbW9ja1NlcnZpY2VcbiAgICApO1xuICBpZiAoIW1pZGRsZXdhcmUpIHtcbiAgICBkb25lLmZhaWwoKTtcbiAgICBkb25lKCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBtaWRkbGV3YXJlKG1vY2tDdHgsICgpID0+IFByb21pc2UucmVzb2x2ZSgpKS50aGVuKCgpID0+IHtcbiAgICAgIGV4cGVjdChcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICBtb2NrQ3R4LmJvZHkuZGF0YS5tZXNzYWdlXG4gICAgICApLnRvQmUoXG4gICAgICAgICdVbmtub3duRXJyb3IgLSBVc2UgUmVzcG9uc2VFcnJvciBmcm9tIGZ1c2lvbi1wbHVnaW4tcnBjIChvciBmdXNpb24tcGx1Z2luLXJwYy1yZWR1eC1yZWFjdCBpZiB5b3UgYXJlIHVzaW5nIFJlYWN0KSBwYWNrYWdlIGZvciBtb3JlIGRldGFpbGVkIGVycm9yIG1lc3NhZ2VzJ1xuICAgICAgKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuZGF0YS5jb2RlKS50b0JlKHVuZGVmaW5lZCk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEubWV0YSkudG9CZSh1bmRlZmluZWQpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5zdGF0dXMpLnRvQmUoJ2ZhaWx1cmUnKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChPYmplY3Qua2V5cyhtb2NrQ3R4LmJvZHkpLmxlbmd0aCkudG9CZSgyKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChPYmplY3Qua2V5cyhtb2NrQ3R4LmJvZHkuZGF0YSkubGVuZ3RoKS50b0JlKDMpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgndGhyb3dzIHdoZW4gbm90IHBhc3NlZCBjdHgnLCAoZG9uZSkgPT4ge1xuICBjb25zdCBhcHAgPSBjcmVhdGVUZXN0Rml4dHVyZSgpO1xuXG4gIGV4cGVjdC5hc3NlcnRpb25zKDEpO1xuICBnZXRTaW11bGF0b3IoXG4gICAgYXBwLFxuICAgIGNyZWF0ZVBsdWdpbih7XG4gICAgICBkZXBzOiB7cnBjRmFjdG9yeTogUlBDVG9rZW59LFxuICAgICAgbWlkZGxld2FyZTpcbiAgICAgICAgKHtycGNGYWN0b3J5fSkgPT5cbiAgICAgICAgYXN5bmMgKCkgPT4ge1xuICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICBleHBlY3QoKCkgPT4gcnBjRmFjdG9yeS5mcm9tKCkpLnRvVGhyb3coKTtcbiAgICAgICAgICBkb25lKCk7XG4gICAgICAgIH0sXG4gICAgfSlcbiAgKS5yZXF1ZXN0KCcvJyk7XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIGJvZHlwYXJzZXIgb3B0aW9ucyB3aXRoIHZlcnkgc21hbGwganNvbkxpbW