UNPKG

unmock-core

Version:

[![npm](https://img.shields.io/npm/v/unmock-core.svg)][npmjs] [![CircleCI](https://circleci.com/gh/unmock/unmock-js.svg?style=svg)](https://circleci.com/gh/unmock/unmock-js) [![codecov](https://codecov.io/gh/unmock/unmock-js/branch/dev/graph/badge.svg)](h

295 lines 19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const jsfRequire = require("@meeshkanml/json-schema-faker"); const jsf = jsfRequire.hasOwnProperty("default") ? jsfRequire.default : jsfRequire; const jsonschema = require("@meeshkanml/jsonschema"); const Array_1 = require("fp-ts/lib/Array"); const Option_1 = require("fp-ts/lib/Option"); const pipeable_1 = require("fp-ts/lib/pipeable"); const full_1 = require("loas3/dist/generated/full"); const lodash_1 = require("lodash"); const monocle_ts_1 = require("monocle-ts"); const openapi_refinements_1 = require("openapi-refinements"); const url = require("whatwg-url"); const interfaces_1 = require("./service/interfaces"); exports.matchUrls = (protocol, host, o) => o.servers ? o.servers .map(m => m.url) .filter(i => new url.URL(i).host === host && new url.URL(i).protocol === `${protocol}:`) : []; const schemaPrism = (oai) => new monocle_ts_1.Prism(s => interfaces_1.isReference(s) ? openapi_refinements_1.getSchemaFromRef(oai, s.$ref.split("/")[3]) : Option_1.some(s), a => a); const makeDefinitionsFromSchema = (o) => o.components && o.components.schemas ? Object.entries(o.components.schemas).reduce((a, b) => (Object.assign(Object.assign({}, a), { [b[0]]: interfaces_1.isReference(b[1]) ? openapi_refinements_1.changeRef(b[1]) : openapi_refinements_1.changeRefs(b[1]) })), {}) : {}; const findRelevantPath = (m, a, p) => a.length === 0 ? p : findRelevantPath(m, a.slice(1), a[0] === m ? p : lodash_1.omit(p, a[0])); exports.getPathItemWithMethod = (m, p) => findRelevantPath(m, openapi_refinements_1.allMethods, p); const getRequiredRequestQueryOrHeaderParametersInternal = (header, t, oai, p) => t .composePrism(new monocle_ts_1.Prism(s => interfaces_1.isReference(s) ? openapi_refinements_1.getParameterFromRef(oai, s.$ref.split("/")[3]) : Option_1.some(s), a => a)) .composePrism(new monocle_ts_1.Prism(s => s.in === (header ? "header" : "query") && s.required ? Option_1.some(s) : Option_1.none, a => a)) .composeGetter(new monocle_ts_1.Getter(i => i)) .getAll(p) .filter(a => a.schema ? !Option_1.isNone(schemaPrism(oai).getOption(a.schema)) : false) .map(b => (Object.assign(Object.assign({}, b), { schema: b.schema ? interfaces_1.isReference(b.schema) ? openapi_refinements_1.changeRef(b.schema) : openapi_refinements_1.changeRefs(b.schema) : { type: "string" } }))); const getRequiredRequestQueryOrHeaderParameters = (header, req, oai, p) => [ ...getRequiredRequestQueryOrHeaderParametersInternal(header, monocle_ts_1.Optional.fromNullableProp()("parameters").composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()), oai, p), ...getRequiredRequestQueryOrHeaderParametersInternal(header, monocle_ts_1.Optional.fromNullableProp()(req.method) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("parameters")) .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()), oai, p), ]; const getRequiredRequestBodySchemas = (req, oai, p) => monocle_ts_1.Optional.fromNullableProp()(req.method) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("requestBody")) .composePrism(new monocle_ts_1.Prism(s => interfaces_1.isReference(s) ? openapi_refinements_1.getRequestBodyFromRef(oai, s.$ref.split("/")[3]) : Option_1.some(s), a => a)) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("content")) .composeIso(openapi_refinements_1.objectToArray()) .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()) .composeLens(openapi_refinements_1.valueLens()) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("schema")) .composePrism(schemaPrism(oai)) .composeGetter(new monocle_ts_1.Getter(i => i)) .getAll(p); const keepMethodIfRequiredRequestBodyIsPresent = (req, oai) => (p) => !p[req.method] ? p : pipeable_1.pipe(getRequiredRequestBodySchemas(req, oai, p).filter(i => !jsonschema.validate(req.bodyAsJson, Object.assign(Object.assign({}, openapi_refinements_1.changeRefs(i)), { definitions: makeDefinitionsFromSchema(oai) })).valid).length === 0 ? Option_1.some(p) : Option_1.none, Option_1.fold(() => lodash_1.omit(p, req.method), a => a)); const keepMethodIfRequiredHeaderParametersArePresent = (req, oai) => (p) => keepMethodIfRequiredQueryOrHeaderParametersArePresent(true, req, oai, p); const keepMethodIfRequiredQueryPrametersArePresent = (req, oai) => (p) => keepMethodIfRequiredQueryOrHeaderParametersArePresent(false, req, oai, p); const keepMethodIfRequiredQueryOrHeaderParametersArePresent = (header, req, oai, p) => !p[req.method] ? p : pipeable_1.pipe(getRequiredRequestQueryOrHeaderParameters(header, req, oai, p), properties => jsonschema.validate(header ? req.headers || {} : req.query, { type: "object", properties: properties.reduce((a, b) => (Object.assign(Object.assign({}, a), { [b.name]: b.schema })), {}), required: properties.filter(i => i.required).map(i => i.name), additionalProperties: true, definitions: makeDefinitionsFromSchema(oai), }).valid ? Option_1.some(p) : Option_1.none, Option_1.fold(() => lodash_1.omit(p, req.method), a => a)); const maybeAddStringSchema = (s) => (s.length === 0 ? [{ type: "string" }] : s); const discernName = (o, n) => Option_1.isNone(o) || (o.value.name === n && o.value.in === "path") ? o : Option_1.none; const internalGetParameter = (t, vname, pathItem, oas) => t .composePrism(new monocle_ts_1.Prism(i => interfaces_1.isReference(i) ? discernName(openapi_refinements_1.getParameterFromRef(oas, exports.refName(i)), vname) : discernName(Option_1.some(i), vname), a => a)) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("schema")) .composeGetter(exports.identityGetter()) .getAll(pathItem); const pathItemPathParameter = (vname, pathItem, oas) => internalGetParameter(monocle_ts_1.Optional.fromNullableProp()("parameters").composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()), vname, pathItem, oas); const operationPathParameter = (vname, pathItem, operation, oas) => internalGetParameter(monocle_ts_1.Optional.fromNullableProp()(operation) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("parameters")) .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()), vname, pathItem, oas); const getMatchingParameters = (vname, pathItem, operation, oas) => maybeAddStringSchema([ ...pathItemPathParameter(vname, pathItem, oas), ...operationPathParameter(vname, pathItem, operation, oas), ]); const maybeJson = (maybe) => { try { return JSON.parse(maybe); } catch (_a) { return maybe; } }; const pathParameterMatch = (part, vname, pathItem, operation, oas) => getMatchingParameters(vname, pathItem, operation, oas).filter(i => ![...new Set([part, maybeJson(part)])].reduce((a, b) => a || jsonschema.validate(b, Object.assign(Object.assign({}, i), { definitions: makeDefinitionsFromSchema(oas) })).valid, false)).length === 0; const pathParamRegex = new RegExp(/^\{[^}]+\}/gi); exports.matchesInternal = (path, pathItemKey, pathItem, operation, o) => path.length === pathItemKey.length && (path.length === 0 || ((path[0] === pathItemKey[0] || (pathItemKey[0].match(pathParamRegex) !== null && pathParameterMatch(path[0], pathItemKey[0].slice(1, -1), pathItem, operation, o))) && exports.matchesInternal(path.slice(1), pathItemKey.slice(1), pathItem, operation, o))); exports.matches = (path, pathItemKey, pathItem, method, oas) => exports.matchesInternal(path.split("/").filter(i => i !== ""), pathItemKey.split("/").filter(i => i !== ""), pathItem, method, oas); exports.refName = (r) => r.$ref.split("/")[3]; exports.firstElementOptional = () => new monocle_ts_1.Optional(s => (s.length > 0 ? Option_1.some(s[0]) : Option_1.none), a => s => [a, ...s.slice(1)]); exports.keyLens = () => new monocle_ts_1.Lens(a => a[0], a => s => [a, s[1]]); const getFirstMethodInternal2 = (p, n, m, o) => o ? Option_1.some([n, o]) : getFirstMethodInternal(m, p); const getFirstMethodInternal = (m, p) => m.length === 0 ? Option_1.none : getFirstMethodInternal2(p, m[0], m.slice(1), p[m[0]]); exports.getFirstMethod = (p) => getFirstMethodInternal(openapi_refinements_1.allMethods, p); exports.operationOptional = new monocle_ts_1.Optional(a => exports.getFirstMethod(a), a => s => (Object.assign(Object.assign({}, s), { [a[0]]: a[1] }))); exports.getHeaderFromRef = (o, d) => openapi_refinements_1.getComponentFromRef(o, d, a => (a.headers ? Option_1.some(a.headers) : Option_1.none), exports.internalGetHeaderFromRef); exports.internalGetHeaderFromRef = openapi_refinements_1.internalGetComponent(exports.getHeaderFromRef); exports.useIfHeaderLastMile = (p, r) => Option_1.isNone(r) ? Option_1.none : Option_1.some([p.name, r.value || { type: "string" }]); exports.useIfHeader = (o, p) => p.in !== "header" ? Option_1.none : exports.useIfHeaderLastMile(p, p.schema ? interfaces_1.isReference(p.schema) ? openapi_refinements_1.getSchemaFromRef(o, exports.refName(p.schema)) : Option_1.some(p.schema) : Option_1.some({ type: "string" })); exports.identityGetter = () => new monocle_ts_1.Getter(i => i); exports.parameterSchema = (o) => new monocle_ts_1.Optional(a => exports.useIfHeader(o, a), a => s => (Object.assign(Object.assign({}, s), { name: a[0], schema: a[1] }))); const cutPath = (paths, path) => paths.length === 0 ? path : path.slice(0, paths[0].length) === paths[0] ? path.slice(paths[0].length) : cutPath(paths.slice(1), path); const removeTrailingSlash = (s) => s.length === 0 ? s : s.slice(-1) === "/" ? s.slice(0, -1) : s; exports.truncatePath = (path, o, i) => cutPath(exports.matchUrls(i.protocol, i.host, o).map(u => removeTrailingSlash(new url.URL(u).pathname)), path); exports.matcher = (req, r) => openapi_refinements_1.objectToArray() .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()) .composeLens(openapi_refinements_1.valueLens()) .modify(oai => monocle_ts_1.Optional.fromNullableProp()("paths") .composeIso(openapi_refinements_1.objectToArray()) .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()) .composeLens(openapi_refinements_1.valueLens()) .modify(pathItem => pipeable_1.pipe(exports.getPathItemWithMethod(req.method, pathItem), keepMethodIfRequiredHeaderParametersArePresent(req, oai), keepMethodIfRequiredQueryPrametersArePresent(req, oai), keepMethodIfRequiredRequestBodyIsPresent(req, oai)))(Object.assign(Object.assign({}, oai), (oai.paths ? { paths: Object.entries(oai.paths).reduce((i, [n, o]) => (Object.assign(Object.assign({}, i), (exports.matches(exports.truncatePath(req.pathname, oai, req), n, o, req.method, oai) ? { [n]: o } : {}))), {}), } : {}))))(Object.entries(r).reduce((i, [n, o]) => (Object.assign(Object.assign({}, i), (exports.matchUrls(req.protocol, req.host, o).length > 0 ? { [n]: o } : {}))), {})); exports.hoistTransformer = (f, name) => (req, r) => openapi_refinements_1.objectToArray() .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)().filter(([inName, o]) => inName === name && exports.matchUrls(req.protocol, req.host, o).length > 0)) .composeLens(openapi_refinements_1.valueLens()) .modify(oai => f(Object.assign(Object.assign({}, req), { path: exports.truncatePath(req.path, oai, req), pathname: exports.truncatePath(req.pathname, oai, req) }), oai))(r); const toSchemas = openapi_refinements_1.objectToArray().composeOptional(exports.firstElementOptional()); const toSchema = toSchemas.composeLens(openapi_refinements_1.valueLens()); const drillDownToOperation = (schemaRecord) => toSchema .composeOptional(monocle_ts_1.Optional.fromNullableProp()("paths")) .composeIso(openapi_refinements_1.objectToArray()) .composeOptional(exports.firstElementOptional()) .composeLens(openapi_refinements_1.valueLens()) .composeOptional(exports.operationOptional) .composeLens(openapi_refinements_1.valueLens()) .getOption(schemaRecord); const headersFromOperation = (schema, operation) => monocle_ts_1.Optional.fromNullableProp()("parameters") .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()) .composePrism(new monocle_ts_1.Prism(s => full_1.isParameter(s) ? Option_1.some(s) : interfaces_1.isReference(s) ? openapi_refinements_1.getParameterFromRef(schema, exports.refName(s)) : Option_1.none, a => a)) .composeOptional(exports.parameterSchema(schema)) .composeGetter(exports.identityGetter()) .getAll(operation); const makeLensToResponseStartingFromOperation = (schema, code) => monocle_ts_1.Optional.fromNullableProp()("responses") .composeOptional(monocle_ts_1.Optional.fromNullableProp()(code)) .composePrism(new monocle_ts_1.Prism(s => full_1.isResponse(s) ? Option_1.some(s) : interfaces_1.isReference(s) ? openapi_refinements_1.getResponseFromRef(schema, exports.refName(s)) : Option_1.none, a => a)); const stringHeader = (n, o) => Option_1.isNone(o) ? Option_1.none : Option_1.some([n, o.value]); const headersFromResponse = (schema, operation, code) => makeLensToResponseStartingFromOperation(schema, code) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("headers")) .composeIso(openapi_refinements_1.objectToArray()) .composeTraversal(monocle_ts_1.fromTraversable(Array_1.array)()) .composePrism(new monocle_ts_1.Prism(a => interfaces_1.isReference(a[1]) ? stringHeader(a[0], exports.getHeaderFromRef(schema, exports.refName(a[1]))) : Option_1.some([a[0], a[1]]), a => a)) .composeIso(new monocle_ts_1.Iso(a => (Object.assign(Object.assign({}, a[1]), { name: a[0], in: "header" })), a => [a.name, a])) .composeOptional(exports.parameterSchema(schema)) .composeGetter(exports.identityGetter()) .getAll(operation); const lensToMimeType = (schema, code) => makeLensToResponseStartingFromOperation(schema, code) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("content")) .composeIso(openapi_refinements_1.objectToArray()) .composeOptional(exports.firstElementOptional()); const bodyFromResponse = (schema, operation, code) => lensToMimeType(schema, code) .composeLens(openapi_refinements_1.valueLens()) .composeOptional(monocle_ts_1.Optional.fromNullableProp()("schema")) .getOption(operation); const mimeTypeFromResponse = (schema, operation, code) => lensToMimeType(schema, code) .composeLens(exports.keyLens()) .getOption(operation); function responseCreatorFactory({ listeners = [], options, fakerOptions, store, }) { return (req) => { const transformers = [ exports.matcher, ...Object.values(store.cores).map(core => exports.hoistTransformer(core.transformer, core.name)), ]; const schemas = Object.entries(store.cores).reduce((a, [n, x]) => (Object.assign(Object.assign({}, a), { [n]: x.schema })), {}); const schemaRecord = transformers.reduce((a, b) => b(req, a), schemas); const schema = toSchema.getOption(schemaRecord); const operation = drillDownToOperation(schemaRecord); if (Option_1.isNone(operation) || Option_1.isNone(schema)) { throw Error(`Cannot find a matcher for this request: ${JSON.stringify(req, null, 2)}`); } const codes = Object.keys(operation.value.responses); const code = codes[Math.floor(Math.random() * codes.length)]; const statusCode = code === "default" ? 500 : +code; const definitions = makeDefinitionsFromSchema(schema.value); const operationLevelHeaders = headersFromOperation(schema.value, operation.value); const responseLevelHeaders = headersFromResponse(schema.value, operation.value, code); const mimeType = mimeTypeFromResponse(schema.value, operation.value, code); const bodySchema = bodyFromResponse(schema.value, operation.value, code); const headerProperties = Object.assign({}, [...operationLevelHeaders, ...responseLevelHeaders].reduce((a, b) => (Object.assign(Object.assign({}, a), { [b[0]]: b[1] })), {})); if (!options.randomize()) { fakerOptions.randomNumberGenerator.restore(); } const res = generateMockFromTemplate({ fakerOptions, statusCode, mimeType: Option_1.isNone(mimeType) ? undefined : mimeType.value, headerSchema: { definitions, type: "object", properties: headerProperties, required: Object.keys(headerProperties), }, bodySchema: Option_1.isNone(bodySchema) ? undefined : Object.assign({ definitions }, (interfaces_1.isReference(bodySchema.value) ? openapi_refinements_1.changeRef(bodySchema.value) : openapi_refinements_1.changeRefs(bodySchema.value))), }); const serviceName = toSchemas .composeLens(exports.keyLens()) .getOption(schemaRecord); if (!Option_1.isNone(serviceName) && store.cores[serviceName.value]) { store.cores[serviceName.value].track({ req, res }); } listeners.forEach((listener) => listener.notify({ req, res })); jsf.reset(); return res; }; } exports.responseCreatorFactory = responseCreatorFactory; const isNonEnumString = (s) => s.type === "string" && !s.enum; const chop = (s) => s.substring(1, s.length - 2); const generateMockFromTemplate = ({ fakerOptions, statusCode, headerSchema, bodySchema, mimeType, }) => { jsf.extend("faker", () => require("faker")); jsf.option("optionalsProbability", fakerOptions.optionalsProbability); jsf.option("fixedProbabilities", fakerOptions.optionalsProbability === 1); jsf.option("failOnInvalidFormat", false); jsf.option("useDefaultValue", false); jsf.option("random", () => fakerOptions.randomNumberGenerator.get()); const bodyAsJson = bodySchema && mimeType && mimeType.indexOf("application/json") !== -1 ? jsf.generate(bodySchema) : undefined; const body = bodyAsJson ? JSON.stringify(bodyAsJson) : bodySchema && mimeType && isNonEnumString(bodySchema) ? chop(jsf.generate(bodySchema)) : bodySchema && mimeType ? jsf.generate(bodySchema) : undefined; jsf.option("useDefaultValue", true); const resHeaders = headerSchema ? jsf.generate(headerSchema) : undefined; jsf.option("useDefaultValue", false); if (mimeType) { resHeaders["Content-Type"] = mimeType; } return { body, bodyAsJson, headers: resHeaders, statusCode, }; }; //# sourceMappingURL=generator.js.map