chore(xoConnection): various changes (#95)

This commit is contained in:
badrAZ
2019-04-15 09:22:53 +02:00
committed by GitHub
parent 876850a7a7
commit a64960ddd0
9 changed files with 271 additions and 166 deletions

View File

@@ -12,6 +12,12 @@ module.exports = {
"prettier/standard",
],
parser: "babel-eslint",
parserOptions: {
ecmaFeatures: {
legacyDecorators: true,
},
},
rules: {
// prefer let/const over var
"no-var": "error",

108
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

151
src/_xoConnection.js Normal file
View File

@@ -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);
}
);

View File

@@ -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");
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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",

View File

@@ -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);
}
}