unmock-core
Version:
[][npmjs] [](https://circleci.com/gh/unmock/unmock-js) [](h
295 lines • 19 kB
JavaScript
;
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