feat: add method to remove references from a thing

Fixes #6
This commit is contained in:
Karl-Philipp Wulfert
2019-04-02 18:04:38 +02:00
parent a3b16b8a37
commit 9cf6fde050
4 changed files with 142 additions and 2 deletions

10
package-lock.json generated
View File

@@ -403,6 +403,11 @@
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw==" "integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw=="
}, },
"@types/traverse": {
"version": "0.6.32",
"resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.32.tgz",
"integrity": "sha512-RBz2uRZVCXuMg93WD//aTS5B120QlT4lR/gL+935QtGsKHLS6sCtZBaKfWjIfk7ZXv/r8mtGbwjVIee6/3XTow=="
},
"@types/uuid": { "@types/uuid": {
"version": "3.4.4", "version": "3.4.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz",
@@ -5227,6 +5232,11 @@
} }
} }
}, },
"traverse": {
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
"integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc="
},
"trim-newlines": { "trim-newlines": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz",

View File

@@ -19,11 +19,13 @@
"@types/cli-progress": "1.8.1", "@types/cli-progress": "1.8.1",
"@types/node": "10.14.4", "@types/node": "10.14.4",
"@types/request": "2.48.1", "@types/request": "2.48.1",
"@types/traverse": "0.6.32",
"@types/uuid": "3.4.4", "@types/uuid": "3.4.4",
"cli-progress": "2.1.1", "cli-progress": "2.1.1",
"commander": "2.19.0", "commander": "2.19.0",
"moment": "2.24.0", "moment": "2.24.0",
"request": "2.88.0", "request": "2.88.0",
"traverse": "0.6.6",
"uuid": "3.3.2" "uuid": "3.3.2"
}, },
"license": "GPL-3.0-only", "license": "GPL-3.0-only",

View File

