UNPKG

@n1k1t/mock-server

Version:

The ultimate toolkit to intercept, transform, and simulate HTTP/WS traffic with type-safe expectations

252 lines 12.4 kB
"use strict"; 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