refactor: adjust code to new configuration

This commit is contained in:
Karl-Philipp Wulfert
2019-06-05 17:13:28 +02:00
parent e70d5dccab
commit 4d4f7bf7ac
9 changed files with 221 additions and 118 deletions

View File

@@ -23,14 +23,17 @@ import {Converter, getValidatableTypesFromReflection} from './schema';
import {validateFiles, writeReport} from './validate';
// handle unhandled promise rejections
process.on('unhandledRejection', (error: Error) => {
Logger.error(error.message);
process.on('unhandledRejection', async (error: Error) => {
await Logger.error(error.message);
Logger.info(error.stack);
process.exit(1);
});
commander
.version(JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json')).toString()).version);
.version(JSON.parse(
readFileSync(resolve(__dirname, '..', 'package.json'))
.toString(),
).version);
commander
.command('routes <srcPath> <mdPath>')
@@ -46,7 +49,7 @@ commander
const routes = await gatherRouteInformation(projectReflection);
// initialize markdown output
let output: string = '# Routes\n\n';
let output = '# Routes\n\n';
// generate documentation for all routes
routes.forEach((routeWithMetaInformation) => {
@@ -84,7 +87,8 @@ commander
Logger.info(`Trying to find a package.json for ${srcPath}.`);
let path = srcPath;
// TODO: this check should be less ugly!
// TODO: this check should be less ugly! --- What is this doing anyway?
// tslint:disable-next-line:no-magic-numbers
while (!existsSync(join(path, 'package.json')) && path.length > 5) {
path = resolve(path, '..');
}
@@ -103,9 +107,10 @@ commander
validatableTypes.forEach((type) => {
const schema = coreConverter.getSchema(type, coreVersion);
// tslint:disable-next-line:no-magic-numbers
const stringifiedSchema = JSON.stringify(schema, null, 2);
const file = join(schemaPath, type + '.json');
const file = join(schemaPath, `${type}.json`);
// write schema to file
writeFileSync(file, stringifiedSchema);
@@ -126,9 +131,13 @@ commander
const errorsPerFile = await validateFiles(schemaPath, testPath);
let unexpected = false;
Object.keys(errorsPerFile).forEach((file) => {
for (const file in errorsPerFile) {
if (!errorsPerFile.hasOwnProperty(file)) {
continue;
}
unexpected = unexpected || errorsPerFile[file].some((error) => !error.expected);
});
}
if (typeof relativeReportPath !== 'undefined') {
const reportPath = resolve(relativeReportPath);
@@ -138,7 +147,7 @@ commander
if (!unexpected) {
Logger.ok('Successfully finished validation.');
} else {
Logger.error('Unexpected errors occurred during validation');
await Logger.error('Unexpected errors occurred during validation');
process.exit(1);
}
});

View File

@@ -53,14 +53,35 @@ export interface RouteWithMetaInformation {
* Instance of the route
*/
route: {
/**
* Error names of a route
*/
errorNames: Error[];
/**
* Method of the route
*/
method: string;
/**
* Obligatory parameters of the route
*/
obligatoryParameters: {
[k: string]: string;
}
};
/**
* Name of the request body
*/
requestBodyName: string;
/**
* Name of the response body
*/
responseBodyName: string;
/**
* Status code on success
*/
statusCodeSuccess: number;
/**
* URL fragment
*/
urlFragment: string;
};
}
@@ -93,13 +114,21 @@ export interface NodesWithMetaInformation {
* A schema with definitions
*/
interface SchemaWithDefinitions extends JSONSchema {
definitions: { [name: string]: Definition };
/**
* Definitions of the schema
*/
definitions: { [name: string]: Definition; };
}
/**
* An expectable error
*/
export type ExpectableValidationError = ValidationError & { expected: boolean };
export interface ExpectableValidationError extends ValidationError {
/**
* Whether or not the error is expected
*/
expected: boolean;
}
/**
* A map of files and their expectable validation errors
@@ -162,7 +191,9 @@ export function getTsconfigPath(startPath: string): string {
let tsconfigPath = startPath;
// see https://stackoverflow.com/questions/9652043/identifying-the-file-system-root-with-node-js
const root = (platform() === 'win32') ? process.cwd().split(sep)[0] : '/';
const root = (platform() === 'win32') ? process
.cwd()
.split(sep)[0] : '/';
// repeat until a tsconfig.json is found
while (!existsSync(join(tsconfigPath, 'tsconfig.json'))) {

View File

@@ -72,18 +72,15 @@ async function packCliJs(): Promise<void> {
let internalRequire: string | null = null;
const adjustedContent = '#!/usr/bin/env node\n\n' + content
const adjustedContent = content
.split('\n')
.map((line, lineNumber) => {
// check for exports (cli.js is not allowed to export anything)
if (Array.isArray(line.match(/^\s*((exports)|(module\.exports))/))) {
throw new Error(
'Line ' +
lineNumber +
' in cli.js has a reference to the exports object. cli.js is not for exporting. Line was: "'
+ line
+ '"',
`Line '${lineNumber}' in 'cli.js' exports something. cli.js is not for exporting. Line was:
${line}`,
);
}
@@ -92,7 +89,9 @@ async function packCliJs(): Promise<void> {
const match = line.match(/^(\s*)(const|var) ([a-z0-9_]*) = require\(("[^"]+"|'[^']+')\);$/i);
if (match !== null) {
// tslint:disable-next-line:no-magic-numbers
const importedName = match[3];
// tslint:disable-next-line:no-magic-numbers
const moduleName = match[4].substring(1, match[4].length - 1);
// if it begins with '.' and not ends with json
@@ -100,19 +99,23 @@ async function packCliJs(): Promise<void> {
// is the first internal require
if (internalRequire !== null) {
return 'const ' + importedName + ' = ' + internalRequire + ';';
return `const ${importedName} = ${internalRequire};`;
}
// only the first import needs a require
internalRequire = importedName;
return 'const ' + importedName + ' = require("./index");';
return `const ${importedName} = require("./index");`;
}
}
return line;
})
.join('\n');
return await writeFilePromisified(path, adjustedContent);
return writeFilePromisified(path, `#!/usr/bin/env node
${adjustedContent}`);
}
/**
@@ -127,11 +130,11 @@ async function getAllTypeDefinitions(): Promise<string[]> {
],
});
const promises = fileNames.map((fileName) => {
const promises = fileNames.map(async (fileName) => {
return readFilePromisified(fileName, 'utf8');
});
return await Promise.all(promises);
return Promise.all(promises);
}
/**
@@ -147,7 +150,7 @@ async function packTypeDefinitions(): Promise<void> {
const typeDefinitions = await getAllTypeDefinitions();
// pack TypeScript definition files
const imports: { [k: string]: string[] } = {};
const imports: { [k: string]: string[]; } = {};
const referenceLines: string[] = [];
@@ -163,6 +166,7 @@ async function packTypeDefinitions(): Promise<void> {
if (line.indexOf('/// <reference') === 0) {
referenceLines.push(line);
return '// moved referenced line';
}
@@ -170,13 +174,15 @@ async function packTypeDefinitions(): Promise<void> {
const match = line.match(/^import {([^}].*)} from '([^'].*)';$/);
if (match !== null) {
// extract module
// tslint:disable-next-line:no-magic-numbers - extract module
const module = match[2];
// extract imported objects
const importedObjects = match[1].split(',').map((object) => {
return object.trim();
});
const importedObjects = match[1]
.split(',')
.map((object) => {
return object.trim();
});
// add list of already imported objects for module
if (typeof imports[module] === 'undefined') {
@@ -195,27 +201,31 @@ async function packTypeDefinitions(): Promise<void> {
// replace import line
if (objectsToImport.length === 0) {
return '// extraneous removed import';
} else {
return 'import { ' + objectsToImport.join(', ') + ' } from \'' + module + '\';';
}
return `import {${objectsToImport.join(', ')}} from '${module}';`;
}
return line;
})
// filter lines which contain "local" imports
.filter((line) => {
return !line.match(/^import .* from '\./);
return line.match(/^import .* from '\./) === null;
})
// concat all lines separated by new lines
.join('\n');
if (allDefinitions.length > 0) {
if (referenceLines.length > 0) {
allDefinitions = referenceLines.join('\n') + '\n\n' + allDefinitions;
allDefinitions = `${referenceLines.join('\n')}
${allDefinitions}`;
}
// write packed TypeScript definition files
return await writeFilePromisified(path, PACK_IDENTIFIER + '\n\n' + allDefinitions);
return writeFilePromisified(path, `${PACK_IDENTIFIER}
${allDefinitions}`);
}
}
@@ -233,17 +243,21 @@ async function getAllJavaScriptModules(): Promise<JavaScriptModule[]> {
const promises = fileNames.map(async (fileName) => {
const fileContent = await readFilePromisified(fileName, 'utf8');
const directory = dirname(fileName).replace(new RegExp('^' + join(cwd(), 'lib')), '');
const directory = dirname(fileName)
.replace(new RegExp(`^${join(cwd(), 'lib')}`), '');
return {
content: '(function() {\n' + fileContent + '\n})();\n',
content: `(function() {
${fileContent}
})();
`,
dependencies: getAllInternalDependencies(fileContent),
directory: directory,
name: basename(fileName, '.js'),
};
});
return await Promise.all(promises);
return Promise.all(promises);
}
/**
@@ -271,13 +285,16 @@ async function packJavaScriptFiles(): Promise<void> {
// replace lines with internal requires
if (match !== null) {
// match[6] or match[8] contain the modulePath
// tslint:disable-next-line:no-magic-numbers - match[6] or match[8] contain the modulePath
if (typeof match[6] === 'undefined') {
// tslint:disable-next-line:no-magic-numbers
match[6] = match[8];
}
const whiteSpace = match[1] ? match[1] : '';
const whiteSpace = (typeof match[1] === 'string' && match[1].length > 0) ? match[1] : '';
// tslint:disable-next-line:no-magic-numbers
const importedName = match[3];
// tslint:disable-next-line:no-magic-numbers
const modulePath = match[6];
// leave line unchanged if it is a "global" import
@@ -286,22 +303,23 @@ async function packJavaScriptFiles(): Promise<void> {
}
// replace internal requires with `module.exports`
if (existsSync(join(cwd(), 'lib', module.directory, modulePath + '.js'))) {
return whiteSpace + 'const ' + importedName + ' = module.exports;';
if (existsSync(join(cwd(), 'lib', module.directory, `${modulePath}.js`))) {
return `${whiteSpace}const ${importedName} = module.exports;`;
}
if (existsSync(join(cwd(), 'src', module.directory, modulePath))) {
return whiteSpace + 'const ' + importedName + ' = require(\'../src/' + modulePath + '\');';
return `${whiteSpace} const ${importedName} = require(../src/${modulePath});`;
}
Logger.warn('Import ' + importedName + ' could not be found in module.directory ' + modulePath);
Logger.warn(`Import ${importedName} could not be found in module.directory ${modulePath}.`);
}
return line;
})
.join('\n');
return '// Module: ' + module.name + '\n' + module.content;
return `// Module: ${module.name}
${module.content}`;
})
// concat them separated by new lines
.join('\n\n\n\n\n')
@@ -332,10 +350,15 @@ async function packJavaScriptFiles(): Promise<void> {
if (wholeCode.length > 0) {
// add meta lines to the file
wholeCode = '"use strict";\nObject.defineProperty(exports, "__esModule", { value: true });\n\n' + wholeCode;
wholeCode = `"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
${wholeCode}`;
// write packed JavaScript files
return await writeFilePromisified(path, PACK_IDENTIFIER + '\n\n' + wholeCode);
return writeFilePromisified(path, `${PACK_IDENTIFIER}
${wholeCode}`);
}
}
@@ -351,8 +374,9 @@ async function deleteFileIfExistingAndPacked(path: string): Promise<void> {
// check if packed by this script
if (content.indexOf(PACK_IDENTIFIER) === 0) {
Logger.log('Found `' + path + '` which is packed by this script. Deleting it...');
return await unlinkPromisified(path);
Logger.log(`Found '${path}' which is packed by this script. Deleting it...`);
return unlinkPromisified(path);
}
} catch (err) {
if (err.code === 'ENOENT') {
@@ -372,23 +396,26 @@ function getAllInternalDependencies(moduleContent: string): string[] {
moduleContent.match(/^\s*(const|var) [a-z0-9_]* = require\("([^"]+)"\)|require\('([^']+)'\);$/gmi);
if (Array.isArray(requireLines)) {
return requireLines.map((requireLine) => {
const matches = requireLine.match(/require\("([^"]+)"\)|require\('([^']+)'\);$/i);
return requireLines
.map((requireLine) => {
const matches = requireLine.match(/require\("([^"]+)"\)|require\('([^']+)'\);$/i);
// previously matched require line does not contain a require?!
if (matches === null) {
throw new Error();
}
// previously matched require line does not contain a require?!
if (matches === null) {
throw new Error();
}
// return only the moduleName
return matches[1];
}).filter((moduleName) => {
// filter out internal modules beginning with './' and not ending with '.json'
return /^[.]{1,2}\/(?!.*\.json$).*$/i.test(moduleName);
}).map((internalModuleName) => {
// cut './' from the name
return internalModuleName.substring(2);
});
// return only the moduleName
return matches[1];
})
.filter((moduleName) => {
// filter out internal modules beginning with './' and not ending with '.json'
return /^[.]{1,2}\/(?!.*\.json$).*$/i.test(moduleName);
})
.map((internalModuleName) => {
// cut './' from the name
return internalModuleName.substring('./'.length);
});
}
return [];
@@ -417,11 +444,13 @@ function topologicalSort(modules: JavaScriptModule[]): JavaScriptModule[] {
});
// sort graph and return as an array of sorted modules
return topoSort.array(nodes, edges).map((moduleName: string) => {
return modules.find((module) => {
return module.name === moduleName;
return topoSort
.array(nodes, edges)
.map((moduleName: string) => {
return modules.find((module) => {
return module.name === moduleName;
});
});
});
}
/**

View File

@@ -17,5 +17,8 @@
* @validatable
*/
export interface Foo {
/**
* Dummy parameter
*/
lorem: 'ipsum';
}

View File

@@ -14,7 +14,9 @@
*/
import {asyncPool} from '@krlwlfrt/async-pool';
import {Logger} from '@openstapps/logger';
import {basename, dirname, join} from 'path';
import {ProjectReflection} from 'typedoc';
import {Type} from 'typedoc/dist/lib/models';
import {NodesWithMetaInformation, NodeWithMetaInformation, RouteWithMetaInformation} from './common';
/**
@@ -32,17 +34,20 @@ export async function gatherRouteInformation(reflection: ProjectReflection): Pro
throw new Error('Project reflection doesn\'t contain any modules.');
}
// tslint:disable-next-line:no-magic-numbers
await asyncPool(2, reflection.children, async (module) => {
if (Array.isArray(module.children) && module.children.length > 0) {
// tslint:disable-next-line:no-magic-numbers
await asyncPool(2, module.children, (async (node) => {
if (Array.isArray(node.extendedTypes) && node.extendedTypes.length > 0) {
if (node.extendedTypes.some((extendedType) => {
return (extendedType as any).name === 'SCAbstractRoute';
// tslint:disable-next-line:completed-docs
return (extendedType as (Type & { name: string; })).name === 'SCAbstractRoute';
})) {
Logger.info(`Found ${node.name} in ${module.originalName}.`);
if (module.originalName.match(/\.d\.ts$/)) {
module.originalName = module.originalName.substr(0, module.originalName.length - 5);
if (Array.isArray(module.originalName.match(/\.d\.ts$/))) {
module.originalName = join(dirname(module.originalName), basename(module.originalName, '.d.ts'));
Logger.info(`Using compiled version of module in ${module.originalName}.`);
}
@@ -71,17 +76,18 @@ export async function gatherRouteInformation(reflection: ProjectReflection): Pro
* @param node Node itself
* @param humanize Whether to humanize the name or not
*/
export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation, humanize: boolean = false): string {
export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation, humanize = false): string {
const humanizeString = require('humanize-string');
let printableName = name;
if (humanize) {
printableName = humanizeString(name.substr(2));
printableName = humanizeString(name.substr('SC'.length));
}
let link = `[${printableName}]`;
link += `(${getLinkForNode(name, node)})`;
return link;
}
@@ -93,12 +99,16 @@ export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation
*/
export function getLinkForNode(name: string, node: NodeWithMetaInformation): string {
let link = 'https://openstapps.gitlab.io/core/';
const module = node.module.toLowerCase().split('/').join('_');
const module = node.module
.toLowerCase()
.split('/')
.join('_');
if (node.type === 'Type alias') {
link += 'modules/';
link += `_${module}_`;
link += `.html#${name.toLowerCase()}`;
return link;
}
@@ -110,6 +120,7 @@ export function getLinkForNode(name: string, node: NodeWithMetaInformation): str
link += `${type}/`;
link += `_${module}_`;
link += `.${name.toLowerCase()}.html`;
return link;
}
@@ -117,7 +128,7 @@ export function getLinkForNode(name: string, node: NodeWithMetaInformation): str
* Generate documentation snippet for one route
*
* @param routeWithInfo A route instance with its meta information
* @param nodes
* @param nodes Nodes with meta information
*/
export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInformation,
nodes: NodesWithMetaInformation): string {
@@ -143,14 +154,20 @@ export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInform
| request | ${getLinkedNameForNode(route.requestBodyName, nodes[route.requestBodyName])} |
| response | ${getLinkedNameForNode(route.responseBodyName, nodes[route.responseBodyName])} |
| success code | ${route.statusCodeSuccess} |
| errors | ${route.errorNames.map((error) => {
return getLinkedNameForNode(error.name, nodes[error.name]);
}).join('<br>')} |
| errors | ${route.errorNames
.map((error) => {
return getLinkedNameForNode(error.name, nodes[error.name]);
})
.join('<br>')} |
`;
if (typeof route.obligatoryParameters === 'object' && Object.keys(route.obligatoryParameters).length > 0) {
let parameterTable = '<table><tr><th>parameter</th><th>type</th></tr>';
Object.keys(route.obligatoryParameters).forEach((parameter) => {
for (const parameter in route.obligatoryParameters) {
if (!route.obligatoryParameters.hasOwnProperty(parameter)) {
continue;
}
let type = route.obligatoryParameters![parameter];
if (typeof nodes[type] !== 'undefined') {
@@ -158,7 +175,7 @@ export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInform
}
parameterTable += `<tr><td>${parameter}</td><td>${type}</td></tr>`;
});
}
parameterTable += '</table>';
@@ -182,14 +199,14 @@ export function getNodeMetaInformationMap(projectReflection: ProjectReflection):
}
// iterate over modules
projectReflection.children.forEach((module: any) => {
projectReflection.children.forEach((module) => {
if (Array.isArray(module.children) && module.children.length > 0) {
// iterate over types
module.children.forEach((node: any) => {
module.children.forEach((node) => {
// add node with module and type
nodes[node.name] = {
module: module.name.substring(1, module.name.length - 1),
type: node.kindString,
type: node.kindString!,
};
});
}

View File

@@ -12,10 +12,10 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as ajv from 'ajv';
import * as Ajv from 'ajv';
import {Schema as JSONSchema} from 'jsonschema';
import {join} from 'path';
import {DEFAULT_CONFIG, SchemaGenerator} from 'ts-json-schema-generator';
import {DEFAULT_CONFIG, Definition, SchemaGenerator} from 'ts-json-schema-generator';
import {createFormatter} from 'ts-json-schema-generator/dist/factory/formatter';
import {createParser} from 'ts-json-schema-generator/dist/factory/parser';
import {createProgram} from 'ts-json-schema-generator/dist/factory/program';
@@ -28,8 +28,15 @@ import {getTsconfigPath, isSchemaWithDefinitions} from './common';
* Converts TypeScript source files to JSON schema files
*/
export class Converter {
private generator: SchemaGenerator;
private schemaValidator: ajv.Ajv;
/**
* Generator instance
*/
private readonly generator: SchemaGenerator;
/**
* Schema validator instance
*/
private readonly schemaValidator: Ajv.Ajv;
/**
* Create a new converter
@@ -58,24 +65,24 @@ export class Converter {
createFormatter(config),
);
// create ajv instance
this.schemaValidator = new ajv();
// create Ajv instance
this.schemaValidator = new Ajv();
this.schemaValidator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
}
/**
* Get schema for specific StAppsCore type
*
* @param {string} type Type to get the schema for
* @param {string} version Version to set for the schema
* @returns {Schema} Generated schema
* @param type Type to get the schema for
* @param version Version to set for the schema
* @returns Generated schema
*/
getSchema(type: string, version: string): JSONSchema {
// generate schema for this file/type
const schema: JSONSchema = this.generator.createSchema(type);
// set id of schema
schema.id = 'https://core.stapps.tu-berlin.de/v' + version + '/lib/schema/' + type + '.json';
schema.id = `https://core.stapps.tu-berlin.de/v${version}/lib/schema/${type}.json`;
if (isSchemaWithDefinitions(schema)) {
const selfReference = {
@@ -88,7 +95,10 @@ export class Converter {
delete selfReference.id;
// add self reference to definitions
schema.definitions['SC' + type] = Object.assign({}, selfReference as any);
schema.definitions[`SC${type}`] = {
...{},
...selfReference as unknown as Definition,
};
}
if (!this.schemaValidator.validateSchema(schema)) {
@@ -119,7 +129,7 @@ export function getValidatableTypesFromReflection(projectReflection: ProjectRefl
// check if type has annotation @validatable
if (typeof type.comment === 'object'
&& Array.isArray(type.comment.tags)
&& type.comment.tags.find((tag) => tag.tagName === 'validatable')) {
&& type.comment.tags.findIndex((tag) => tag.tagName === 'validatable') >= 0) {
// add type to list
validatableTypes.push(type.name);
}

View File

@@ -18,12 +18,7 @@ import {PathLike} from 'fs';
import {Schema, Validator as JSONSchemaValidator, ValidatorResult} from 'jsonschema';
import * as mustache from 'mustache';
import {basename, join, resolve} from 'path';
import {
ExpectableValidationErrors,
globPromisified,
readFilePromisified,
writeFilePromisified,
} from './common';
import {ExpectableValidationErrors, globPromisified, readFilePromisified, writeFilePromisified} from './common';
/**
* StAppsCore validator
@@ -32,7 +27,7 @@ export class Validator {
/**
* Map of schema names to schemas
*/
private readonly schemas: { [type: string]: Schema } = {};
private readonly schemas: { [type: string]: Schema; } = {};
/**
* JSONSchema validator instance
@@ -60,7 +55,7 @@ export class Validator {
Logger.log(`Adding schemas from ${schemaDir} to validator.`);
// Iterate over schema files
// tslint:disable-next-line:no-magic-numbers - iterate over schema files
await asyncPool(2, schemaFiles, async (file) => {
// read schema file
const buffer = await readFilePromisified(file);
@@ -84,7 +79,7 @@ export class Validator {
* @param instance Instance to validate
* @param schema Name of schema to validate instance against or the schema itself
*/
public validate(instance: any, schema: string | Schema): ValidatorResult {
public validate(instance: unknown, schema: string | Schema): ValidatorResult {
if (typeof schema === 'string') {
// if you want to access a schema that is contained in the validator object
if (typeof this.schemas[schema] !== 'object') {
@@ -92,10 +87,10 @@ export class Validator {
}
return this.validator.validate(instance, this.schemas[schema]);
} else {
// if you have a schema and want to validate it directly
return this.validator.validate(instance, schema);
}
// if you have a schema and want to validate it directly
return this.validator.validate(instance, schema);
}
}
@@ -122,7 +117,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
// map of errors per file
const errors: ExpectableValidationErrors = {};
// iterate over files to test
// tslint:disable-next-line:no-magic-numbers - iterate over files to test
await asyncPool(2, testFiles, async (testFile) => {
const testFileName = basename(testFile);
@@ -145,7 +140,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
errors[testFileName] = [];
// iterate over errors
result.errors.forEach((error) => {
for (const error of result.errors) {
// get idx of expected error
const errorIdx = expectedErrors.indexOf(error.name);
let expected = false;
@@ -156,7 +151,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
expected = true;
} else {
unexpectedErrors++;
Logger.error(`Unexpected error ${error.name} in ${testFile}`);
await Logger.error(`Unexpected error ${error.name} in ${testFile}`);
}
// add error to list of errors
@@ -164,12 +159,13 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
...error,
expected,
});
});
}
}
if (expectedErrors.length > 0) {
expectedErrors.forEach((error) => {
Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
for (const error of expectedErrors) {
await Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
errors[testFileName].push({
argument: false,
expected: false,
@@ -177,9 +173,9 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
message: `expected error ${error} did not occur`,
name: `expected ${error}`,
property: 'unknown',
schema: undefined as any,
schema: 'undefined',
});
});
}
} else if (unexpectedErrors === 0) {
Logger.info(`Successfully validated ${testFile}.`);
}
@@ -203,15 +199,21 @@ export async function writeReport(reportPath: PathLike, errors: ExpectableValida
let output = '';
Object.keys(errors).forEach((fileName) => {
for (const fileName in errors) {
if (!errors.hasOwnProperty(fileName)) {
continue;
}
let fileOutput = '';
errors[fileName].forEach((error, idx) => {
fileOutput += mustache.render(errorTemplate, {
idx: idx + 1,
// tslint:disable-next-line:no-magic-numbers
instance: JSON.stringify(error.instance, null, 2),
message: error.message,
property: error.property,
// tslint:disable-next-line:no-magic-numbers
schema: JSON.stringify(error.schema, null, 2),
status: (error.expected) ? 'alert-success' : 'alert-danger',
});
@@ -221,7 +223,7 @@ export async function writeReport(reportPath: PathLike, errors: ExpectableValida
errors: fileOutput,
testFile: fileName,
});
});
}
buffer = await readFilePromisified(resolve(__dirname, '..', 'resources', 'report.html.mustache'));
const reportTemplate = buffer.toString();

View File

@@ -23,7 +23,7 @@ process.on('unhandledRejection', (err) => {
process.exit(1);
});
@suite(timeout(10000), slow(5000))
@suite(timeout(20000), slow(10000))
export class CommonSpec {
@test
async getTsconfigPath() {

View File

@@ -23,7 +23,7 @@ process.on('unhandledRejection', (err) => {
process.exit(1);
});
@suite(timeout(15000), slow(5000))
@suite(timeout(20000), slow(10000))
export class SchemaSpec {
@test
async getSchema() {
@@ -39,6 +39,7 @@ export class SchemaSpec {
additionalProperties: false,
properties: {
lorem: {
description: 'Dummy parameter',
enum: [
'ipsum',
],
@@ -54,6 +55,7 @@ export class SchemaSpec {
id: 'https://core.stapps.tu-berlin.de/v0.0.1/lib/schema/Foo.json',
properties: {
lorem: {
description: 'Dummy parameter',
enum: [
'ipsum',
],