From 4d4f7bf7ace49f9c1dced62942eee966fd5c5ae5 Mon Sep 17 00:00:00 2001 From: Karl-Philipp Wulfert Date: Wed, 5 Jun 2019 17:13:28 +0200 Subject: [PATCH] refactor: adjust code to new configuration --- src/cli.ts | 27 ++++-- src/common.ts | 39 ++++++++- src/pack.ts | 137 +++++++++++++++++++------------ src/resources/{Foo.ts => foo.ts} | 3 + src/routes.ts | 47 +++++++---- src/schema.ts | 34 +++++--- src/validate.ts | 46 ++++++----- test/Common.spec.ts | 2 +- test/Schema.spec.ts | 4 +- 9 files changed, 221 insertions(+), 118 deletions(-) rename src/resources/{Foo.ts => foo.ts} (95%) diff --git a/src/cli.ts b/src/cli.ts index 11352edc..c36cb73c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -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 ') @@ -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); } }); diff --git a/src/common.ts b/src/common.ts index 532a0e67..02a49104 100644 --- a/src/common.ts +++ b/src/common.ts @@ -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'))) { diff --git a/src/pack.ts b/src/pack.ts index 8149bd2b..3369c827 100644 --- a/src/pack.ts +++ b/src/pack.ts @@ -72,18 +72,15 @@ async function packCliJs(): Promise { 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 { 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 { // 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 { ], }); - 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 { 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 { if (line.indexOf('/// { 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 { // 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 { 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 { // 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 { } // 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 { 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 { // 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; + }); }); - }); } /** diff --git a/src/resources/Foo.ts b/src/resources/foo.ts similarity index 95% rename from src/resources/Foo.ts rename to src/resources/foo.ts index 83727420..b5b09c70 100644 --- a/src/resources/Foo.ts +++ b/src/resources/foo.ts @@ -17,5 +17,8 @@ * @validatable */ export interface Foo { + /** + * Dummy parameter + */ lorem: 'ipsum'; } diff --git a/src/routes.ts b/src/routes.ts index e9e50756..2f58f716 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -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('
')} | +| errors | ${route.errorNames + .map((error) => { + return getLinkedNameForNode(error.name, nodes[error.name]); + }) + .join('
')} | `; if (typeof route.obligatoryParameters === 'object' && Object.keys(route.obligatoryParameters).length > 0) { let parameterTable = ''; - 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 += ``; - }); + } parameterTable += '
parametertype
${parameter}${type}
'; @@ -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!, }; }); } diff --git a/src/schema.ts b/src/schema.ts index cff2e7bd..ef6290d3 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -12,10 +12,10 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -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); } diff --git a/src/validate.ts b/src/validate.ts index d6a03b24..0efb50c2 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -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(); diff --git a/test/Common.spec.ts b/test/Common.spec.ts index 69361f74..588fb071 100644 --- a/test/Common.spec.ts +++ b/test/Common.spec.ts @@ -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() { diff --git a/test/Schema.spec.ts b/test/Schema.spec.ts index 2a863a73..a04a8bc0 100644 --- a/test/Schema.spec.ts +++ b/test/Schema.spec.ts @@ -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', ],