From a64960ddd002047d9747898d5c7bfccbd4c748f1 Mon Sep 17 00:00:00 2001 From: badrAZ Date: Mon, 15 Apr 2019 09:22:53 +0200 Subject: [PATCH] chore(xoConnection): various changes (#95) --- .eslintrc.js | 6 ++ package-lock.json | 108 ++++++++++++++++++++++++ package.json | 1 + src/_xoConnection.js | 151 ++++++++++++++++++++++++++++++++++ src/_xoWithTestHelpers.js | 59 ------------- src/backupNg/backupNg.spec.js | 2 +- src/job/job.spec.js | 5 +- src/user/user.spec.js | 3 +- src/util.js | 102 ----------------------- 9 files changed, 271 insertions(+), 166 deletions(-) create mode 100644 src/_xoConnection.js delete mode 100644 src/_xoWithTestHelpers.js diff --git a/.eslintrc.js b/.eslintrc.js index 5fae147e8..bd60b5cae 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,12 @@ module.exports = { "prettier/standard", ], + parser: "babel-eslint", + parserOptions: { + ecmaFeatures: { + legacyDecorators: true, + }, + }, rules: { // prefer let/const over var "no-var": "error", diff --git a/package-lock.json b/package-lock.json index 26e73f624..e204e835c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,6 +104,96 @@ "@babel/types": "^7.0.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.3.tgz", + "integrity": "sha512-UMl3TSpX11PuODYdWGrUeW6zFkdYhDn7wRLrOuNVM6f9L+S9CzmDXYyrp3MTHcwWjnzur1f/Op8A7iYZWya2Yg==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.0", + "@babel/helper-split-export-declaration": "^7.4.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-replace-supers": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz", + "integrity": "sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/parser": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==" + }, + "@babel/traverse": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "@babel/helper-define-map": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", @@ -285,6 +375,16 @@ "@babel/plugin-syntax-async-generators": "^7.2.0" } }, + "@babel/plugin-proposal-decorators": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.0.tgz", + "integrity": "sha512-d08TLmXeK/XbgCo7ZeZ+JaeZDtDai/2ctapTRsWWkkmy7G/cqz8DQN/HlWG7RR4YmfXxmExsbU3SuCjlM7AtUg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.4.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-decorators": "^7.2.0" + } + }, "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", @@ -330,6 +430,14 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-decorators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz", + "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", diff --git a/package.json b/package.json index b345cbb98..a9fb7d3be 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "@babel/cli": "^7.1.5", "@babel/core": "^7.1.6", + "@babel/plugin-proposal-decorators": "^7.4.0", "@babel/preset-env": "^7.1.6", "@iarna/toml": "^2.2.1", "app-conf": "^0.6.1", diff --git a/src/_xoConnection.js b/src/_xoConnection.js new file mode 100644 index 000000000..ba93400c3 --- /dev/null +++ b/src/_xoConnection.js @@ -0,0 +1,151 @@ +/* eslint-env jest */ +import defer from "golike-defer"; +import Xo from "xo-lib"; +import XoCollection from "xo-collection"; +import { find, forOwn } from "lodash"; + +import config from "./_config"; + +class XoConnection extends Xo { + constructor(opts) { + super(opts); + + const objects = (this._objects = new XoCollection()); + const watchers = (this._watchers = {}); + this._tempResourceDisposers = []; + + this.on("notification", ({ method, params }) => { + if (method !== "all") { + return; + } + + const fn = params.type === "exit" ? objects.unset : objects.set; + forOwn(params.items, (item, id) => { + fn.call(objects, id, item); + + const watcher = watchers[id]; + if (watcher !== undefined) { + watcher(item); + delete watchers[id]; + } + }); + }); + } + + get objects() { + return this._objects; + } + + async _fetchObjects() { + const { _objects: objects, _watchers: watchers } = this; + forOwn(await this.call("xo.getAllObjects"), (object, id) => { + objects.set(id, object); + + const watcher = watchers[id]; + if (watcher !== undefined) { + watcher(object); + delete watchers[id]; + } + }); + } + + // TODO: integrate in xo-lib. + waitObject(id) { + return new Promise(resolve => { + this._watchers[id] = resolve; + }); // FIXME: work with multiple listeners. + } + + async getOrWaitObject(id) { + const object = this._objects.all[id]; + if (object !== undefined) { + return object; + } + return this.waitObject(id); + } + + @defer + async connect($defer, credentials = config.adminCredentials) { + await this.open(); + $defer.onFailure(() => this.close()); + + await this.signIn(credentials); + await this._fetchObjects(); + + return this; + } + + async waitObjectState(id, predicate) { + let obj = this._objects.all[id]; + while (true) { + try { + await predicate(obj); + return; + } catch (_) {} + // If failed, wait for next object state/update and retry. + obj = await this.waitObject(id); + } + } + + async createUser(params) { + const id = await this.call("user.create", params); + this._tempResourceDisposers.push("user.delete", { id }); + return id; + } + + async getUser(id) { + return find(await super.call("user.getAll"), { id }); + } + + async createTempJob(params) { + const id = await this.call("job.create", { job: params }); + this._tempResourceDisposers.push("job.delete", { id }); + return id; + } + + async createTempBackupNgJob(params) { + const job = await this.call("backupNg.createJob", params); + this._tempResourceDisposers.push("backupNg.deleteJob", { id: job.id }); + return job; + } + + async deleteTempResources() { + const disposers = this._tempResourceDisposers; + for (let n = disposers.length - 1; n > 0; ) { + const params = disposers[n--]; + const method = disposers[n--]; + await this.call(method, params).catch(error => { + console.warn("deleteTempResources", method, params, error); + }); + } + disposers.length = 0; + } +} + +const getConnection = credentials => { + const xo = new XoConnection({ url: config.xoServerUrl }); + return xo.connect(credentials); +}; + +let xo; +beforeAll(async () => { + xo = await getConnection(); +}); +afterAll(async () => { + await xo.close(); + xo = null; +}); +afterEach(() => xo.deleteTempResources()); + +export { xo as default }; + +export const testConnection = ({ credentials }) => + getConnection(credentials).then(connection => connection.close()); + +export const testWithOtherConnection = defer( + async ($defer, credentials, functionToExecute) => { + const xoUser = await getConnection(credentials); + $defer(() => xoUser.close()); + await functionToExecute(xoUser); + } +); diff --git a/src/_xoWithTestHelpers.js b/src/_xoWithTestHelpers.js deleted file mode 100644 index f388b9067..000000000 --- a/src/_xoWithTestHelpers.js +++ /dev/null @@ -1,59 +0,0 @@ -import { find } from "lodash"; -import Xo from "xo-lib"; - -export default class XoWithTestHelpers extends Xo { - constructor(opts) { - super(opts); - this._tempResourcesByClass = { - backupNg: [], - job: [], - user: [], - }; - } - - async _createTempResources(class_, method, params) { - const result = await super.call(`${class_}.${method}`, params); - this._tempResourcesByClass[class_].push(result.id || result); - return result; - } - - async _deleteTempResources(class_, method, params) { - const tempResources = this._tempResourcesByClass[class_]; - await Promise.all( - tempResources.map(id => - super - .call(`${class_}.${method}`, { id, ...params }) - .catch(error => console.error(error)) - ) - ); - tempResources.length = 0; - } - - createUser(params) { - return this._createTempResources("user", "create", params); - } - - deleteAllUsers() { - return this._deleteTempResources("user", "delete"); - } - - async getUser(id) { - return find(await super.call("user.getAll"), { id }); - } - - createTempJob(params) { - return this._createTempResources("job", "create", { job: params }); - } - - deleteTempJobs() { - return this._deleteTempResources("job", "delete"); - } - - createTempBackupNgJob(params) { - return this._createTempResources("backupNg", "createJob", params); - } - - deleteTempBackupNgJobs() { - return this._deleteTempResources("backupNg", "deleteJob"); - } -} diff --git a/src/backupNg/backupNg.spec.js b/src/backupNg/backupNg.spec.js index 7f6800a58..5e4635322 100644 --- a/src/backupNg/backupNg.spec.js +++ b/src/backupNg/backupNg.spec.js @@ -5,7 +5,7 @@ import { noSuchObject } from "xo-common/api-errors"; import config from "../_config"; import randomId from "../_randomId"; -import { xo } from "../util"; +import xo from "../_xoConnection"; describe("backupNg", () => { let defaultBackupNg; diff --git a/src/job/job.spec.js b/src/job/job.spec.js index d37b84643..055feb069 100644 --- a/src/job/job.spec.js +++ b/src/job/job.spec.js @@ -3,7 +3,7 @@ import { difference, keyBy, omit } from "lodash"; import config from "../_config"; -import { testWithOtherConnection, waitObjectState, xo } from "../util"; +import xo, { testWithOtherConnection } from "../_xoConnection"; const ADMIN_USER = { email: "admin2@admin.net", @@ -181,8 +181,7 @@ describe("job", () => { const jobId = await xo.createTempJob(defaultJob); const snapshots = xo.objects.all[config.vmIdXoTest].snapshots; await xo.call("job.runSequence", { idSequence: [jobId] }); - await waitObjectState( - xo, + await xo.waitObjectState( config.vmIdXoTest, ({ snapshots: actualSnapshots }) => { expect(actualSnapshots.length).toBe(snapshots.length + 1); diff --git a/src/user/user.spec.js b/src/user/user.spec.js index b38241683..47c962afb 100644 --- a/src/user/user.spec.js +++ b/src/user/user.spec.js @@ -1,7 +1,8 @@ /* eslint-env jest */ import { forOwn, keyBy, omit } from "lodash"; -import { testConnection, testWithOtherConnection, xo } from "../util"; + +import xo, { testConnection, testWithOtherConnection } from "../_xoConnection"; const SIMPLE_USER = { email: "wayne3@vates.fr", diff --git a/src/util.js b/src/util.js index 1925ea833..cf4526d41 100644 --- a/src/util.js +++ b/src/util.js @@ -1,81 +1,7 @@ -import defer from "golike-defer"; import expect from "must"; -import XoCollection from "xo-collection"; import { find, forEach, map, cloneDeep } from "lodash"; import config from "./_config"; -import XoConnection from "./_xoWithTestHelpers"; - -/* eslint-env jest */ - -export const getConnection = defer( - async ({ onFailure: $onFailure }, { credentials } = {}) => { - const xo = new XoConnection({ url: config.xoServerUrl }); - await xo.open(); - $onFailure(() => xo.close()); - await xo.signIn( - credentials === undefined ? config.adminCredentials : credentials - ); - // Injects waitObject() - // - // TODO: integrate in xo-lib. - const watchers = {}; - const waitObject = (xo.waitObject = id => - new Promise(resolve => { - watchers[id] = resolve; - })); // FIXME: work with multiple listeners. - - const objects = (xo.objects = new XoCollection()); - xo.on("notification", ({ method, params }) => { - if (method !== "all") { - return; - } - - const fn = params.type === "exit" ? objects.unset : objects.set; - - forEach(params.items, (item, id) => { - fn.call(objects, id, item); - - const watcher = watchers[id]; - if (watcher) { - watcher(item); - delete watchers[id]; - } - }); - }); - forEach(await xo.call("xo.getAllObjects"), (object, id) => { - objects.set(id, object); - - const watcher = watchers[id]; - if (watcher) { - watcher(object); - delete watchers[id]; - } - }); - - xo.getOrWaitObject = async id => { - const object = objects.all[id]; - if (object) { - return object; - } - - return waitObject(id); - }; - - return xo; - } -); - -export const testConnection = opts => - getConnection(opts).then(connection => connection.close()); - -export const testWithOtherConnection = defer( - async ($defer, credentials, functionToExecute) => { - const xoUser = await getConnection({ credentials }); - $defer(() => xoUser.close()); - await functionToExecute(xoUser); - } -); export const rejectionOf = promise => promise.then( @@ -85,22 +11,6 @@ export const rejectionOf = promise => reason => reason ); -export let xo; -beforeAll(async () => { - xo = await getConnection(); -}); -afterAll(async () => { - await xo.close(); - xo = null; -}); -afterEach(async () => { - await Promise.all([ - xo.deleteAllUsers(), - xo.deleteTempJobs(), - xo.deleteTempBackupNgJobs(), - ]); -}); - // ================================================================= async function getAllUsers(xo) { @@ -234,15 +144,3 @@ export function almostEqual(actual, expected, ignoredAttributes) { }); expect(actual).to.be.eql(expected); } - -export async function waitObjectState(xo, id, predicate) { - let obj = xo.objects.all[id]; - while (true) { - try { - await predicate(obj); - return; - } catch (_) {} - // If failed, wait for next object state/update and retry. - obj = await xo.waitObject(id); - } -}