From 5f4ef82a3320fa10f893606c99d418806f6e32e1 Mon Sep 17 00:00:00 2001 From: miguelBinpar Date: Wed, 6 May 2020 11:28:24 +0200 Subject: [PATCH] runtime changes ready and full coverage --- index.ts | 6 +- package-lock.json | 179 ++++++++++-------- package.json | 8 +- settings/index.ts | 4 +- .../init-ok/api/ok.ts} | 0 .../init-ok/model/ok.ts} | 0 .../server/stress-validator/folder/file-0.ts | 1 + .../server/stress-validator/folder/file-1.ts | 1 + .../server/stress-validator/folder/file-2.ts | 1 + .../server/stress-validator/folder/file-3.ts | 1 + .../server/stress-validator/folder/file-4.ts | 1 + .../server/stress-validator/folder/file.ts | 1 + .../stress-validator/folder/last-file.ts | 1 + test/mocks/working-server/api/documented.ts | 8 - test/mocks/working-server/model/documented.ts | 8 - test/tests/basic/jest-init.ts | 1 + .../getFunctionDocContainer.ts | 0 test/tests/{watcher => ts}/getFunctionName.ts | 0 .../{watcher => ts}/getMainMethodName.ts | 0 .../{watcher => ts}/getMainMethodNode.ts | 0 .../apiFileValidation.ts | 0 test/tests/validations/stressValidator.ts | 98 ++++++++++ test/tests/validations/validator.ts | 106 +++++++++++ test/tests/watcher/initWatchers.ts | 90 +++++---- test/tests/watcher/watcherAddEventHandler.ts | 69 ------- test/tests/watcher/watcherStress.ts | 105 ---------- .../watcher/watcherUnlinkEventHandler.ts | 77 -------- utils/getOnApiWatcherReady.ts | 25 --- utils/initWatchers.ts | 52 ++++- utils/modelFileValidation.ts | 15 ++ utils/onApiValidation.ts | 6 + utils/onModelValidation.ts | 5 + utils/runtimeValidator.ts | 4 +- 33 files changed, 449 insertions(+), 424 deletions(-) rename test/mocks/{.a2r/runtime/api/documented.ts => server/init-ok/api/ok.ts} (100%) rename test/mocks/{.a2r/runtime/model/documented.ts => server/init-ok/model/ok.ts} (100%) create mode 100644 test/mocks/server/stress-validator/folder/file-0.ts create mode 100644 test/mocks/server/stress-validator/folder/file-1.ts create mode 100644 test/mocks/server/stress-validator/folder/file-2.ts create mode 100644 test/mocks/server/stress-validator/folder/file-3.ts create mode 100644 test/mocks/server/stress-validator/folder/file-4.ts create mode 100644 test/mocks/server/stress-validator/folder/file.ts create mode 100644 test/mocks/server/stress-validator/folder/last-file.ts delete mode 100644 test/mocks/working-server/api/documented.ts delete mode 100644 test/mocks/working-server/model/documented.ts rename test/tests/{watcher => ts}/getFunctionDocContainer.ts (100%) rename test/tests/{watcher => ts}/getFunctionName.ts (100%) rename test/tests/{watcher => ts}/getMainMethodName.ts (100%) rename test/tests/{watcher => ts}/getMainMethodNode.ts (100%) rename test/tests/{watcher => validations}/apiFileValidation.ts (100%) create mode 100644 test/tests/validations/stressValidator.ts create mode 100644 test/tests/validations/validator.ts delete mode 100644 test/tests/watcher/watcherAddEventHandler.ts delete mode 100644 test/tests/watcher/watcherStress.ts delete mode 100644 test/tests/watcher/watcherUnlinkEventHandler.ts delete mode 100644 utils/getOnApiWatcherReady.ts create mode 100644 utils/modelFileValidation.ts create mode 100644 utils/onApiValidation.ts create mode 100644 utils/onModelValidation.ts diff --git a/index.ts b/index.ts index b80e624..216349b 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ import path from 'path'; import chokidar from 'chokidar'; -import { targetPath, runtimePath } from './settings'; +import { targetPath, proxyPath } from './settings'; import initWatchers from './utils/initWatchers'; import { ensureDir } from './tools/fs'; import isJest from './tools/isJest'; @@ -16,7 +16,7 @@ interface Process { const pendingProcesses: Process[] = []; const serverPath = path.resolve(__dirname, targetPath); -const runtimeDestPath = path.resolve(__dirname, runtimePath); +const proxyDestPath = path.resolve(__dirname, proxyPath); let runningProcess: 'start' | 'stop' = null; @@ -93,7 +93,7 @@ export const restart = start; * Inits API Watcher by ensuring destination path and running start process */ const init = async (): Promise => { - await ensureDir(runtimeDestPath); + await ensureDir(proxyDestPath); await start(); }; diff --git a/package-lock.json b/package-lock.json index 8badd3c..6e636fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "api-watcher", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1255,6 +1255,13 @@ "source-map-support": "^0.5.7", "temp-dir": "^2.0.0", "tslib": "^1.9.3" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + } } }, "@snyk/lodash": { @@ -1336,11 +1343,6 @@ "supports-color": "^5.3.0" } }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1536,9 +1538,9 @@ "dev": true }, "@types/node": { - "version": "13.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz", - "integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==" + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -2572,9 +2574,9 @@ } }, "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" }, "class-utils": { "version": "0.3.6", @@ -3694,17 +3696,29 @@ "dev": true }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } } }, "exit": { @@ -4137,12 +4151,9 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-uri": { "version": "2.0.4", @@ -4277,13 +4288,6 @@ "timed-out": "^4.0.0", "unzip-response": "^2.0.1", "url-parse-lax": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - } } }, "graceful-fs": { @@ -4768,6 +4772,14 @@ "dev": true, "requires": { "ci-info": "^2.0.0" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + } } }, "is-data-descriptor": { @@ -5611,18 +5623,16 @@ "@types/istanbul-reports": "^1.1.1", "@types/yargs": "^15.0.0", "chalk": "^3.0.0" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "graceful-fs": { @@ -8688,6 +8698,21 @@ } } }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -8711,6 +8736,15 @@ } } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -9076,9 +9110,9 @@ } }, "snyk": { - "version": "1.317.0", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.317.0.tgz", - "integrity": "sha512-zNUvvT50Ii5TpHEo9YmDo+j5tqO1b8jXQK9XygfgiuXPRUwxf6PMSqDM5hKkfzuBhaBYZ4IbZWQFQPbMkprj0w==", + "version": "1.319.0", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.319.0.tgz", + "integrity": "sha512-tOFkZuJYPxzT50joiHd/bxdfsWd/cqFZcfEj6VxRJ28wYBC7imzodp4m8cC6Rcox4++2WEm6ZHNlPLyYjmfPOg==", "requires": { "@snyk/cli-interface": "2.6.0", "@snyk/configstore": "^3.2.0-rc1", @@ -10165,37 +10199,6 @@ "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "requires": { "execa": "^0.7.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - } } }, "terminal-link": { @@ -10938,6 +10941,30 @@ "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==", "requires": { "execa": "^1.0.0" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + } } }, "winston": { diff --git a/package.json b/package.json index d6f5624..099dc81 100644 --- a/package.json +++ b/package.json @@ -45,15 +45,15 @@ "typescript": "^3.5.3", "winston": "^3.2.1", "winston-transport": "^4.3.0", - "snyk": "^1.317.0" + "snyk": "^1.319.0" }, "devDependencies": { "@types/jest": "^25.1.1", - "@types/node": "^13.13.4", + "@types/node": "^13.13.5", "@types/rimraf": "^3.0.0", "@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/parser": "^2.31.0", - "babel-jest": "^25.5.1", + "babel-jest": "^25.4.0", "better-docs": "^2.0.1", "coveralls": "^3.0.11", "eslint": "^6.8.0", @@ -62,7 +62,7 @@ "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.14.2", - "jest": "^25.5.4", + "jest": "^25.4.0", "jsdoc": "^3.6.3", "ts-jest": "^25.4.0", "ts-node-dev": "^1.0.0-pre.40", diff --git a/settings/index.ts b/settings/index.ts index dec3a2c..e6a1d0e 100644 --- a/settings/index.ts +++ b/settings/index.ts @@ -14,6 +14,6 @@ export const apiPath = 'api'; export const modelPath = 'model'; /** - * Default runtime target path, where watcher will copy api files once validated + * Default proxy target path, where watcher will generate proxy for API and Model */ -export const runtimePath = './.a2r/runtime'; \ No newline at end of file +export const proxyPath = './.a2r/proxy'; \ No newline at end of file diff --git a/test/mocks/.a2r/runtime/api/documented.ts b/test/mocks/server/init-ok/api/ok.ts similarity index 100% rename from test/mocks/.a2r/runtime/api/documented.ts rename to test/mocks/server/init-ok/api/ok.ts diff --git a/test/mocks/.a2r/runtime/model/documented.ts b/test/mocks/server/init-ok/model/ok.ts similarity index 100% rename from test/mocks/.a2r/runtime/model/documented.ts rename to test/mocks/server/init-ok/model/ok.ts diff --git a/test/mocks/server/stress-validator/folder/file-0.ts b/test/mocks/server/stress-validator/folder/file-0.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/file-0.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/server/stress-validator/folder/file-1.ts b/test/mocks/server/stress-validator/folder/file-1.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/file-1.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/server/stress-validator/folder/file-2.ts b/test/mocks/server/stress-validator/folder/file-2.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/file-2.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/server/stress-validator/folder/file-3.ts b/test/mocks/server/stress-validator/folder/file-3.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/file-3.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/server/stress-validator/folder/file-4.ts b/test/mocks/server/stress-validator/folder/file-4.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/file-4.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/server/stress-validator/folder/file.ts b/test/mocks/server/stress-validator/folder/file.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/file.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/server/stress-validator/folder/last-file.ts b/test/mocks/server/stress-validator/folder/last-file.ts new file mode 100644 index 0000000..b5f02fc --- /dev/null +++ b/test/mocks/server/stress-validator/folder/last-file.ts @@ -0,0 +1 @@ +// test file \ No newline at end of file diff --git a/test/mocks/working-server/api/documented.ts b/test/mocks/working-server/api/documented.ts deleted file mode 100644 index 4d67a4e..0000000 --- a/test/mocks/working-server/api/documented.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Method documentation - */ -const method = (): void => { - // Do stuff -}; - -export default method; diff --git a/test/mocks/working-server/model/documented.ts b/test/mocks/working-server/model/documented.ts deleted file mode 100644 index 4d67a4e..0000000 --- a/test/mocks/working-server/model/documented.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Method documentation - */ -const method = (): void => { - // Do stuff -}; - -export default method; diff --git a/test/tests/basic/jest-init.ts b/test/tests/basic/jest-init.ts index 2a53a5a..be92c8b 100644 --- a/test/tests/basic/jest-init.ts +++ b/test/tests/basic/jest-init.ts @@ -8,6 +8,7 @@ test('Should init when jest forced disabled', async (): Promise => { await waitForExpect(async (): Promise => { expect(index.activeWatchers.length).toBe(2); }); + setForceDisableJestDetection(false); await index.stop(); expect(index.activeWatchers.length).toBe(0); }); \ No newline at end of file diff --git a/test/tests/watcher/getFunctionDocContainer.ts b/test/tests/ts/getFunctionDocContainer.ts similarity index 100% rename from test/tests/watcher/getFunctionDocContainer.ts rename to test/tests/ts/getFunctionDocContainer.ts diff --git a/test/tests/watcher/getFunctionName.ts b/test/tests/ts/getFunctionName.ts similarity index 100% rename from test/tests/watcher/getFunctionName.ts rename to test/tests/ts/getFunctionName.ts diff --git a/test/tests/watcher/getMainMethodName.ts b/test/tests/ts/getMainMethodName.ts similarity index 100% rename from test/tests/watcher/getMainMethodName.ts rename to test/tests/ts/getMainMethodName.ts diff --git a/test/tests/watcher/getMainMethodNode.ts b/test/tests/ts/getMainMethodNode.ts similarity index 100% rename from test/tests/watcher/getMainMethodNode.ts rename to test/tests/ts/getMainMethodNode.ts diff --git a/test/tests/watcher/apiFileValidation.ts b/test/tests/validations/apiFileValidation.ts similarity index 100% rename from test/tests/watcher/apiFileValidation.ts rename to test/tests/validations/apiFileValidation.ts diff --git a/test/tests/validations/stressValidator.ts b/test/tests/validations/stressValidator.ts new file mode 100644 index 0000000..3326c8c --- /dev/null +++ b/test/tests/validations/stressValidator.ts @@ -0,0 +1,98 @@ +import path from 'path'; +import waitForExpect from 'wait-for-expect'; + +import { WatcherOptions, OnReady } from '../../../model/watcher'; + +import onError from '../../../utils/onError'; +import Validator from '../../../utils/runtimeValidator'; +import watchFolder from '../../../utils/watchFolder'; +import fileValidation from '../../../utils/modelFileValidation'; +import { mkDir, emptyFolder, exists, writeFile } from '../../../tools/fs'; + +const serverPath = path.resolve( + __dirname, + '../../mocks/server/stress-validator', +); + +beforeAll(async () => { + await emptyFolder(serverPath); +}); + +const writeSeveralFiles = async ( + filePath: string, + nFiles = 5, +): Promise => { + const files = new Array(nFiles) + .fill(null) + .map((_val, i) => path.resolve(filePath, `file-${i}.ts`)); + await Promise.all(files.map(file => writeFile(file, '// test file'))); +}; + +/** + * Validator should process several files. Method `processQueue` should be called when already processing and `filesToProcess` array should be filtered when adding new files + */ +test('Stress validator by writing several files at once', async (): Promise< + void +> => { + const onValidation = (): void => { + // Empty validation handler for testing purposes + }; + let onEvent: ( + eventName: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', + eventPath: string, + ) => void = null; + const onReady: OnReady = async (watcher, targetPath): Promise => { + const validator = new Validator(targetPath, fileValidation, onValidation); + onEvent = jest.fn((eventName, eventPath): void => { + validator.addFileToQueue({ targetPath: eventPath, type: eventName }); + }); + watcher.on('all', onEvent); + }; + const jestOnReady = jest.fn(onReady); + const watcherOptions: WatcherOptions = { + onError, + onReady: jestOnReady, + targetPath: serverPath, + }; + + const watcher = await watchFolder(watcherOptions); + await waitForExpect( + async (): Promise => { + expect(jestOnReady).toHaveBeenCalled(); + }, + ); + + const folderPath = path.resolve(serverPath, 'folder'); + await mkDir(folderPath); + await waitForExpect( + async (): Promise => { + expect(await exists(folderPath)).toBe(true); + }, + ); + + const filePath = path.resolve(folderPath, 'file.ts'); + await writeFile(filePath, '// test file'); + await waitForExpect( + async (): Promise => { + expect(await exists(filePath)).toBe(true); + expect(onEvent).toHaveBeenCalledWith('add', filePath); + }, + ); + + const lastFilePath = path.resolve(folderPath, 'last-file.ts'); + setTimeout(async function writeFiles(): Promise { + await writeSeveralFiles(folderPath); + setTimeout(async function writeLastFile(): Promise { + await writeFile(lastFilePath, '// test file'); + }, 2000); + }, 2000); + + await waitForExpect( + async (): Promise => { + expect(await exists(lastFilePath)).toBe(true); + expect(onEvent).toHaveBeenCalledWith('add', lastFilePath); + }, + ); + + await watcher.close(); +}); diff --git a/test/tests/validations/validator.ts b/test/tests/validations/validator.ts new file mode 100644 index 0000000..fbbfb20 --- /dev/null +++ b/test/tests/validations/validator.ts @@ -0,0 +1,106 @@ +import path from 'path'; +import waitForExpect from 'wait-for-expect'; + +import { WatcherOptions, OnReady } from '../../../model/watcher'; + +import onError from '../../../utils/onError'; +import Validator from '../../../utils/runtimeValidator'; +import watchFolder from '../../../utils/watchFolder'; +import fileValidation from '../../../utils/modelFileValidation'; +import { + mkDir, + emptyFolder, + exists, + writeFile, + unlink, + rmDir, +} from '../../../tools/fs'; + +const serverPath = path.resolve(__dirname, '../../mocks/server/validator'); + +beforeAll(async () => { + await emptyFolder(serverPath); +}); + +/** + * Validator should handle basic event types properly + */ +test('Basic validator flow', async (): Promise => { + const onValidation = (): void => { + // Empty validation handler for testing purposes + }; + let onEvent: ( + eventName: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', + eventPath: string, + ) => void = null; + const onReady: OnReady = async (watcher, targetPath): Promise => { + const validator = new Validator(targetPath, fileValidation, onValidation); + onEvent = jest.fn((eventName, eventPath): void => { + validator.addFileToQueue({ targetPath: eventPath, type: eventName }); + }); + watcher.on('all', onEvent); + }; + const jestOnReady = jest.fn(onReady); + const watcherOptions: WatcherOptions = { + onError, + onReady: jestOnReady, + targetPath: serverPath, + }; + + const watcher = await watchFolder(watcherOptions); + await waitForExpect( + async (): Promise => { + expect(jestOnReady).toHaveBeenCalled(); + }, + ); + + const folderPath = path.resolve(serverPath, 'folder'); + await mkDir(folderPath); + await waitForExpect( + async (): Promise => { + expect(await exists(folderPath)).toBe(true); + }, + ); + + const filePath = path.resolve(folderPath, 'file.ts'); + await writeFile(filePath, '// test file'); + await waitForExpect( + async (): Promise => { + expect(await exists(filePath)).toBe(true); + expect(onEvent).toHaveBeenCalledWith( + 'add', + filePath, + ); + }, + ); + + await writeFile(filePath, '// test file changed'); + await waitForExpect(async (): Promise => { + expect(onEvent).toHaveBeenCalledWith( + 'change', + filePath, + ); + }); + + await unlink(filePath); + await waitForExpect( + async (): Promise => { + expect(await exists(filePath)).toBe(false); + expect(onEvent).toHaveBeenCalledWith( + 'unlink', + filePath, + ); + }, + ); + + await rmDir(folderPath); + await waitForExpect(async (): Promise => { + expect(await exists(folderPath)).toBe(false); + expect(onEvent).toHaveBeenCalledWith( + 'unlinkDir', + folderPath, + ); + }); + + await watcher.close(); +}); diff --git a/test/tests/watcher/initWatchers.ts b/test/tests/watcher/initWatchers.ts index a2f882f..0a5cd8b 100644 --- a/test/tests/watcher/initWatchers.ts +++ b/test/tests/watcher/initWatchers.ts @@ -1,52 +1,70 @@ import path from 'path'; import waitForExpect from 'wait-for-expect'; -import { runtimePath, apiPath, modelPath } from '../../../settings'; +import { apiPath, modelPath } from '../../../settings'; +import { emptyFolder, ensureDir, writeFile, exists } from '../../../tools/fs'; import initWatchers from '../../../utils/initWatchers'; -import { exists, rimraf } from '../../../tools/fs'; +const serverPath = path.resolve(__dirname, '../../mocks/server/init-ok'); +const serverApiPath = path.resolve(serverPath, apiPath); +const serverModelPath = path.resolve(serverPath, modelPath); const mainPath = path.resolve(__dirname, '../../mocks'); +beforeAll(async () => { + await emptyFolder(serverPath); + await ensureDir(serverApiPath); + await ensureDir(serverModelPath); +}); + +const validatedContent = `/** + * Method documentation + */ +const method = (): void => { + // Do stuff +}; + +export default method; +`; + /** * Unexisting server path will throw exception */ -test('Unexisting server path will throw exception', (): Promise => { +test('Unexisting server path will throw exception', async (): Promise => { const wrongPath = '/wrong/path/to/server'; - expect.assertions(1); - return expect(initWatchers(wrongPath, wrongPath)).rejects.toBeInstanceOf( - Error, - ); + await waitForExpect(async (): Promise => { + expect(initWatchers(wrongPath, wrongPath)).rejects.toBeInstanceOf(Error); + }); }); /** - * Server path must exist and watcher should process existing files + * Should work with existing server path and process files when added */ -test(`Watchers should work for existing server path and process existing files when starting`, async (): Promise< - void -> => { - const workingPath = path.resolve(mainPath, 'working-server'); - const runtimeDestPath = path.resolve(mainPath, runtimePath); - const expectedApiFilePath = path.resolve( - runtimeDestPath, - apiPath, - 'documented.ts', - ); - const expectedModelFilePath = path.resolve( - runtimeDestPath, - modelPath, - 'documented.ts', - ); - await rimraf(runtimeDestPath); - const watchers = await initWatchers(workingPath, mainPath); - await waitForExpect( - async (): Promise => { - expect(await exists(expectedApiFilePath)).toBe(true); - expect(await exists(expectedModelFilePath)).toBe(true); - }, - ); - await Promise.all( - watchers.map(async watcher => { - await watcher.close(); - }), - ); +test('Basic watchers flow', async (): Promise => { + const watchers = await initWatchers(serverPath, mainPath); + const handler = jest.fn((eventName, eventPath): void => { + console.log(eventName, eventPath); + // Empty function to check event handler + }); + watchers.forEach((watcher): void => { + watcher.on('all', handler); + }); + const fileName = 'ok.ts'; + const serverApiFilePath = path.resolve(serverApiPath, fileName); + const serverModelFilePath = path.resolve(serverModelPath, fileName); + await writeFile(serverApiFilePath, validatedContent); + await writeFile(serverModelFilePath, validatedContent); + await waitForExpect(async (): Promise => { + expect(await exists(serverApiFilePath)).toBe(true); + expect(await exists(serverModelFilePath)).toBe(true); + expect(handler).toHaveBeenCalledWith( + 'add', + serverApiFilePath, + ); + expect(handler).toHaveBeenCalledWith( + 'add', + serverModelFilePath, + ); + }); + + await Promise.all(watchers.map(w => w.close())); }); diff --git a/test/tests/watcher/watcherAddEventHandler.ts b/test/tests/watcher/watcherAddEventHandler.ts deleted file mode 100644 index 8215ecc..0000000 --- a/test/tests/watcher/watcherAddEventHandler.ts +++ /dev/null @@ -1,69 +0,0 @@ -import path from 'path'; -import waitForExpect from 'wait-for-expect'; - -import { apiPath } from '../../../settings'; -import onError from '../../../utils/onError'; -import watchFolder from '../../../utils/watchFolder'; -import getOnApiWatcherReady from '../../../utils/getOnApiWatcherReady'; -import { exists, rimraf, emptyFolder, writeFile } from '../../../tools/fs'; - -const runtimePath = '.a2r-add/runtime'; - -const mainPath = path.resolve(__dirname, '../../mocks'); -const targetPath = path.resolve(__dirname, '../../mocks/server/right'); -const fileName = 'documented.ts'; -const documentedFilePath = path.resolve(targetPath, fileName); -const runtimeDestPath = path.resolve(mainPath, runtimePath); -const runtimeApiDestPath = path.resolve(runtimeDestPath, apiPath); -const expectedApiFilePath = path.resolve(runtimeApiDestPath, fileName); - -const validatedFileContent = `/** - * Method documentation - */ -const method = (): void => { - // Do stuff -}; - -export default method;`; - -beforeAll(async (): Promise => { - await emptyFolder(targetPath); - await emptyFolder(runtimeApiDestPath); -}); - -/** - * Once watcher is ready and a new file is added, it should be processed - */ -test(`Watcher should process a file when added`, async (): Promise< - void -> => { - await rimraf(runtimeDestPath); - const onApiWatcherReady = getOnApiWatcherReady(runtimeApiDestPath); - const jestOnReady = jest.fn(onApiWatcherReady); - const watcher = await watchFolder({ - onError, - targetPath, - onReady: jestOnReady, - }); - await waitForExpect( - async (): Promise => { - expect(jestOnReady).toHaveBeenCalled(); - }, - ); - setTimeout(async (): Promise => { - await writeFile(documentedFilePath, validatedFileContent); - }, 1000); - await waitForExpect( - async (): Promise => { - expect(await exists(documentedFilePath)).toBe(true); - expect(await exists(expectedApiFilePath)).toBe(true); - }, - ); - await watcher.close(); -}); - -afterAll(async (): Promise => { - await emptyFolder(targetPath); - await emptyFolder(runtimeApiDestPath); -}); - diff --git a/test/tests/watcher/watcherStress.ts b/test/tests/watcher/watcherStress.ts deleted file mode 100644 index 03176bc..0000000 --- a/test/tests/watcher/watcherStress.ts +++ /dev/null @@ -1,105 +0,0 @@ -import path from 'path'; -import waitForExpect from 'wait-for-expect'; - -import { apiPath } from '../../../settings'; -import { emptyFolder, writeFile, exists, rimraf } from '../../../tools/fs'; -import getOnApiWatcherReady from '../../../utils/getOnApiWatcherReady'; -import watchFolder from '../../../utils/watchFolder'; -import onError from '../../../utils/onError'; - -const runtimePath = '.a2r-stress/runtime'; - -const targetPath = path.resolve(__dirname, '../../mocks/server/stressing-api'); -const mainPath = path.resolve(__dirname, '../../mocks'); -const runtimeDestPath = path.resolve(mainPath, runtimePath, apiPath); - -const lastFileName = 'validated-final.ts'; -const lastFilePath = path.resolve(targetPath, lastFileName); -const lastFileDestPath = path.resolve(mainPath, runtimePath, apiPath, lastFileName); - -const validatedFileContent = `/** - * Method documentation - */ -const method = (): void => { - // Do stuff -}; - -export default method;`; - -beforeAll( - async (): Promise => { - await emptyFolder(targetPath); - await emptyFolder(runtimeDestPath); - }, -); - -const writeAndChangeFiles = async (nFiles = 5): Promise => { - const files = new Array(nFiles) - .fill(null) - .map((_val, i) => path.resolve(targetPath, `validated-${i}.ts`)); - await Promise.all( - files.map((file) => - writeFile( - file, - `${validatedFileContent}${Math.random() < 0.5 ? '\n' : '\n\n'}`, - ), - ), - ); -}; - -/** - * Basic API Watcher should work - */ -test(`API should work fine with multiple file changes`, async (): Promise< - void -> => { - await rimraf(runtimeDestPath); - const onApiWatcherReady = getOnApiWatcherReady(runtimeDestPath); - const jestOnReady = jest.fn(onApiWatcherReady); - const nFiles = 5; - const files = new Array(nFiles) - .fill(null) - .map((_val, i) => path.resolve(targetPath, `validated-${i}.ts`)); - - const watcher = await watchFolder({ - onError, - targetPath, - onReady: jestOnReady, - }); - await waitForExpect( - async (): Promise => { - expect(jestOnReady).toHaveBeenCalled(); - }, - ); - await Promise.all(files.map((file) => writeFile(file, validatedFileContent))); - await Promise.all( - files.map((file) => - waitForExpect( - async (): Promise => { - expect(await exists(file)).toBe(true); - }, - ), - ), - ); - setTimeout(async function writeFiles(): Promise { - await writeAndChangeFiles(nFiles); - setTimeout(async function closeWatcher(): Promise { - writeFile(lastFilePath, validatedFileContent) - }, 2000); - }, 2000); - await waitForExpect( - async (): Promise => { - expect(await exists(lastFilePath)).toBe(true); - expect(await exists(lastFileDestPath)).toBe(true); - }, - 15000, - ); - await watcher.close(); -}, 25000); - -afterAll( - async (): Promise => { - await emptyFolder(targetPath); - await emptyFolder(runtimeDestPath); - }, -); diff --git a/test/tests/watcher/watcherUnlinkEventHandler.ts b/test/tests/watcher/watcherUnlinkEventHandler.ts deleted file mode 100644 index 06b297b..0000000 --- a/test/tests/watcher/watcherUnlinkEventHandler.ts +++ /dev/null @@ -1,77 +0,0 @@ -import path from 'path'; -import waitForExpect from 'wait-for-expect'; - -import { apiPath } from '../../../settings'; -import onError from '../../../utils/onError'; -import watchFolder from '../../../utils/watchFolder'; -import getOnApiWatcherReady from '../../../utils/getOnApiWatcherReady'; -import { exists, rimraf, emptyFolder, writeFile, unlink, ensureDir } from '../../../tools/fs'; - -const runtimePath = '.a2r-unlink/runtime'; - -const mainPath = path.resolve(__dirname, '../../mocks'); -const targetPath = path.resolve(__dirname, '../../mocks/server/unlink-api'); -const subFolderPath = path.resolve(targetPath, 'folder'); -const fileName = 'unlink-documented.ts'; -const documentedFilePath = path.resolve(targetPath, fileName); -const subFolderDocumentedFilePath = path.resolve(subFolderPath, fileName); -const runtimeDestPath = path.resolve(mainPath, runtimePath); -const runtimeApiDestPath = path.resolve(runtimeDestPath, apiPath); -const expectedApiFilePath = path.resolve(runtimeApiDestPath, fileName); -const expectedApiSubFolderFilePath = path.resolve(runtimeApiDestPath, 'folder', fileName); - -const validatedFileContent = `/** - * Method documentation - */ -const method = (): void => { - // Do stuff -}; - -export default method;`; - -beforeAll(async (): Promise => { - await emptyFolder(targetPath); - await emptyFolder(runtimeApiDestPath); -}); - -/** - * Basic API Watcher unlink flow should cover unlink and unlinkDir event types - */ -test(`Basic API Watcher unlink flow`, async (): Promise => { - await rimraf(runtimeDestPath); - const onApiWatcherReady = getOnApiWatcherReady(runtimeApiDestPath); - const jestOnReady = jest.fn(onApiWatcherReady); - await writeFile(documentedFilePath, validatedFileContent); - await ensureDir(subFolderPath); - await writeFile(subFolderDocumentedFilePath, validatedFileContent); - const watcher = await watchFolder({ - onError, - targetPath, - onReady: jestOnReady, - }); - await waitForExpect( - async (): Promise => { - expect(jestOnReady).toHaveBeenCalled(); - expect(await exists(expectedApiFilePath)).toBe(true); - expect(await exists(expectedApiSubFolderFilePath)).toBe(true); - }, - ); - setTimeout(async (): Promise => { - await unlink(documentedFilePath); - await rimraf(subFolderPath); - }, 1000); - await waitForExpect( - async (): Promise => { - expect(await exists(documentedFilePath)).toBe(false); - expect(await exists(subFolderPath)).toBe(false); - expect(await exists(expectedApiFilePath)).toBe(false); - expect(await exists(expectedApiSubFolderFilePath)).toBe(false); - }, - ); - await watcher.close(); -}); - -afterAll(async (): Promise => { - await emptyFolder(targetPath); - await emptyFolder(runtimeApiDestPath); -}); diff --git a/utils/getOnApiWatcherReady.ts b/utils/getOnApiWatcherReady.ts deleted file mode 100644 index c105c39..0000000 --- a/utils/getOnApiWatcherReady.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OnReady } from '../model/watcher'; - -import Validator from './runtimeValidator'; -import apiFileValidation from './apiFileValidation'; -import { copyContents, emptyFolder } from '../tools/fs'; - -const getOnApiWatcherReady = (apiRuntimePath: string): OnReady => async ( - watcher, - targetPath, -): Promise => { - const validator = new Validator( - targetPath, - apiFileValidation, - async (): Promise => { - await emptyFolder(apiRuntimePath); - await copyContents(targetPath, apiRuntimePath); - // Generate proxy - }, - ); - watcher.on('all', (eventName, eventPath): void => { - validator.addFileToQueue({ targetPath: eventPath, type: eventName }); - }); -}; - -export default getOnApiWatcherReady; diff --git a/utils/initWatchers.ts b/utils/initWatchers.ts index 836ff58..bedf002 100644 --- a/utils/initWatchers.ts +++ b/utils/initWatchers.ts @@ -1,13 +1,19 @@ import path from 'path'; import chokidar from 'chokidar'; +import { out } from '@a2r/telemetry'; -import { WatcherOptions } from '../model/watcher'; +import { WatcherOptions, OnReady } from '../model/watcher'; -import { apiPath, modelPath, runtimePath } from '../settings'; +import { apiPath, modelPath, proxyPath } from '../settings'; import { exists, emptyFolder } from '../tools/fs'; +import { fullPath } from '../tools/colors'; import watchFolder from './watchFolder'; import onError from './onError'; -import getOnApiWatcherReady from './getOnApiWatcherReady'; +import Validator from './runtimeValidator'; +import apiFileValidation from './apiFileValidation'; +import onApiValidation from './onApiValidation'; +import modelFileValidation from './modelFileValidation'; +import onModelValidation from './onModelValidation'; /** * Starts watchers @@ -22,10 +28,25 @@ const initWatchers = async ( throw new Error(`Provided server path doesn't exist`); } + const proxyTargetPath = path.resolve(mainPath, proxyPath); + const apiTargetPath = path.resolve(serverPath, apiPath); - const apiRuntimePath = path.resolve(mainPath, runtimePath, apiPath); - await emptyFolder(apiRuntimePath); - const onApiWatcherReady = getOnApiWatcherReady(apiRuntimePath); + const apiProxyPath = path.resolve(proxyTargetPath, apiPath); + await emptyFolder(apiProxyPath); + const onApiWatcherReady: OnReady = async ( + watcher, + targetPath, + ): Promise => { + out.verbose(`Model proxy path: ${fullPath(apiProxyPath)}`); + const validator = new Validator( + targetPath, + apiFileValidation, + onApiValidation, + ); + watcher.on('all', (eventName, eventPath): void => { + validator.addFileToQueue({ targetPath: eventPath, type: eventName }); + }); + }; const apiWatcherOptions: WatcherOptions = { onError, targetPath: apiTargetPath, @@ -34,9 +55,22 @@ const initWatchers = async ( const apiWatcher = await watchFolder(apiWatcherOptions); const modelTargetPath = path.resolve(serverPath, modelPath); - const modelRuntimePath = path.resolve(mainPath, runtimePath, modelPath); - await emptyFolder(modelRuntimePath); - const onModelWatcherReady = getOnApiWatcherReady(modelRuntimePath); + const modelProxyPath = path.resolve(proxyTargetPath, modelPath); + await emptyFolder(modelProxyPath); + const onModelWatcherReady: OnReady = async ( + watcher, + targetPath, + ): Promise => { + out.verbose(`Model proxy path: ${fullPath(modelProxyPath)}`); + const validator = new Validator( + targetPath, + modelFileValidation, + onModelValidation, + ); + watcher.on('all', (eventName, eventPath): void => { + validator.addFileToQueue({ targetPath: eventPath, type: eventName }); + }); + }; const modelWatcherOptions: WatcherOptions = { onError, targetPath: modelTargetPath, diff --git a/utils/modelFileValidation.ts b/utils/modelFileValidation.ts new file mode 100644 index 0000000..bde0004 --- /dev/null +++ b/utils/modelFileValidation.ts @@ -0,0 +1,15 @@ +import { out } from '@a2r/telemetry'; + +import { readFile } from '../tools/fs'; +import { fullPath } from '../tools/colors'; + +const fileValidation = async (filePath: string): Promise => { + const content = await readFile(filePath, 'utf8'); + if (!content) { + out.error(`File ${fullPath(filePath)} is empty`); + return false; + } + return true; +}; + +export default fileValidation; diff --git a/utils/onApiValidation.ts b/utils/onApiValidation.ts new file mode 100644 index 0000000..72e84fe --- /dev/null +++ b/utils/onApiValidation.ts @@ -0,0 +1,6 @@ +const onApiValidation = async (): Promise => { + // TODO: Call to main A2R instance to restart API Runtime + // TODO: Generate API proxy +}; + +export default onApiValidation; diff --git a/utils/onModelValidation.ts b/utils/onModelValidation.ts new file mode 100644 index 0000000..e0a9109 --- /dev/null +++ b/utils/onModelValidation.ts @@ -0,0 +1,5 @@ +const onModelValidation = async (): Promise => { + // TODO: Generate model proxy +}; + +export default onModelValidation; diff --git a/utils/runtimeValidator.ts b/utils/runtimeValidator.ts index d95eefc..248aa40 100644 --- a/utils/runtimeValidator.ts +++ b/utils/runtimeValidator.ts @@ -11,7 +11,7 @@ class RuntimeValidator { constructor( targetPath: string, fileValidator: (filePath: string) => Promise, - onSuccess: () => Promise, + onSuccess: () => void | Promise, ) { this.filesToProcess = new Array(); this.failingFiles = new Map(); @@ -34,7 +34,7 @@ class RuntimeValidator { /** * Function to be executed if there are no failing files after all files are processed */ - private onSuccess: () => Promise; + private onSuccess: () => void | Promise; /** * Indicates whether the validator is processing a file or not