fusion-plugin-rpc
Version:
Fetch data on the server and client with an RPC style interface.
816 lines (814 loc) • 86.7 kB
JavaScript
/** 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.
*
*/
import MockEmitter from 'events';
import MockReq from 'mock-req';
import FormData from 'form-data';
import App, { createPlugin, RouteTagsToken } from 'fusion-core';
import { getSimulator, getService } from 'fusion-test-utils';
import { UniversalEventsToken } from 'fusion-plugin-universal-events';
import { RPCHandlersToken, RPCToken } from '../tokens';
import RPCPlugin from '../server';
import MockRPCPlugin from '../mock';
import ResponseError from '../response-error';
import createMockEmitter from './create-mock-emitter';
const MOCK_JSON_PARAMS = {
test: 'test-args'
};
const mockService = getService(() => {
const app = new App('content', el => el);
// @ts-ignore
const mockEmitter = new MockEmitter();
mockEmitter.from = () => mockEmitter;
const mockEmitterPlugin = createPlugin({
provides: () => mockEmitter
});
app.register(UniversalEventsToken, mockEmitterPlugin);
app.register(RPCHandlersToken, createPlugin({
provides: () => ({})
}));
return app;
}, MockRPCPlugin);
/* Test fixtures */
function createTestFixture() {
const mockHandlers = {};
// @ts-ignore
const mockEmitter = new MockEmitter();
mockEmitter.from = () => mockEmitter;
const mockEmitterPlugin = createPlugin({
provides: () => mockEmitter
});
const app = new App('content', el => el);
app.register(UniversalEventsToken, mockEmitterPlugin);
app.register(RPCHandlersToken, mockHandlers);
app.register(RPCToken, RPCPlugin);
return app;
}
function mockRequest() {
const req = new MockReq({
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;
getSimulator(app, createPlugin({
deps: {
rpcFactory: RPCToken
},
provides: ({
rpcFactory
}) => {
expect(rpcFactory).toBeTruthy();
wasResolved = true;
}
}));
expect(wasResolved).toBeTruthy();
});
test('service - requires ctx', () => {
const app = createTestFixture();
let wasResolved = false;
getSimulator(app, createPlugin({
deps: {
rpcFactory: 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 = createPlugin({
provides: () => createMockEmitter({
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 App('content', el => el);
app.register(UniversalEventsToken, mockEmitter);
app.register(RPCToken, RPCPlugin);
app.register(RPCHandlersToken, mockHandlers);
return app;
};
const sim = getSimulator(appCreator());
const rpcFactory = sim.getService(RPCToken);
const routeTags = sim.getService(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 = createPlugin({
provides: () => createMockEmitter({
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 App('content', el => el);
app.register(UniversalEventsToken, mockEmitter);
app.register(RPCHandlersToken, mockHandlers);
return app;
};
const rpcFactory = getService(appCreator, RPCPlugin);
// @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 = createPlugin({
provides: () => createMockEmitter({
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 App('content', el => el);
app.register(UniversalEventsToken, mockEmitter);
app.register(RPCHandlersToken, mockHandlers);
return app;
};
const rpcFactory = getService(appCreator, RPCPlugin);
// @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 = createPlugin({
deps: {
rpcFactory: RPCToken
},
middleware: ({
rpcFactory
}) => {
expect(rpcFactory).toBeTruthy();
wasResolved = true;
return async () => {};
}
});
app.register(testPlugin);
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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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('Test Failure');
// $FlowFixMe
e.code = 'ERR_CODE_TEST';
// $FlowFixMe
e.meta = {
hello: 'world'
};
const mockHandlers = {
test() {
return Promise.reject(e);
}
};
const mockEmitter = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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);
getSimulator(app, createPlugin({
deps: {
rpcFactory: 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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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();
form.append('name', 'test');
const req = new MockReq({
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 = createMockEmitter({
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 = RPCPlugin.middleware && RPCPlugin.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,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJNb2NrRW1pdHRlciIsIk1vY2tSZXEiLCJGb3JtRGF0YSIsIkFwcCIsImNyZWF0ZVBsdWdpbiIsIlJvdXRlVGFnc1Rva2VuIiwiZ2V0U2ltdWxhdG9yIiwiZ2V0U2VydmljZSIsIlVuaXZlcnNhbEV2ZW50c1Rva2VuIiwiUlBDSGFuZGxlcnNUb2tlbiIsIlJQQ1Rva2VuIiwiUlBDUGx1Z2luIiwiTW9ja1JQQ1BsdWdpbiIsIlJlc3BvbnNlRXJyb3IiLCJjcmVhdGVNb2NrRW1pdHRlciIsIk1PQ0tfSlNPTl9QQVJBTVMiLCJ0ZXN0IiwibW9ja1NlcnZpY2UiLCJhcHAiLCJlbCIsIm1vY2tFbWl0dGVyIiwiZnJvbSIsIm1vY2tFbWl0dGVyUGx1Z2luIiwicHJvdmlkZXMiLCJyZWdpc3RlciIsImNyZWF0ZVRlc3RGaXh0dXJlIiwibW9ja0hhbmRsZXJzIiwibW9ja1JlcXVlc3QiLCJyZXEiLCJtZXRob2QiLCJ1cmwiLCJoZWFkZXJzIiwiQWNjZXB0Iiwid3JpdGUiLCJlbmQiLCJ3YXNSZXNvbHZlZCIsImRlcHMiLCJycGNGYWN0b3J5IiwiZXhwZWN0IiwidG9CZVRydXRoeSIsInRvVGhyb3ciLCJkb25lIiwibW9ja0N0eCIsIm1lbW9pemVkIiwiTWFwIiwiYXJncyIsImN0eCIsInRvQmUiLCJlbWl0IiwidHlwZSIsInBheWxvYWQiLCJzdGF0dXMiLCJ0aW1pbmciLCJhcHBDcmVhdG9yIiwic2ltIiwicm91dGVUYWdzIiwicnBjIiwicmVxdWVzdCIsInAiLCJQcm9taXNlIiwidGhlbiIsInJlcyIsIm5hbWUiLCJlIiwiZmFpbCIsIkVycm9yIiwicmVqZWN0IiwiZXJyb3IiLCJyZWplY3RzIiwib3JpZ2luIiwibWVzc2FnZSIsInRvVGhyb3dFcnJvciIsInRlc3RQbHVnaW4iLCJtaWRkbGV3YXJlIiwicHJlZml4IiwicGF0aCIsImJvZHkiLCJzb21ldGhpbmciLCJvdGhlciIsImVtaXR0ZXIiLCJoYW5kbGVycyIsInJlc29sdmUiLCJkYXRhIiwiY29kZSIsImV4ZWN1dGVkSGFuZGxlciIsInRhZ3MiLCJSb3V0ZVRhZ3MiLCJycGNDb25maWciLCJhcGlQYXRoIiwibWV0YSIsImhlbGxvIiwiT2JqZWN0Iiwia2V5cyIsImxlbmd0aCIsInVuZGVmaW5lZCIsImFzc2VydGlvbnMiLCJpcyIsIm1pbmVUeXBlcyIsInNvbWUiLCJtaW5lVHlwZSIsImluY2x1ZGVzIiwidG9FcXVhbCIsIm1vY2tCb2R5UGFyc2VyT3B0aW9ucyIsImpzb25MaW1pdCIsImJvZHlQYXJzZXJPcHRpb25zIiwiZm9ybSIsImFwcGVuZCIsImdldEhlYWRlcnMiLCJwaXBlIl0sInNvdXJjZXMiOlsic3JjL19fdGVzdHNfXy9pbmRleC5ub2RlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKiBDb3B5cmlnaHQgKGMpIDIwMTggVWJlciBUZWNobm9sb2dpZXMsIEluYy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKi9cblxuaW1wb3J0IE1vY2tFbWl0dGVyIGZyb20gJ2V2ZW50cyc7XG5pbXBvcnQgTW9ja1JlcSBmcm9tICdtb2NrLXJlcSc7XG5pbXBvcnQgRm9ybURhdGEgZnJvbSAnZm9ybS1kYXRhJztcblxuaW1wb3J0IEFwcCwge2NyZWF0ZVBsdWdpbiwgUm91dGVUYWdzVG9rZW59IGZyb20gJ2Z1c2lvbi1jb3JlJztcbmltcG9ydCB0eXBlIHtDb250ZXh0fSBmcm9tICdmdXNpb24tY29yZSc7XG5pbXBvcnQge2dldFNpbXVsYXRvciwgZ2V0U2VydmljZX0gZnJvbSAnZnVzaW9uLXRlc3QtdXRpbHMnO1xuaW1wb3J0IHtVbml2ZXJzYWxFdmVudHNUb2tlbn0gZnJvbSAnZnVzaW9uLXBsdWdpbi11bml2ZXJzYWwtZXZlbnRzJztcbmltcG9ydCB7UlBDSGFuZGxlcnNUb2tlbiwgUlBDVG9rZW59IGZyb20gJy4uL3Rva2Vucyc7XG5pbXBvcnQgUlBDUGx1Z2luIGZyb20gJy4uL3NlcnZlcic7XG5pbXBvcnQgdHlwZSB7SUVtaXR0ZXIsIFJQQ1NlcnZpY2VUeXBlfSBmcm9tICcuLi90eXBlcyc7XG5pbXBvcnQgTW9ja1JQQ1BsdWdpbiBmcm9tICcuLi9tb2NrJztcbmltcG9ydCBSZXNwb25zZUVycm9yIGZyb20gJy4uL3Jlc3BvbnNlLWVycm9yJztcbmltcG9ydCBjcmVhdGVNb2NrRW1pdHRlciBmcm9tICcuL2NyZWF0ZS1tb2NrLWVtaXR0ZXInO1xuXG5jb25zdCBNT0NLX0pTT05fUEFSQU1TID0ge3Rlc3Q6ICd0ZXN0LWFyZ3MnfTtcblxuY29uc3QgbW9ja1NlcnZpY2U6IFJQQ1NlcnZpY2VUeXBlID0gZ2V0U2VydmljZSgoKSA9PiB7XG4gIGNvbnN0IGFwcCA9IG5ldyBBcHAoJ2NvbnRlbnQnLCAoZWwpID0+IGVsKTtcbiAgLy8gQHRzLWlnbm9yZVxuICBjb25zdCBtb2NrRW1pdHRlcjogSUVtaXR0ZXIgPSBuZXcgTW9ja0VtaXR0ZXIoKSBhcyBhbnk7XG4gIG1vY2tFbWl0dGVyLmZyb20gPSAoKSA9PiBtb2NrRW1pdHRlcjtcbiAgY29uc3QgbW9ja0VtaXR0ZXJQbHVnaW4gPSBjcmVhdGVQbHVnaW4oe1xuICAgIHByb3ZpZGVzOiAoKSA9PiBtb2NrRW1pdHRlcixcbiAgfSk7XG4gIGFwcC5yZWdpc3RlcihVbml2ZXJzYWxFdmVudHNUb2tlbiwgbW9ja0VtaXR0ZXJQbHVnaW4pO1xuICBhcHAucmVnaXN0ZXIoUlBDSGFuZGxlcnNUb2tlbiwgY3JlYXRlUGx1Z2luKHtwcm92aWRlczogKCkgPT4gKHt9KX0pKTtcbiAgcmV0dXJuIGFwcDtcbn0sIE1vY2tSUENQbHVnaW4pO1xuXG4vKiBUZXN0IGZpeHR1cmVzICovXG5mdW5jdGlvbiBjcmVhdGVUZXN0Rml4dHVyZSgpIHtcbiAgY29uc3QgbW9ja0hhbmRsZXJzID0ge307XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgbW9ja0VtaXR0ZXI6IElFbWl0dGVyID0gbmV3IE1vY2tFbWl0dGVyKCkgYXMgYW55O1xuICBtb2NrRW1pdHRlci5mcm9tID0gKCkgPT4gbW9ja0VtaXR0ZXI7XG4gIGNvbnN0IG1vY2tFbWl0dGVyUGx1Z2luID0gY3JlYXRlUGx1Z2luKHtcbiAgICBwcm92aWRlczogKCkgPT4gbW9ja0VtaXR0ZXIsXG4gIH0pO1xuXG4gIGNvbnN0IGFwcCA9IG5ldyBBcHAoJ2NvbnRlbnQnLCAoZWwpID0+IGVsKTtcbiAgYXBwLnJlZ2lzdGVyKFVuaXZlcnNhbEV2ZW50c1Rva2VuLCBtb2NrRW1pdHRlclBsdWdpbik7XG4gIGFwcC5yZWdpc3RlcihSUENIYW5kbGVyc1Rva2VuLCBtb2NrSGFuZGxlcnMpO1xuICBhcHAucmVnaXN0ZXIoUlBDVG9rZW4sIFJQQ1BsdWdpbik7XG4gIHJldHVybiBhcHA7XG59XG5cbmZ1bmN0aW9uIG1vY2tSZXF1ZXN0KCkge1xuICBjb25zdCByZXEgPSBuZXcgTW9ja1JlcSh7XG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgdXJsOiAnL2FwaS90ZXN0JyxcbiAgICBoZWFkZXJzOiB7XG4gICAgICBBY2NlcHQ6ICd0ZXh0L3BsYWluJyxcbiAgICB9LFxuICB9KTtcbiAgcmVxLndyaXRlKE1PQ0tfSlNPTl9QQVJBTVMpO1xuICByZXEuZW5kKCk7XG5cbiAgcmV0dXJuIHJlcTtcbn1cblxudGVzdCgnRnVzaW9uQXBwIC0gc2VydmljZSByZXNvbHZlZCcsICgpID0+IHtcbiAgY29uc3QgYXBwID0gY3JlYXRlVGVzdEZpeHR1cmUoKTtcblxuICBsZXQgd2FzUmVzb2x2ZWQgPSBmYWxzZTtcbiAgZ2V0U2ltdWxhdG9yKFxuICAgIGFwcCxcbiAgICBjcmVhdGVQbHVnaW4oe1xuICAgICAgZGVwczoge3JwY0ZhY3Rvcnk6IFJQQ1Rva2VufSxcbiAgICAgIHByb3ZpZGVzOiAoe3JwY0ZhY3Rvcnl9KSA9PiB7XG4gICAgICAgIGV4cGVjdChycGNGYWN0b3J5KS50b0JlVHJ1dGh5KCk7XG4gICAgICAgIHdhc1Jlc29sdmVkID0gdHJ1ZTtcbiAgICAgIH0sXG4gICAgfSlcbiAgKTtcbiAgZXhwZWN0KHdhc1Jlc29sdmVkKS50b0JlVHJ1dGh5KCk7XG59KTtcblxudGVzdCgnc2VydmljZSAtIHJlcXVpcmVzIGN0eCcsICgpID0+IHtcbiAgY29uc3QgYXBwID0gY3JlYXRlVGVzdEZpeHR1cmUoKTtcblxuICBsZXQgd2FzUmVzb2x2ZWQgPSBmYWxzZTtcbiAgZ2V0U2ltdWxhdG9yKFxuICAgIGFwcCxcbiAgICBjcmVhdGVQbHVnaW4oe1xuICAgICAgZGVwczoge3JwY0ZhY3Rvcnk6IFJQQ1Rva2VufSxcbiAgICAgIHByb3ZpZGVzOiAoe3JwY0ZhY3Rvcnl9KSA9PiB7XG4gICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgZXhwZWN0KCgpID0+IHJwY0ZhY3RvcnkoKSkudG9UaHJvdygpO1xuICAgICAgICB3YXNSZXNvbHZlZCA9IHRydWU7XG4gICAgICB9LFxuICAgIH0pXG4gICk7XG4gIGV4cGVjdCh3YXNSZXNvbHZlZCkudG9CZVRydXRoeSgpO1xufSk7XG5cbnRlc3QoJ3NlcnZpY2UgLSByZXF1ZXN0IGFwaScsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgbWVtb2l6ZWQ6IG5ldyBNYXAoKSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KGFyZ3MsIGN0eCkge1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvQmUoJ3Rlc3QtYXJncycpO1xuICAgICAgZXhwZWN0KGN0eCkudG9CZShtb2NrQ3R4KTtcbiAgICAgIHJldHVybiAxO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlUGx1Z2luKHtcbiAgICBwcm92aWRlczogKCkgPT5cbiAgICAgIGNyZWF0ZU1vY2tFbWl0dGVyKHtcbiAgICAgICAgZW1pdDogKHR5cGU6IHVua25vd24sIHBheWxvYWQ6IGFueSkgPT4ge1xuICAgICAgICAgIGV4cGVjdCh0eXBlKS50b0JlKCdycGM6bWV0aG9kJyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQuc3RhdHVzKS50b0JlKCdzdWNjZXNzJyk7XG4gICAgICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgICAgIH0sXG4gICAgICAgIGZyb20oKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgfSk7XG5cbiAgY29uc3QgYXBwQ3JlYXRvciA9ICgpID0+IHtcbiAgICBjb25zdCBhcHAgPSBuZXcgQXBwKCdjb250ZW50JywgKGVsKSA9PiBlbCk7XG4gICAgYXBwLnJlZ2lzdGVyKFVuaXZlcnNhbEV2ZW50c1Rva2VuLCBtb2NrRW1pdHRlcik7XG4gICAgYXBwLnJlZ2lzdGVyKFJQQ1Rva2VuLCBSUENQbHVnaW4pO1xuICAgIGFwcC5yZWdpc3RlcihSUENIYW5kbGVyc1Rva2VuLCBtb2NrSGFuZGxlcnMpO1xuICAgIHJldHVybiBhcHA7XG4gIH07XG5cbiAgY29uc3Qgc2ltID0gZ2V0U2ltdWxhdG9yKGFwcENyZWF0b3IoKSk7XG5cbiAgY29uc3QgcnBjRmFjdG9yeSA9IHNpbS5nZXRTZXJ2aWNlKFJQQ1Rva2VuKTtcbiAgY29uc3Qgcm91dGVUYWdzID0gc2ltLmdldFNlcnZpY2UoUm91dGVUYWdzVG9rZW4pO1xuICAvLyBAdHMtaWdub3JlXG4gIGNvbnN0IHJwYyA9IHJwY0ZhY3RvcnkuZnJvbShtb2NrQ3R4KTtcblxuICBleHBlY3QodHlwZW9mIHJwYy5yZXF1ZXN0KS50b0JlKCdmdW5jdGlvbicpO1xuICB0cnkge1xuICAgIGNvbnN0IHAgPSBycGMucmVxdWVzdCgndGVzdCcsICd0ZXN0LWFyZ3MnKTtcbiAgICBleHBlY3QocCBpbnN0YW5jZW9mIFByb21pc2UpLnRvQmVUcnV0aHkoKTtcbiAgICBwLnRoZW4oKHJlcykgPT4ge1xuICAgICAgZXhwZWN0KHJlcykudG9CZSgxKTtcbiAgICAgIGV4cGVjdChyb3V0ZVRhZ3MuZnJvbShtb2NrQ3R4KS5uYW1lKS50b0JlKCd1bmtub3duX3JvdXRlJyk7XG4gICAgICAvLyAgJ2RvZXMgbm90IG92ZXJ3cml0ZSB0aGUgbmFtZSB0YWcgb24gU1NSJ1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgLy8gJEZsb3dGaXhNZVxuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ3NlcnZpY2UgLSByZXF1ZXN0IGFwaSB3aXRoIGZhaWxpbmcgcmVxdWVzdCcsIGFzeW5jICgpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBtZW1vaXplZDogbmV3IE1hcCgpLFxuICB9IGFzIGFueTtcbiAgY29uc3QgZSA9IG5ldyBFcnJvcignZmFpbCcpO1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgdGVzdCgpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdChlKTtcbiAgICB9LFxuICB9O1xuICBjb25zdCBtb2NrRW1pdHRlciA9IGNyZWF0ZVBsdWdpbih7XG4gICAgcHJvdmlkZXM6ICgpID0+XG4gICAgICBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgICAgIGV4cGVjdCh0eXBlKS50b0JlKCdycGM6bWV0aG9kJyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQuc3RhdHVzKS50b0JlKCdmYWlsdXJlJyk7XG4gICAgICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgICAgICAgZXhwZWN0KHBheWxvYWQuZXJyb3IpLnRvQmUoZSk7XG4gICAgICAgIH0sXG4gICAgICAgIGZyb20oKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgfSk7XG5cbiAgY29uc3QgYXBwQ3JlYXRvciA9ICgpID0+IHtcbiAgICBjb25zdCBhcHAgPSBuZXcgQXBwKCdjb250ZW50JywgKGVsKSA9PiBlbCk7XG4gICAgYXBwLnJlZ2lzdGVyKFVuaXZlcnNhbEV2ZW50c1Rva2VuLCBtb2NrRW1pdHRlcik7XG4gICAgYXBwLnJlZ2lzdGVyKFJQQ0hhbmRsZXJzVG9rZW4sIG1vY2tIYW5kbGVycyk7XG4gICAgcmV0dXJuIGFwcDtcbiAgfTtcblxuICBjb25zdCBycGNGYWN0b3J5ID0gZ2V0U2VydmljZShhcHBDcmVhdG9yLCBSUENQbHVnaW4pO1xuICAvLyBAdHMtaWdub3JlXG4gIGNvbnN0IHJwYyA9IHJwY0ZhY3RvcnkuZnJvbShtb2NrQ3R4KTtcblxuICBleHBlY3QodHlwZW9mIHJwYy5yZXF1ZXN0KS50b0JlKCdmdW5jdGlvbicpO1xuICBjb25zdCBwID0gcnBjLnJlcXVlc3QoJ3Rlc3QnLCAndGVzdC1hcmdzJyk7XG4gIGV4cGVjdChwIGluc3RhbmNlb2YgUHJvbWlzZSkudG9CZVRydXRoeSgpO1xuICBhd2FpdCBleHBlY3QocCkucmVqZWN0cy50b1Rocm93KGUpO1xufSk7XG5cbnRlc3QoJ3NlcnZpY2UgLSByZXF1ZXN0IGFwaSB3aXRoIGludmFsaWQgZW5kcG9pbnQnLCBhc3luYyAoKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgbWVtb2l6ZWQ6IG5ldyBNYXAoKSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHt9O1xuICBjb25zdCBtb2NrRW1pdHRlciA9IGNyZWF0ZVBsdWdpbih7XG4gICAgcHJvdmlkZXM6ICgpID0+XG4gICAgICBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgICAgIGV4cGVjdCh0eXBlKS50b0JlKCdycGM6ZXJyb3InKTtcbiAgICAgICAgICBleHBlY3QocGF5bG9hZC5tZXRob2QpLnRvQmUoJ3Rlc3QnKTtcbiAgICAgICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ3NlcnZlcicpO1xuICAgICAgICAgIGV4cGVjdChwYXlsb2FkLmVycm9yLm1lc3NhZ2UpLnRvQmUoJ01pc3NpbmcgUlBDIGhhbmRsZXIgZm9yIHRlc3QnKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnJvbSgpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcbiAgICAgIH0pLFxuICB9KTtcblxuICBjb25zdCBhcHBDcmVhdG9yID0gKCkgPT4ge1xuICAgIGNvbnN0IGFwcCA9IG5ldyBBcHAoJ2NvbnRlbnQnLCAoZWwpID0+IGVsKTtcbiAgICBhcHAucmVnaXN0ZXIoVW5pdmVyc2FsRXZlbnRzVG9rZW4sIG1vY2tFbWl0dGVyKTtcbiAgICBhcHAucmVnaXN0ZXIoUlBDSGFuZGxlcnNUb2tlbiwgbW9ja0hhbmRsZXJzKTtcbiAgICByZXR1cm4gYXBwO1xuICB9O1xuXG4gIGNvbnN0IHJwY0ZhY3RvcnkgPSBnZXRTZXJ2aWNlKGFwcENyZWF0b3IsIFJQQ1BsdWdpbik7XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgcnBjID0gcnBjRmFjdG9yeS5mcm9tKG1vY2tDdHgpO1xuXG4gIGV4cGVjdCh0eXBlb2YgcnBjLnJlcXVlc3QpLnRvQmUoJ2Z1bmN0aW9uJyk7XG4gIGNvbnN0IHAgPSBycGMucmVxdWVzdCgndGVzdCcsICd0ZXN0LWFyZ3MnKTtcbiAgZXhwZWN0KHAgaW5zdGFuY2VvZiBQcm9taXNlKS50b0JlVHJ1dGh5KCk7XG4gIGF3YWl0IGV4cGVjdChwKS5yZWplY3RzLnRvVGhyb3dFcnJvcignTWlzc2luZyBSUEMgaGFuZGxlciBmb3IgdGVzdCcpO1xufSk7XG5cbnRlc3QoJ0Z1c2lvbkpTIC0gbWlkZGxld2FyZSByZXNvbHZlcycsIGFzeW5jICgpID0+IHtcbiAgY29uc3QgYXBwID0gY3JlYXRlVGVzdEZpeHR1cmUoKTtcblxuICBsZXQgd2FzUmVzb2x2ZWQgPSBmYWxzZTtcblxuICBjb25zdCB0ZXN0UGx1Z2luID0gY3JlYXRlUGx1Z2luKHtcbiAgICBkZXBzOiB7cnBjRmFjdG9yeTogUlBDVG9rZW59LFxuICAgIG1pZGRsZXdhcmU6ICh7cnBjRmFjdG9yeX0pID0+IHtcbiAgICAgIGV4cGVjdChycGNGYWN0b3J5KS50b0JlVHJ1dGh5KCk7XG4gICAgICB3YXNSZXNvbHZlZCA9IHRydWU7XG5cbiAgICAgIHJldHVybiBhc3luYyAoKSA9PiB7fTtcbiAgICB9LFxuICB9KTtcbiAgYXBwLnJlZ2lzdGVyKHRlc3RQbHVnaW4pO1xuXG4gIGdldFNpbXVsYXRvcihhcHApO1xuICBleHBlY3Qod2FzUmVzb2x2ZWQpLnRvQmVUcnV0aHkoKTtcbn0pO1xuXG50ZXN0KCdtaWRkbGV3YXJlIC0gaW52YWxpZCBlbmRwb2ludCcsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgcHJlZml4OiAnJyxcbiAgICBwYXRoOiAnL2FwaS92YWx1ZU9mJyxcbiAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICBib2R5OiB7fSxcbiAgICByZXF1ZXN0OiB7XG4gICAgICBib2R5OiB7fSxcbiAgICB9LFxuICAgIG1lbW9pemVkOiBuZXcgTWFwKCksXG4gIH0gYXMgYW55O1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgc29tZXRoaW5nOiAoKSA9PiB7fSxcbiAgICBvdGhlcjogKCkgPT4ge30sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzplcnJvcicpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd2YWx1ZU9mJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLmVycm9yLm1lc3NhZ2UpLnRvQmUoJ01pc3NpbmcgUlBDIGhhbmRsZXIgZm9yIHZhbHVlT2YnKTtcbiAgICB9LFxuICB9KTtcblxuICBjb25zdCBtaWRkbGV3YXJlID1cbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZSAmJlxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlKFxuICAgICAge1xuICAgICAgICBlbWl0dGVyOiBtb2NrRW1pdHRlcixcbiAgICAgICAgaGFuZGxlcnM6IG1vY2tIYW5kbGVycyxcbiAgICAgIH0sXG4gICAgICBtb2NrU2VydmljZVxuICAgICk7XG4gIGlmICghbWlkZGxld2FyZSkge1xuICAgIGRvbmUuZmFpbCgpO1xuICAgIGRvbmUoKTtcbiAgICByZXR1cm47XG4gIH1cblxuICB0cnkge1xuICAgIG1pZGRsZXdhcmUobW9ja0N0eCwgKCkgPT4gUHJvbWlzZS5yZXNvbHZlKCkpLnRoZW4oKCkgPT4ge1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhLm1lc3NhZ2UpLnRvQmUoJ01pc3NpbmcgUlBDIGhhbmRsZXIgZm9yIHZhbHVlT2YnKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuZGF0YS5jb2RlKS50b0JlKCdFUlJfTUlTU0lOR19IQU5ETEVSJyk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnZmFpbHVyZScpO1xuICAgICAgZXhwZWN0KG1vY2tDdHguc3RhdHVzKS50b0JlKDQwNCk7XG4gICAgICBkb25lKCk7XG4gICAgfSk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBkb25lLmZhaWwoZSk7XG4gIH1cbn0pO1xuXG50ZXN0KCdtaWRkbGV3YXJlIC0gdmFsaWQgZW5kcG9pbnQnLCAoZG9uZSkgPT4ge1xuICBjb25zdCBtb2NrQ3R4OiBDb250ZXh0ID0ge1xuICAgIGhlYWRlcnM6IHt9LFxuICAgIHByZWZpeDogJycsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgYm9keToge30sXG4gICAgcmVxdWVzdDoge1xuICAgICAgYm9keTogJ3Rlc3QtYXJncycsXG4gICAgfSxcbiAgfSBhcyBhbnk7XG4gIGxldCBleGVjdXRlZEhhbmRsZXIgPSBmYWxzZTtcbiAgY29uc3QgbW9ja0hhbmRsZXJzID0ge1xuICAgIHRlc3QoYXJncywgY3R4KSB7XG4gICAgICBleGVjdXRlZEhhbmRsZXIgPSB0cnVlO1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvQmUoJ3Rlc3QtYXJncycpO1xuICAgICAgZXhwZWN0KGN0eCkudG9CZShtb2NrQ3R4KTtcbiAgICAgIHJldHVybiAxO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ3N1Y2Nlc3MnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgIH0sXG4gIH0pO1xuXG4gIGNvbnN0IHRhZ3MgPSB7XG4gICAgbmFtZTogJ3Vua25vd25fcm91dGUnLFxuICB9O1xuICBjb25zdCBtaWRkbGV3YXJlID1cbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZSAmJlxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlKFxuICAgICAge1xuICAgICAgICBSb3V0ZVRhZ3M6IHtcbiAgICAgICAgICBmcm9tOiAoKSA9PiB0YWdzLFxuICAgICAgICB9LFxuICAgICAgICBlbWl0dGVyOiBtb2NrRW1pdHRlcixcbiAgICAgICAgaGFuZGxlcnM6IG1vY2tIYW5kbGVycyxcbiAgICAgIH0sXG4gICAgICBtb2NrU2VydmljZVxuICAgICk7XG4gIGlmICghbWlkZGxld2FyZSkge1xuICAgIGRvbmUuZmFpbCgpO1xuICAgIGRvbmUoKTtcbiAgICByZXR1cm47XG4gIH1cblxuICB0cnkge1xuICAgIG1pZGRsZXdhcmUobW9ja0N0eCwgYXN5bmMgKCkgPT4ge1xuICAgICAgZXhwZWN0KGV4ZWN1dGVkSGFuZGxlcikudG9CZShmYWxzZSk7XG4gICAgICBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgIGV4cGVjdCh0YWdzLm5hbWUpLnRvQmUoJ3Rlc3QnKTtcbiAgICAgIGV4cGVjdChleGVjdXRlZEhhbmRsZXIpLnRvQmUodHJ1ZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEpLnRvQmUoMSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIHZhbGlkIGVuZHBvaW50IChjdXN0b20gYXBpIHBhdGgpJywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBwcmVmaXg6ICcnLFxuICAgIHBhdGg6ICcvdGVzdC9hcGkvbG9uZy90ZXN0JyxcbiAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICBib2R5OiB7fSxcbiAgICByZXF1ZXN0OiB7XG4gICAgICBib2R5OiAndGVzdC1hcmdzJyxcbiAgICB9LFxuICB9IGFzIGFueTtcbiAgbGV0IGV4ZWN1dGVkSGFuZGxlciA9IGZhbHNlO1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgdGVzdChhcmdzLCBjdHgpIHtcbiAgICAgIGV4ZWN1dGVkSGFuZGxlciA9IHRydWU7XG4gICAgICBleHBlY3QoYXJncykudG9CZSgndGVzdC1hcmdzJyk7XG4gICAgICBleHBlY3QoY3R4KS50b0JlKG1vY2tDdHgpO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfSxcbiAgfTtcbiAgY29uc3QgbW9ja0VtaXR0ZXIgPSBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgZW1pdCh0eXBlLCBwYXlsb2FkKSB7XG4gICAgICBleHBlY3QodHlwZSkudG9CZSgncnBjOm1ldGhvZCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICAgIHJwY0NvbmZpZzoge1xuICAgICAgICAgIGFwaVBhdGg6ICd0ZXN0L2FwaS9sb25nJyxcbiAgICAgICAgfSxcbiAgICAgIH0sXG4gICAgICBtb2NrU2VydmljZVxuICAgICk7XG4gIGlmICghbWlkZGxld2FyZSkge1xuICAgIGRvbmUuZmFpbCgpO1xuICAgIGRvbmUoKTtcbiAgICByZXR1cm47XG4gIH1cblxuICB0cnkge1xuICAgIG1pZGRsZXdhcmUobW9ja0N0eCwgYXN5bmMgKCkgPT4ge1xuICAgICAgZXhwZWN0KGV4ZWN1dGVkSGFuZGxlcikudG9CZShmYWxzZSk7XG4gICAgICBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgIGV4cGVjdChleGVjdXRlZEhhbmRsZXIpLnRvQmUodHJ1ZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEpLnRvQmUoMSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIHZhbGlkIGVuZHBvaW50IChjdXN0b20gYXBpIHBhdGggaW5jbHVkaW5nIHNsYXNoZXMpJywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBwcmVmaXg6ICcnLFxuICAgIHBhdGg6ICcvdGVzdC9hcGkvbG9uZy90ZXN0JyxcbiAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICBib2R5OiB7fSxcbiAgICByZXF1ZXN0OiB7XG4gICAgICBib2R5OiAndGVzdC1hcmdzJyxcbiAgICB9LFxuICB9IGFzIGFueTtcbiAgbGV0IGV4ZWN1dGVkSGFuZGxlciA9IGZhbHNlO1xuICBjb25zdCBtb2NrSGFuZGxlcnMgPSB7XG4gICAgdGVzdChhcmdzLCBjdHgpIHtcbiAgICAgIGV4ZWN1dGVkSGFuZGxlciA9IHRydWU7XG4gICAgICBleHBlY3QoYXJncykudG9CZSgndGVzdC1hcmdzJyk7XG4gICAgICBleHBlY3QoY3R4KS50b0JlKG1vY2tDdHgpO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfSxcbiAgfTtcbiAgY29uc3QgbW9ja0VtaXR0ZXIgPSBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgZW1pdCh0eXBlLCBwYXlsb2FkKSB7XG4gICAgICBleHBlY3QodHlwZSkudG9CZSgncnBjOm1ldGhvZCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICAgIHJwY0NvbmZpZzoge1xuICAgICAgICAgIGFwaVBhdGg6ICcvdGVzdC8vL2FwaS9sb25nLy8vLycsXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgICAgbW9ja1NlcnZpY2VcbiAgICApO1xuICBpZiAoIW1pZGRsZXdhcmUpIHtcbiAgICBkb25lLmZhaWwoKTtcbiAgICBkb25lKCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBtaWRkbGV3YXJlKG1vY2tDdHgsIGFzeW5jICgpID0+IHtcbiAgICAgIGV4cGVjdChleGVjdXRlZEhhbmRsZXIpLnRvQmUoZmFsc2UpO1xuICAgICAgUHJvbWlzZS5yZXNvbHZlKCk7XG4gICAgfSkudGhlbigoKSA9PiB7XG4gICAgICBleHBlY3QoZXhlY3V0ZWRIYW5kbGVyKS50b0JlKHRydWUpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhKS50b0JlKDEpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5zdGF0dXMpLnRvQmUoJ3N1Y2Nlc3MnKTtcbiAgICAgIGRvbmUoKTtcbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ21pZGRsZXdhcmUgLSB2YWxpZCBlbmRwb2ludCB3aXRoIHJvdXRlIHByZWZpeCcsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgaGVhZGVyczoge30sXG4gICAgcHJlZml4OiAnL2xvbCcsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgYm9keToge30sXG4gICAgcmVxdWVzdDoge1xuICAgICAgYm9keTogJ3Rlc3QtYXJncycsXG4gICAgfSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KGFyZ3MsIGN0eCkge1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvQmUoJ3Rlc3QtYXJncycpO1xuICAgICAgZXhwZWN0KGN0eCkudG9CZShtb2NrQ3R4KTtcbiAgICAgIHJldHVybiAxO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ3N1Y2Nlc3MnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgIH0sXG4gIH0pO1xuXG4gIGNvbnN0IG1pZGRsZXdhcmUgPVxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlICYmXG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUoXG4gICAgICB7XG4gICAgICAgIGVtaXR0ZXI6IG1vY2tFbWl0dGVyLFxuICAgICAgICBoYW5kbGVyczogbW9ja0hhbmRsZXJzLFxuICAgICAgfSxcbiAgICAgIG1vY2tTZXJ2aWNlXG4gICAgKTtcbiAgaWYgKCFtaWRkbGV3YXJlKSB7XG4gICAgZG9uZS5mYWlsKCk7XG4gICAgZG9uZSgpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgbWlkZGxld2FyZShtb2NrQ3R4LCAoKSA9PiBQcm9taXNlLnJlc29sdmUoKSkudGhlbigoKSA9PiB7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEpLnRvQmUoMSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnc3VjY2VzcycpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIHZhbGlkIGVuZHBvaW50IGZhaWx1cmUgd2l0aCBSZXNwb25zZUVycm9yJywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICBoZWFkZXJzOiB7fSxcbiAgICBwcmVmaXg6ICcnLFxuICAgIHBhdGg6ICcvYXBpL3Rlc3QnLFxuICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgIGJvZHk6IHt9LFxuICAgIHJlcXVlc3Q6IHtcbiAgICAgIGJvZHk6ICd0ZXN0LWFyZ3MnLFxuICAgIH0sXG4gICAgbWVtb2l6ZWQ6IG5ldyBNYXAoKSxcbiAgfSBhcyBhbnk7XG4gIGNvbnN0IGUgPSBuZXcgUmVzcG9uc2VFcnJvcignVGVzdCBGYWlsdXJlJyk7XG4gIC8vICRGbG93Rml4TWVcbiAgZS5jb2RlID0gJ0VSUl9DT0RFX1RFU1QnO1xuICAvLyAkRmxvd0ZpeE1lXG4gIGUubWV0YSA9IHtoZWxsbzogJ3dvcmxkJ307XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KGUpO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ2ZhaWx1cmUnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgICAgZXhwZWN0KHBheWxvYWQuZXJyb3IpLnRvQmUoZSk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICB9LFxuICAgICAgbW9ja1NlcnZpY2VcbiAgICApO1xuICBpZiAoIW1pZGRsZXdhcmUpIHtcbiAgICBkb25lLmZhaWwoKTtcbiAgICBkb25lKCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBtaWRkbGV3YXJlKG1vY2tDdHgsICgpID0+IFByb21pc2UucmVzb2x2ZSgpKS50aGVuKCgpID0+IHtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuZGF0YS5tZXNzYWdlKS50b0JlKGUubWVzc2FnZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEuY29kZSkudG9CZShlLmNvZGUpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhLm1ldGEpLnRvQmUoZS5tZXRhKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuc3RhdHVzKS50b0JlKCdmYWlsdXJlJyk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QoT2JqZWN0LmtleXMobW9ja0N0eC5ib2R5KS5sZW5ndGgpLnRvQmUoMik7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QoT2JqZWN0LmtleXMobW9ja0N0eC5ib2R5LmRhdGEpLmxlbmd0aCkudG9CZSgzKTtcbiAgICAgIGRvbmUoKTtcbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ21pZGRsZXdhcmUgLSB2YWxpZCBlbmRwb2ludCBmYWlsdXJlIHdpdGggc3RhbmRhcmQgZXJyb3InLCAoZG9uZSkgPT4ge1xuICBjb25zdCBtb2NrQ3R4OiBDb250ZXh0ID0ge1xuICAgIGhlYWRlcnM6IHt9LFxuICAgIHByZWZpeDogJycsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgYm9keToge30sXG4gICAgcmVxdWVzdDoge1xuICAgICAgYm9keTogJ3Rlc3QtYXJncycsXG4gICAgfSxcbiAgICBtZW1vaXplZDogbmV3IE1hcCgpLFxuICB9IGFzIGFueTtcbiAgY29uc3QgZSA9IG5ldyBFcnJvcignVGVzdCBGYWlsdXJlJyk7XG4gIC8vIEB0cy1pZ25vcmVcbiAgZS5jb2RlID0gJ0VSUl9DT0RFX1RFU1QnO1xuICAvLyBAdHMtaWdub3JlXG4gIGUubWV0YSA9IHtoZWxsbzogJ3dvcmxkJ307XG4gIGNvbnN0IG1vY2tIYW5kbGVycyA9IHtcbiAgICB0ZXN0KCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KGUpO1xuICAgIH0sXG4gIH07XG4gIGNvbnN0IG1vY2tFbWl0dGVyID0gY3JlYXRlTW9ja0VtaXR0ZXIoe1xuICAgIGVtaXQodHlwZSwgcGF5bG9hZCkge1xuICAgICAgZXhwZWN0KHR5cGUpLnRvQmUoJ3JwYzptZXRob2QnKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLm1ldGhvZCkudG9CZSgndGVzdCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQub3JpZ2luKS50b0JlKCdicm93c2VyJyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5zdGF0dXMpLnRvQmUoJ2ZhaWx1cmUnKTtcbiAgICAgIGV4cGVjdCh0eXBlb2YgcGF5bG9hZC50aW1pbmcpLnRvQmUoJ251bWJlcicpO1xuICAgICAgZXhwZWN0KHBheWxvYWQuZXJyb3IpLnRvQmUoZSk7XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWlkZGxld2FyZSA9XG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUgJiZcbiAgICBSUENQbHVnaW4ubWlkZGxld2FyZShcbiAgICAgIHtcbiAgICAgICAgZW1pdHRlcjogbW9ja0VtaXR0ZXIsXG4gICAgICAgIGhhbmRsZXJzOiBtb2NrSGFuZGxlcnMsXG4gICAgICB9LFxuICAgICAgbW9ja1NlcnZpY2VcbiAgICApO1xuICBpZiAoIW1pZGRsZXdhcmUpIHtcbiAgICBkb25lLmZhaWwoKTtcbiAgICBkb25lKCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBtaWRkbGV3YXJlKG1vY2tDdHgsICgpID0+IFByb21pc2UucmVzb2x2ZSgpKS50aGVuKCgpID0+IHtcbiAgICAgIGV4cGVjdChcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICBtb2NrQ3R4LmJvZHkuZGF0YS5tZXNzYWdlXG4gICAgICApLnRvQmUoXG4gICAgICAgICdVbmtub3duRXJyb3IgLSBVc2UgUmVzcG9uc2VFcnJvciBmcm9tIGZ1c2lvbi1wbHVnaW4tcnBjIChvciBmdXNpb24tcGx1Z2luLXJwYy1yZWR1eC1yZWFjdCBpZiB5b3UgYXJlIHVzaW5nIFJlYWN0KSBwYWNrYWdlIGZvciBtb3JlIGRldGFpbGVkIGVycm9yIG1lc3NhZ2VzJ1xuICAgICAgKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChtb2NrQ3R4LmJvZHkuZGF0YS5jb2RlKS50b0JlKHVuZGVmaW5lZCk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LmRhdGEubWV0YSkudG9CZSh1bmRlZmluZWQpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5zdGF0dXMpLnRvQmUoJ2ZhaWx1cmUnKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChPYmplY3Qua2V5cyhtb2NrQ3R4LmJvZHkpLmxlbmd0aCkudG9CZSgyKTtcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGV4cGVjdChPYmplY3Qua2V5cyhtb2NrQ3R4LmJvZHkuZGF0YSkubGVuZ3RoKS50b0JlKDMpO1xuICAgICAgZG9uZSgpO1xuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgZG9uZS5mYWlsKGUpO1xuICB9XG59KTtcblxudGVzdCgndGhyb3dzIHdoZW4gbm90IHBhc3NlZCBjdHgnLCAoZG9uZSkgPT4ge1xuICBjb25zdCBhcHAgPSBjcmVhdGVUZXN0Rml4dHVyZSgpO1xuXG4gIGV4cGVjdC5hc3NlcnRpb25zKDEpO1xuICBnZXRTaW11bGF0b3IoXG4gICAgYXBwLFxuICAgIGNyZWF0ZVBsdWdpbih7XG4gICAgICBkZXBzOiB7cnBjRmFjdG9yeTogUlBDVG9rZW59LFxuICAgICAgbWlkZGxld2FyZTpcbiAgICAgICAgKHtycGNGYWN0b3J5fSkgPT5cbiAgICAgICAgYXN5bmMgKCkgPT4ge1xuICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICBleHBlY3QoKCkgPT4gcnBjRmFjdG9yeS5mcm9tKCkpLnRvVGhyb3coKTtcbiAgICAgICAgICBkb25lKCk7XG4gICAgICAgIH0sXG4gICAgfSlcbiAgKS5yZXF1ZXN0KCcvJyk7XG59KTtcblxudGVzdCgnbWlkZGxld2FyZSAtIGJvZHlwYXJzZXIgb3B0aW9ucyB3aXRoIHZlcnkgc21hbGwganNvbkxpbWl0JywgKGRvbmUpID0+IHtcbiAgY29uc3QgbW9ja0N0eDogQ29udGV4dCA9IHtcbiAgICByZXE6IG1vY2tSZXF1ZXN0KCksXG4gICAgaGVhZGVyczoge30sXG4gICAgcHJlZml4OiAnL2xvbCcsXG4gICAgcGF0aDogJy9hcGkvdGVzdCcsXG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgcmVxdWVzdDoge1xuICAgICAgaXM6IChtaW5lVHlwZXMpID0+XG4gICAgICAgIG1pbmVUeXBlcy5zb21lKChtaW5lVHlwZSkgPT4gbWluZVR5cGUuaW5jbHVkZXMoJ2pzb24nKSksXG4gICAgfSxcbiAgfSBhcyBhbnk7XG4gIGxldCBleGVjdXRlZEhhbmRsZXIgPSBmYWxzZTtcbiAgY29uc3QgbW9ja0hhbmRsZXJzID0ge1xuICAgIHRlc3QoYXJncywgY3R4KSB7XG4gICAgICBleGVjdXRlZEhhbmRsZXIgPSB0cnVlO1xuICAgICAgZXhwZWN0KGFyZ3MpLnRvRXF1YWwoTU9DS19KU09OX1BBUkFNUyk7XG4gICAgICBleHBlY3QoY3R4KS50b0JlKG1vY2tDdHgpO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfSxcbiAgfTtcbiAgY29uc3QgbW9ja0VtaXR0ZXIgPSBjcmVhdGVNb2NrRW1pdHRlcih7XG4gICAgZW1pdCh0eXBlLCBwYXlsb2FkKSB7XG4gICAgICBleHBlY3QodHlwZSkudG9CZSgncnBjOm1ldGhvZCcpO1xuICAgICAgZXhwZWN0KHBheWxvYWQubWV0aG9kKS50b0JlKCd0ZXN0Jyk7XG4gICAgICBleHBlY3QocGF5bG9hZC5vcmlnaW4pLnRvQmUoJ2Jyb3dzZXInKTtcbiAgICAgIGV4cGVjdChwYXlsb2FkLnN0YXR1cykudG9CZSgnZmFpbHVyZScpO1xuICAgICAgZXhwZWN0KHR5cGVvZiBwYXlsb2FkLnRpbWluZykudG9CZSgnbnVtYmVyJyk7XG4gICAgfSxcbiAgfSk7XG4gIGNvbnN0IG1vY2tCb2R5UGFyc2VyT3B0aW9ucyA9IHtqc29uTGltaXQ6ICcxYid9O1xuXG4gIGNvbnN0IG1pZGRsZXdhcmUgPVxuICAgIFJQQ1BsdWdpbi5taWRkbGV3YXJlICYmXG4gICAgUlBDUGx1Z2luLm1pZGRsZXdhcmUoXG4gICAgICB7XG4gICAgICAgIGVtaXR0ZXI6IG1vY2tFbWl0dGVyLFxuICAgICAgICBoYW5kbGVyczogbW9ja0hhbmRsZXJzLFxuICAgICAgICBib2R5UGFyc2VyT3B0aW9uczogbW9ja0JvZHlQYXJzZXJPcHRpb25zLFxuICAgICAgfSxcbiAgICAgIG1vY2tTZXJ2aWNlXG4gICAgKTtcbiAgaWYgKCFtaWRkbGV3YXJlKSB7XG4gICAgZG9uZS5mYWlsKCk7XG4gICAgZG9uZSgpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgbWlkZGxld2FyZShtb2NrQ3R4LCBhc3luYyAoKSA9PiB7XG4gICAgICBleHBlY3QoZXhlY3V0ZWRIYW5kbGVyKS50b0JlKGZhbHNlKTtcbiAgICAgIFByb21pc2UucmVzb2x2ZSgpO1xuICAgIH0pLnRoZW4oKCkgPT4ge1xuICAgICAgZXhwZWN0KGV4ZWN1dGVkSGFuZGxlcikudG9CZShmYWxzZSk7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBleHBlY3QobW9ja0N0eC5ib2R5LnN0YXR1cykudG9CZSgnZmFpbHVyZScpO1xuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgZXhwZWN0KG1vY2tDdHguYm9keS5kYXRhLmNvZGUpLnRvQmUoJ2VudGl0eS50b28ubGFyZ2UnKTtcbiAgICAgIGRvbmUoKTtcbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGRvbmUuZmFpbChlKTtcbiAgfVxufSk7XG5cbnRlc3QoJ21pZGRsZXdhcmUgLSBib2R5cGFyc2VyIG9wdGlvbnMgd2l0aCBkZWZhdWx0IGpzb25MaW1pdCcsIChkb25lKSA9PiB7XG4gIGNvbnN0IG1vY2tDdHg6IENvbnRleHQgPSB7XG4gICAgcmVxOiBtb2NrUmVxdWVzdCgpLFxuICAgIGhlYWRlcnM6IHt9LFxuICAgIHByZWZpeDogJy9sb2wnLFxuICAgIHBhdGg6ICcvYXBpL3Rlc3QnLFxuICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgIHJlcX