chore(xoConnection): various changes (#95)
This commit is contained in:
@@ -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
108
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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
151
src/_xoConnection.js
Normal 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);
|
||||
}
|
||||
);
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
102
src/util.js
102
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user