@n1k1t/mock-server
Version:
The ultimate toolkit to intercept, transform, and simulate HTTP/WS traffic with type-safe expectations
252 lines • 12.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Executor = void 0;
const deepmerge_1 = __importDefault(require("deepmerge"));
const rfdc_1 = __importDefault(require("rfdc"));
const lodash_1 = __importDefault(require("lodash"));
const node_gzip_1 = require("node-gzip");
const rxjs_1 = require("rxjs");
const utils_1 = require("../../utils");
const utils_2 = require("../../../utils");
const errors_1 = require("./errors");
const message_1 = require("../message");
const logger_1 = require("../../../logger");
const context_1 = require("../context");
__exportStar(require("./errors"), exports);
const clone = (0, rfdc_1.default)();
const logger = logger_1.Logger.build('Executor');
class Executor {
/** Matches expectation */
async match(context) {
return context.provider.storages.expectations.match(context.snapshot);
}
/** Prepares context right after expectation was manipulated */
async prepare(context) {
return null;
}
/** Uses to handle whole request */
async exec(context, options) {
context.streams.incoming.subscribe({
error: () => null,
next: (message) => context.snapshot.messages.push(message.clone({ deep: true }).redirect('incoming')),
});
context.streams.outgoing.subscribe({
error: () => null,
next: (message) => context.snapshot.messages.push(message.clone().redirect('outgoing')),
});
const expectation = options?.expectation ? options.expectation : await this.match(context).catch((error) => {
logger.error('Got error while execution [matchExpectation] method', error?.stack ?? error);
return null;
});
if (!expectation) {
if (!context.outgoing) {
context.provider.storages.history.unregister(context.history);
return context;
}
if (context.history?.is('registered')) {
context.history.switch('pending');
}
return context;
}
if (expectation.defaults?.state) {
context.snapshot.state = (0, deepmerge_1.default)(expectation.defaults.state, context.snapshot.state, {
arrayMerge: (target, source) => source,
});
}
context.assign({
snapshot: await expectation.request.manipulate(context.snapshot),
expectation: expectation.increaseExecutionsCounter(),
});
await this
.prepare(context)
.catch((error) => logger.error('Got error while execution [prepare] method', error?.stack ?? error));
context.provider.server.exchanges.io.publish('expectation:updated', expectation.toPlain());
logger.info('Expectation has matched as', `"${expectation.name}" [${expectation.id}]`);
if (context.history?.is('registered')) {
context.history
.switch('pending')
.actualize(context.snapshot)
.assign({ expectation: context.expectation });
context.provider.server.exchanges.io.publish('history:added', context.history.toPlain());
context.provider.server.services.metrics.register('rate', { count: 1 });
}
if (!context.is(['handling'])) {
return context;
}
if (context.snapshot.incoming.delay) {
logger.info(`Has delayed over [${context.snapshot.incoming.delay}ms]`);
await (0, utils_2.wait)(context.snapshot.incoming.delay);
}
if (context.snapshot.incoming.error) {
logger.info(`Has destroyed using [${context.snapshot.incoming.error}]`);
context.snapshot.assign({ error: { code: context.snapshot.incoming.error, isManual: true } });
throw errors_1.ExecutorManualError.build(context.snapshot.incoming.error);
}
if (expectation.schema.forward) {
const forwarded = await this.handleForwarding(context).catch((error) => {
logger.error('Got error while execution [handleForwarding] method', error?.stack ?? error);
return null;
});
if (forwarded) {
context.snapshot.assign({
outgoing: forwarded.outgoing ?? context.snapshot.outgoing,
forwarded: {
schema: forwarded.schema,
messages: forwarded.messages,
incoming: Object.assign(clone(lodash_1.default.omit(forwarded.incoming, ['stream'])), lodash_1.default.pick(forwarded.incoming, ['stream'])),
...(forwarded.outgoing && {
outgoing: Object.assign(clone(lodash_1.default.omit(forwarded.outgoing, ['stream'])), lodash_1.default.pick(forwarded.outgoing, ['stream'])),
}),
}
});
}
if (context.snapshot.forwarded && context.history?.is('pending')) {
context.history.actualize(context.snapshot);
context.provider.server.exchanges.io.publish('history:updated', context.history.toPlain());
}
}
if (!context.is(['handling'])) {
return context;
}
const outgoing = await this.handleReplying(context).catch((error) => {
logger.error('Got error while execution [handleReplying] method', error?.stack ?? error);
return null;
});
return outgoing ? context.assign({ outgoing }) : context;
}
async handleForwarding(context) {
if (!context.expectation?.schema.forward) {
return null;
}
const snapshot = context.snapshot.assign({ cache: context.compileCacheConfiguration() });
const schema = snapshot.overrides?.forward
? (0, deepmerge_1.default)(context.expectation.schema.forward, snapshot.overrides.forward)
: context.expectation.schema.forward;
if (schema.isEnabled === false) {
return null;
}
if (snapshot.cache.isEnabled) {
const zipped = await context.provider.server.databases.redis.get(snapshot.cache.key).catch((error) => {
logger.error('Got error while redis get', error?.stack ?? error);
return null;
});
const unzipped = zipped
? await (0, node_gzip_1.ungzip)(Buffer.from(zipped, 'base64')).catch((error) => {
logger.error('Got error while cache unzip', error?.stack ?? error);
return null;
})
: null;
const cache = unzipped
? (0, utils_2.parseJsonSafe)(unzipped.toString())
: null;
if (cache?.status === 'OK') {
logger.info(`Got cache [${snapshot.cache.key}]`);
const messages = cache.result.messages.map((nested) => {
const message = message_1.RequestMessage.build(nested);
if (nested.raw.data) {
message.raw.data = Buffer.from(nested.raw.data, 'base64');
}
return message;
});
const forwarded = {
schema,
messages,
incoming: snapshot.incoming,
outgoing: Object.assign(lodash_1.default.omit(cache.result.outgoing, ['raw']), {
...(cache.result.outgoing.dataRaw && (0, utils_1.parsePayload)(Buffer.from(cache.result.outgoing.dataRaw, 'base64'))),
stream: (0, rxjs_1.from)(messages),
raw: {
...(cache.result.outgoing.raw?.data && {
data: Buffer.from(cache.result.outgoing.raw.data, 'base64'),
}),
...(cache.result.outgoing.dataRaw && {
data: Buffer.from(cache.result.outgoing.dataRaw, 'base64'),
}),
},
}),
};
snapshot.cache.hasRead = true;
return forwarded;
}
}
const parsed = (0, utils_1.parsePayload)(snapshot.incoming.data);
const raw = snapshot.incoming.data instanceof Buffer ? snapshot.incoming.data : null;
snapshot.incoming.type = (0, context_1.definePayloadType)(snapshot.incoming.headers) ?? parsed.type;
snapshot.incoming.data = parsed.data;
snapshot.incoming.raw.data = raw ?? (0, utils_1.serializePayload)(snapshot.incoming.type, snapshot.incoming.data);
const forwarded = await this
.forward(context, snapshot.incoming, schema)
.catch((error) => {
logger.error('Got error while execution [forward] method', error?.stack ?? error);
return null;
});
if (forwarded) {
forwarded.messages = [];
snapshot.incoming.stream?.subscribe({
error: () => null,
next: (message) => forwarded.messages.push(message.clone().redirect('incoming')),
});
forwarded.outgoing?.stream?.subscribe({
error: () => null,
next: (message) => forwarded.messages.push(message.clone({ deep: true }).redirect('outgoing')),
});
}
return forwarded;
}
async handleReplying(context) {
const snapshot = context.expectation?.response
? await context.expectation.response.manipulate(context.snapshot)
: context.snapshot;
const parsed = (0, utils_1.parsePayload)(snapshot.outgoing.data);
const raw = snapshot.outgoing.data instanceof Buffer ? snapshot.outgoing.data : null;
snapshot.outgoing.type = (0, context_1.definePayloadType)(snapshot.outgoing.headers) ?? parsed.type;
snapshot.outgoing.data = parsed.data;
snapshot.outgoing.raw.data = raw ?? (0, utils_1.serializePayload)(snapshot.outgoing.type, snapshot.outgoing.data);
const outgoing = await this
.reply(context, snapshot.outgoing)
.catch((error) => {
logger.error('Got error while execution [reply] method', error?.stack ?? error);
return null;
});
const shouldBeCached = snapshot.cache.isEnabled &&
snapshot.forwarded?.outgoing &&
snapshot.cache.ttl &&
!snapshot.cache.hasRead &&
typeof snapshot.cache.key === 'string';
if (shouldBeCached) {
const serialized = (0, utils_1.serializePayload)('json', snapshot.toCache());
const zipped = await (0, node_gzip_1.gzip)(serialized).catch((error) => {
logger.error('Got error while zip payload', error?.stack ?? error);
return null;
});
if (zipped) {
await context.provider.server.databases.redis.setex(snapshot.cache.key, snapshot.cache.ttl, zipped.toString('base64'))
.then(() => {
logger.info(`Wrote cache [${snapshot.cache.key}] for [${snapshot.cache.ttl}] seconds`);
snapshot.cache.hasWritten = true;
})
.catch((error) => logger.error('Got error while redis set', error?.stack ?? error));
}
}
return outgoing;
}
}
exports.Executor = Executor;
//# sourceMappingURL=index.js.map