counterfact
Version:
a library for building a fake REST API for testing
118 lines (117 loc) • 4.59 kB
JavaScript
import { JSONSchemaFaker } from "json-schema-faker";
import { jsonToXml } from "./json-to-xml.js";
JSONSchemaFaker.option("useExamplesValue", true);
JSONSchemaFaker.option("minItems", 0);
JSONSchemaFaker.option("maxItems", 20);
JSONSchemaFaker.option("failOnInvalidTypes", false);
JSONSchemaFaker.option("failOnInvalidFormat", false);
JSONSchemaFaker.option("fillProperties", false);
function convertToXmlIfNecessary(type, body, schema) {
if (type.endsWith("/xml")) {
return jsonToXml(body, schema, "root");
}
return body;
}
function oneOf(items) {
if (Array.isArray(items)) {
return items[Math.floor(Math.random() * items.length)];
}
return oneOf(Object.values(items));
}
function unknownStatusCodeResponse(statusCode) {
return {
content: [
{
body: `The Open API document does not specify a response for status code ${statusCode ?? '""'}`,
type: "text/plain",
},
],
status: 500,
};
}
export function createResponseBuilder(operation, config) {
return new Proxy({}, {
get: (target, statusCode) => ({
header(name, value) {
return {
...this,
headers: {
...this.headers,
[name]: value,
},
};
},
html(body) {
return this.match("text/html", body);
},
json(body) {
return this.match("application/json", body)
.match("text/json", body)
.match("text/x-json", body)
.match("application/xml", body)
.match("text/xml", body);
},
match(contentType, body) {
return {
...this,
content: [
...(this.content ?? []).filter((response) => response.type !== contentType),
{
body: convertToXmlIfNecessary(contentType, body, operation.responses[this.status ?? "default"]?.content?.[contentType]?.schema),
type: contentType,
},
],
};
},
random() {
if (config?.alwaysFakeOptionals) {
JSONSchemaFaker.option("alwaysFakeOptionals", true);
JSONSchemaFaker.option("fixedProbabilities", true);
JSONSchemaFaker.option("optionalsProbability", 1.0);
}
if (operation.produces) {
return this.randomLegacy();
}
const response = operation.responses[this.status ?? "default"] ??
operation.responses.default;
if (response?.content === undefined) {
return unknownStatusCodeResponse(this.status);
}
const { content } = response;
return {
...this,
content: Object.keys(content).map((type) => ({
body: convertToXmlIfNecessary(type, content[type]?.examples
? oneOf(Object.values(content[type]?.examples ?? []).map((example) => example.value))
: JSONSchemaFaker.generate(content[type]?.schema ?? { type: "object" }), content[type]?.schema),
type,
})),
};
},
randomLegacy() {
const response = operation.responses[this.status ?? "default"] ??
operation.responses.default;
if (response === undefined) {
return unknownStatusCodeResponse(this.status);
}
const body = response.examples
? oneOf(response.examples)
: JSONSchemaFaker.generate(response.schema ?? { type: "object" });
return {
...this,
content: operation.produces?.map((type) => ({
body,
type,
})),
};
},
status: Number.parseInt(statusCode, 10),
text(body) {
return this.match("text/plain", body);
},
xml(body) {
return this.match("application/xml", body).match("text/xml", body);
},
}),
});
}