fusion-plugin-rpc
Version:
Fetch data on the server and client with an RPC style interface.
821 lines (818 loc) • 89 kB
JavaScript
"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