@agreed/core
Version:
agreed is a mock server and test client, agreed will be helper for Consumer Driven Contract
267 lines (237 loc) • 8.28 kB
JavaScript
;
const isInclude = require("./isInclude");
const url = require("url");
const logger = require("../utils/logger");
const { hasTemplate } = require("../template/hasTemplate");
const tmplBind = require("../template/bind");
const nullishStrings = ["undefined", "null", ""];
class Checker {
static validRequest(result, request, req, debug) {
if (!result || !result.error) {
return true;
}
if (debug) {
logger.log(result.error);
logger.log("agreed request", request);
logger.log("actual request", req);
}
return false;
}
static request(request, req, options) {
const parsedUrl = url.parse(req.url);
let similarity = 0;
let result = Checker.method(request.method, req.method);
similarity += result.similarity;
if (!Checker.validRequest(result, request, req, options.debug)) {
return { result: false, similarity, error: result.error };
}
result = Checker.url(request.pathToRegexp, parsedUrl.pathname, options);
similarity += result.similarity;
if (!Checker.validRequest(result, request, req, options.debug)) {
return { result: false, similarity, error: result.error };
}
result = Checker.headers(request.headers, req.headers, options);
similarity += result.similarity;
if (!Checker.validRequest(result, request, req, options.debug)) {
return { result: false, similarity, error: result.error };
}
result = Checker.body(request.body, req.body, options);
similarity += result.similarity;
if (!Checker.validRequest(result, request, req, options.debug)) {
return { result: false, similarity, error: result.error };
}
// query
// The value of the given query character.
result = Checker.query(request.query, req.query, options);
similarity += result.similarity;
if (
!Checker.validRequest(result, request, req, options.debug) ||
(result.similarity == 0 && options.enablePreferQuery)
) {
return { result: false, similarity, error: result.error };
}
return { result: true, similarity };
}
static method(entryMethod, reqMethod) {
const result = { similarity: 1 };
if (entryMethod.toLowerCase() !== reqMethod.toLowerCase()) {
result.type = "METHOD";
result.error = `Does not match METHOD, expect ${entryMethod}, but ${reqMethod}.`;
result.similarity = 0;
}
return result;
}
static url(entryUrl, reqUrl, options) {
const result = { similarity: 1 };
const match = entryUrl.exec(reqUrl);
const { pathToRegexpKeys, values = {} } = options;
if (!match) {
result.type = "URL";
result.error = `Does not match URL, expect ${entryUrl}, but ${reqUrl}.`;
result.similarity = 0;
return result;
}
const paths = {};
pathToRegexpKeys.forEach((pathKey, index) => {
paths[pathKey.name] = match[index + 1];
});
let valuesSimilarity = 1;
if (pathToRegexpKeys.length !== 0) {
let matched = 0;
Object.keys(paths).forEach((k) => {
if (paths[k] === values[k] + "") {
matched++;
}
});
valuesSimilarity = matched / pathToRegexpKeys.length;
}
const nullish = Checker.checkNullish(paths);
const nullishError = nullish.error;
const nullishSimilarity = nullish.similarity;
result.type = "URL";
result.error = nullishError;
result.similarity = nullishSimilarity;
if (result.similarity >= 1) {
result.similarity += valuesSimilarity;
}
return result;
}
static headers(entryHeaders, reqHeaders, options) {
const result = { similarity: 1 };
if (!entryHeaders) return result;
if (!isInclude(entryHeaders, reqHeaders)) {
result.type = "HEADERS";
result.error = `Does not include header, expect ${JSON.stringify(
entryHeaders
)} but ${JSON.stringify(reqHeaders)}`;
result.similarity = 0;
return result;
}
if (!options.skipCheckHeaderValueNullable) {
const nullish = Checker.checkNullish(reqHeaders);
const nullishError = nullish.error;
const nullishSimilarity = nullish.similarity;
if (nullish) {
result.type = "HEADERS";
result.error = nullishError;
result.similarity = nullishSimilarity;
return result;
}
}
return result;
}
static body(entryBody, reqBody, options) {
const result = { similarity: 1 };
if (!entryBody) return result;
if (!isInclude(entryBody, reqBody)) {
result.type = "BODY";
result.error = `Does not include body, expect ${JSON.stringify(
entryBody
)} but ${JSON.stringify(reqBody)}`;
result.similarity = 0;
}
return result;
}
static query(entryQuery, reqQuery, options) {
const result = { similarity: 1 };
if (!entryQuery) return result;
if (!isInclude(entryQuery, reqQuery)) {
result.type = "QUERY";
result.error = `Does not include query, expect ${JSON.stringify(
entryQuery
)} but ${JSON.stringify(reqQuery)}`;
result.similarity = 0;
}
if (options.enablePreferQuery) {
// e.g.)
// entryQuery = { q: "{:someQueryStrings }" }
// reqQuery = { q: "bar" }
const existEntryQuery = Object.keys(entryQuery).length > 0;
if (existEntryQuery)
return this.queryWhenCarefulCheckRequired(
reqQuery,
entryQuery,
options
);
}
return result;
}
static queryWhenCarefulCheckRequired(reqQuery, entryQuery, options) {
const result = { similarity: 1 };
const isMatch = Object.keys(reqQuery).every((key) => {
if (entryQuery[key] != undefined) {
const tmpl = hasTemplate(entryQuery[key]);
const hasTmpl = tmpl !== null && tmpl.length > 0;
const reqValue = reqQuery[key];
if (hasTmpl) {
// e.g.) {":someQueryStrings"} => someQueryStrings
const normalizedKey = entryQuery[key].replace(/\{|\}|\:/g, "");
return this.matchQueryWhenHasTmpl(
reqQuery,
entryQuery,
options,
reqValue,
normalizedKey
);
} else {
// e.g.) entryQuery = { foo: "foo", bar: "bar" }
// e.g.) entryParameters = { id: "yosuke" }
const entryQueryValue = entryQuery[key];
return reqValue === entryQueryValue;
}
} else {
// An undefined query string may have been passed
return false;
}
});
result.similarity = isMatch ? 1 : 0;
return result;
}
static matchQueryWhenHasTmpl(
reqQuery,
entryQuery,
options,
reqValue,
normalizedKey
) {
// e.g.) { "someQueryStrings": "bar" }
const calcEntryParameters = tmplBind(entryQuery, reqQuery);
const calcEntryQueryValue = calcEntryParameters[normalizedKey];
// e.g.) { id: "yosuke", someQueryStrings: "bar" }
// include path parameters
const entryParameters = options.values;
// Consideration when the query string is given in the path
//
// e.g.)
// path = "/test/arrayreqs/agreed/values?year_months[]=201708&year_months[]=201709&year_months[]=201810"
// options = { pathToRegexpKeys: [], values: undefined, debug: undefined }
if (entryParameters !== undefined) {
const entryQueryValue = entryParameters[normalizedKey];
// By definition, it can be defined as a numerical value, but in reality, only a numerical value as a character string can be passed.
return (
calcEntryQueryValue == String(entryQueryValue) &&
reqValue === String(entryQueryValue)
);
} else {
return reqValue == calcEntryQueryValue;
}
}
static checkNullish(obj = {}) {
const result = { similarity: 1 };
const keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
const key = keys[i];
const realValue = obj[key];
if (typeof realValue === "object" && realValue) {
return Checker.checkNullish(realValue);
}
if (nullishStrings.indexOf(realValue) >= 0) {
result.error = `Request value has nullish strings ${realValue} in ${key}`;
result.similarity = 0.5;
return result;
}
}
return result;
}
}
module.exports = Checker;