Initial commit

This commit is contained in:
Yaser
2026-08-13 20:00:11 +03:30
commit b7d0fcf68b
2029 changed files with 126991 additions and 0 deletions

357
mocks/mockserver.js Normal file
View File

@@ -0,0 +1,357 @@
const fs = require('fs');
const path = require('path');
const colors = require('colors');
const join = path.join;
const Combinatorics = require('js-combinatorics');
const normalizeHeader = require('header-case-normalizer');
/**
* Returns the status code out of the
* first line of an HTTP response
* (ie. HTTP/1.1 200 Ok)
*/
function parseStatus(header) {
return header.split(' ')[1];
};
/**
* Parses an HTTP header, splitting
* by colon.
*/
const parseHeader = function (header) {
header = header.split(': ');
return {key: normalizeHeader(header[0]), value: parseValue(header[1])};
};
const parseValue = function(value) {
if (/^#header/m.test(value)) {
return value.replace(/^#header (.*);/m, function (statement, val) {
const expression = val.replace(/[${}]/g, '');
return eval(expression);
}).replace(/\r\n?/g, '\n');
}
return value;
};
/**
* Prepares headers to watch, no duplicates, non-blanks.
* Priority exports over ENV definition.
*/
const prepareWatchedHeaders = function () {
const exportHeaders = module.exports.headers && module.exports.headers.toString();
const headers = (exportHeaders || process.env.MOCK_HEADERS || '').split(',');
return headers.filter(function(item, pos, self) {
return item && self.indexOf(item) == pos;
});
}
/**
* Parser the content of a mockfile
* returning an HTTP-ish object with
* status code, headers and body.
*/
const parse = function (content, file, request) {
const headers = {};
let body;
const bodyContent = [];
content = content.split(/\r?\n/);
const status = parseStatus(content[0]);
let headerEnd = false;
delete content[0];
content.forEach(function (line) {
switch (true) {
case headerEnd:
bodyContent.push(line);
break;
case (line === '' || line === '\r'):
headerEnd = true;
break;
default:
const header = parseHeader(line);
headers[header.key] = header.value;
break;
}
});
body = bodyContent.join('\n');
if (/^#import/m.test(body)) {
const context = path.parse(file).dir + '/';
body = body.replace(/^#import (.*);/m, function (includeStatement, file) {
const importThisFile = file.replace(/['"]/g, '');
const content = fs.readFileSync(path.join(context, importThisFile));
if (importThisFile.endsWith(".js")) {
return JSON.stringify(eval(content.toString()));
} else {
return content;
}
})
.replace(/\r\n?/g, '\n');
}
return {status: status, headers: headers, body: body};
};
function removeBlanks(array) {
return array.filter(function (i) { return i; });
}
function getWildcardPath(dir) {
let steps = removeBlanks(dir.split('/'));
let testPath;
let newPath;
let exists = false;
while(steps.length) {
steps.pop();
testPath = join(steps.join('/'), '/__');
exists = fs.existsSync(join(mockserver.directory, testPath));
if(exists) { newPath = testPath; }
}
const res = getDirectoriesRecursive(mockserver.directory).filter(dir => {
const directories = dir.split(path.sep);
return directories.includes('__');
}).sort((a, b) => {
const aLength = a.split(path.sep)
const bLength = b.split(path.sep)
if (aLength == bLength)
return 0
// Order from longest file path to shortest.
return aLength > bLength ? -1 : 1
}).map(dir => {
const steps = dir.split(path.sep)
const baseDir = mockserver.directory.split(path.sep)
steps.splice(0, baseDir.length)
return steps.join(path.sep)
})
steps = removeBlanks(dir.split('/'))
newPath = matchWildcardPaths(res, steps) || newPath;
return newPath;
}
function matchWildcardPaths(res, steps) {
for (let resIndex = 0; resIndex < res.length; resIndex++) {
const dirSteps = res[resIndex].split(/\/|\\/);
if (dirSteps.length !== steps.length) { continue; }
const result = matchWildcardPath(steps, dirSteps);
if (result) { return result; }
}
return null;
}
function matchWildcardPath(steps, dirSteps) {
for (let stepIndex = 1; stepIndex <= steps.length; stepIndex++) {
const step = steps[steps.length - stepIndex];
const dirStep = dirSteps[dirSteps.length - stepIndex];
if (step !== dirStep && dirStep != '__') {
return null;
}
}
return '/' + dirSteps.join('/');
}
function flattenDeep(directories){
return directories.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
function getDirectories(srcpath) {
return fs.readdirSync(srcpath)
.map(file => path.join(srcpath, file))
.filter(path => fs.statSync(path).isDirectory());
}
function getDirectoriesRecursive(srcpath) {
const nestedDirectories = getDirectories(srcpath).map(getDirectoriesRecursive);
const directories = flattenDeep(nestedDirectories);
directories.push(srcpath)
return directories;
}
/**
* Returns the body or query string to be used in
* the mock name.
*
* In any case we will prepend the value with a double
* dash so that the mock files will look like:
*
* POST--My-Body=123.mock
*
* or
*
* GET--query=string&hello=hella.mock
*/
// POST--email=accountant@barong.io&password=123123&data.address=111
function getObjectPath(instance, parentName) {
let result = '';
Object.keys(instance).forEach(key=>{
const value = instance[key];
const formatKey = parentName? `${parentName}.${key}`: key;
if(typeof value === 'object') {
result += getObjectPath(value, formatKey);
}
else {
result += `&${formatKey}=${value}`;
}
});
return result;
}
function getBodyOrQueryString(body, query) {
if (query) {
return '--' + query;
}
if (body && body !== '') {
try{
let obj = eval('(' + body + ')');
const path = getObjectPath(obj);
return '--' + path.replace(/^&/gi, '');
}
catch (error) {
console.log('[Body parse error]', error);
}
return '--' + body;
}
return body;
}
/**
* Ghetto way to get the body
* out of the request.
*
* There are definitely better
* ways to do this (ie. npm/body
* or npm/body-parser) but for
* the time being this does it's work
* (ie. we don't need to support
* fancy body parsing in mockserver
* for now).
*/
function getBody(req, callback) {
let body = '';
req.on('data', function(b){
body = body + b.toString();
});
req.on('end', function() {
callback(body);
});
}
function getMockedContent(path, prefix, body, query) {
const mockName = prefix + (getBodyOrQueryString(body, query) || '') + '.mock';
const mockFile = join(mockserver.directory, path, mockName);
let content;
try {
content = fs.readFileSync(mockFile, {encoding: 'utf8'});
if (mockserver.verbose) {
console.log('Reading from '+ mockFile.yellow +' file: ' + 'Matched'.green);
}
} catch(err) {
if (mockserver.verbose) {
console.log('Reading from '+ mockFile.yellow +' file: ' + 'Not matched'.red);
}
content = (body || query) && getMockedContent(path, prefix);
}
return content;
}
function getContentFromPermutations(path, method, body, query, permutations) {
let content, prefix;
while(permutations.length) {
prefix = method + permutations.pop().join('');
content = getMockedContent(path, prefix, body, query) || content;
}
return { content: content, prefix: prefix };
}
const mockserver = {
directory: '.',
verbose: false,
headers: [],
init: function(directory, verbose) {
this.directory = directory;
this.verbose = !!verbose;
this.headers = prepareWatchedHeaders();
},
handle: function(req, res) {
getBody(req, function(body) {
req.body = body;
const url = req.url;
let path = url;
const queryIndex = url.indexOf('?'),
query = queryIndex >= 0 ? url.substring(queryIndex).replace(/\?/g, '') : '',
method = req.method.toUpperCase(),
headers = [];
if (queryIndex > 0) {
path = url.substring(0, queryIndex);
}
if(req.headers && mockserver.headers.length) {
mockserver.headers.forEach(function(header) {
header = header.toLowerCase();
if(req.headers[header]) {
headers.push('_' + normalizeHeader(header) + '=' + req.headers[header]);
}
});
}
// Now, permute the possible headers, and look for any matching files, prioritizing on
// both # of headers and the original header order
let matched,
permutations = [[]];
if(headers.length) {
permutations = Combinatorics.permutationCombination(headers).toArray().sort(function(a, b) { return b.length - a.length; });
permutations.push([]);
}
matched = getContentFromPermutations(path, method, body, query, permutations.slice(0));
if(!matched.content && (path = getWildcardPath(path))) {
matched = getContentFromPermutations(path, method, body, query, permutations.slice(0));
}
if(matched.content) {
const mock = parse(matched.content, join(mockserver.directory, path, matched.prefix), req);
res.writeHead(mock.status, mock.headers);
return res.end(mock.body);
} else {
res.writeHead(404);
res.end('Not Mocked');
}
});
}
};
module.exports = function(directory, silent) {
mockserver.init(directory, silent);
return mockserver.handle;
};
module.exports.headers = null;