@@ -14,6 +14,8 @@
*/ */
import {asyncPool} from '@krlwlfrt/async-pool'; import {asyncPool} from '@krlwlfrt/async-pool';
import { import {
isThing,
SCAssociatedThingWithoutReferences,
SCBulkResponse, SCBulkResponse,
SCBulkRoute, SCBulkRoute,
SCLicensePlate, SCLicensePlate,
@@ -68,6 +70,76 @@ export class ConnectorClient extends Client {
return v5(uid.toString(), SCNamespaces[namespaceId]); return v5(uid.toString(), SCNamespaces[namespaceId]);
} }
/**
* Remove fields from a thing that are references
*
* This effectively turns a thing into a thing without references, e.g. SCDish into SCDishWithoutReferences.
*
* @param thing Thing to remove references from
*/
static removeReferences<THING extends SCThings>(thing: THING): SCAssociatedThingWithoutReferences<THING> {
const thingWithoutReferences: any = {
...{},
...thing,
};
// iterate over all properties
for (const key in thingWithoutReferences) {
/* istanbul ignore if */
if (!thingWithoutReferences.hasOwnProperty(key)) {
continue;
}
const property = thingWithoutReferences[key];
// check if property itself is a thing
if (isThing(property)) {
// delete said property
delete thingWithoutReferences[key];
continue;
}
// check if property is an array
if (Array.isArray(property)) {
if (property.every(isThing)) {
// delete property if every item in it is a thing
delete thingWithoutReferences[key];
} else {
// check every item in array
for (const item of property) {
if (['boolean', 'number', 'string'].indexOf(typeof item) >= 0) {
// skip primitives
continue;
}
// check every property
for (const itemKey in item) {
/* istanbul ignore if */
if (!item.hasOwnProperty(itemKey)) {
continue;
}
if (isThing(item[itemKey])) {
// delete properties that are things
delete item[itemKey];
}
}
}
}
} else if (typeof property === 'object') {
// iterate over all properties in nested objects
for (const nestedKey in property) {
if (isThing(property[nestedKey])) {
// delete properties that are things
delete property[nestedKey];
}
}
}
}
return thingWithoutReferences as SCAssociatedThingWithoutReferences<THING>;
}
/** /**
* Request a bulk transfer to the backend * Request a bulk transfer to the backend
* *

View File

@@ -12,7 +12,9 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {asyncPool} from '@krlwlfrt/async-pool';
import { import {
isThing,
SCBulkAddResponse, SCBulkAddResponse,
SCBulkAddRoute, SCBulkAddRoute,
SCBulkDoneResponse, SCBulkDoneResponse,
@@ -20,17 +22,22 @@ import {
SCBulkResponse, SCBulkResponse,
SCBulkRoute, SCBulkRoute,
SCMessage, SCMessage,
SCThing,
SCThingOriginType,
SCThingType,
SCThingUpdateResponse, SCThingUpdateResponse,
SCThingUpdateRoute, SCThingUpdateRoute,
SCThingType,
SCThingOriginType,
} from '@openstapps/core'; } from '@openstapps/core';
import * as chai from 'chai'; import * as chai from 'chai';
import {expect} from 'chai'; import {expect} from 'chai';
import * as chaiAsPromised from 'chai-as-promised'; import * as chaiAsPromised from 'chai-as-promised';
import * as chaiSpies from 'chai-spies'; import * as chaiSpies from 'chai-spies';
import {readdir, readFile} from 'fs';
import {suite, test} from 'mocha-typescript'; import {suite, test} from 'mocha-typescript';
import * as moment from 'moment'; import * as moment from 'moment';
import {join, resolve} from 'path';
import * as traverse from 'traverse';
import {promisify} from 'util';
import {ConnectorClient} from '../src/connectorClient'; import {ConnectorClient} from '../src/connectorClient';
import {EmptyBulkError, NamespaceNotDefinedError} from '../src/errors'; import {EmptyBulkError, NamespaceNotDefinedError} from '../src/errors';
import {HttpClient} from '../src/httpClient'; import {HttpClient} from '../src/httpClient';
@@ -47,8 +54,27 @@ const bulkDoneRoute = new SCBulkDoneRoute();
const bulkRoute = new SCBulkRoute(); const bulkRoute = new SCBulkRoute();
const thingUpdateRoute = new SCThingUpdateRoute(); const thingUpdateRoute = new SCThingUpdateRoute();
const readdirPromisified = promisify(readdir);
const readFilePromisified = promisify(readFile);
const httpClient = new HttpClient(); const httpClient = new HttpClient();
/**
* Check if something contains things
*
* @param thing Thing to check
*/
function doesContainThings<T extends SCThing>(thing: T): boolean {
/* tslint:disable-next-line:only-arrow-functions */
return traverse(thing).reduce(function(sum, item) {
if (this.isRoot) {
return false;
}
return sum || (item === null) ? false : isThing(item);
}, false);
}
@suite() @suite()
export class ConnectorClientSpec { export class ConnectorClientSpec {
async after() { async after() {
@@ -305,6 +331,36 @@ export class ConnectorClientSpec {
}).to.throw(NamespaceNotDefinedError); }).to.throw(NamespaceNotDefinedError);
} }
@test
async removeReferences() {
const pathToTestFiles = resolve(
__dirname,
'..',
'node_modules',
'@openstapps',
'core',
'test',
'resources',
);
const testFiles = await readdirPromisified(pathToTestFiles);
const testInstances = await asyncPool(5, testFiles, async (testFile) => {
const buffer = await readFilePromisified(join(pathToTestFiles, testFile));
const content = JSON.parse(buffer.toString());
return content.instance;
});
for (const testInstance of testInstances) {
const testInstanceWithoutReferences = ConnectorClient.removeReferences(testInstance);
expect(doesContainThings(testInstanceWithoutReferences)).to.be.equal(false, JSON.stringify(
[testInstance, testInstanceWithoutReferences],
null,
2,
));
}
}
@test @test
async update() { async update() {
const message: SCMessage = { const message: SCMessage = {