/* * Copyright (C) 2018 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import {asyncPool} from '@krlwlfrt/async-pool/lib/async-pool'; import { isThing, SCBulkAddResponse, SCBulkAddRoute, SCBulkDoneResponse, SCBulkDoneRoute, SCBulkResponse, SCBulkRoute, SCMessage, SCThingOriginType, SCThingType, SCThingUpdateResponse, SCThingUpdateRoute, SCThingWithoutReferences, } from '@openstapps/core'; import chai from 'chai'; import {expect} from 'chai'; import chaiAsPromised from 'chai-as-promised'; import chaiSpies from 'chai-spies'; import clone = require('rfdc'); import {readdir, readFile} from 'fs'; import {suite, test} from '@testdeck/mocha'; import moment from 'moment'; import {join, resolve} from 'path'; import traverse from 'traverse'; import {promisify} from 'util'; import {ConnectorClient} from '../src/connector-client'; import {EmptyBulkError, NamespaceNotDefinedError} from '../src/errors'; import {HttpClient} from '../src/http-client'; import {HttpClientRequest, HttpClientResponse} from '../src/http-client-interface'; chai.should(); chai.use(chaiSpies); chai.use(chaiAsPromised); const sandbox = chai.spy.sandbox(); const bulkAddRoute = new SCBulkAddRoute(); const bulkDoneRoute = new SCBulkDoneRoute(); const bulkRoute = new SCBulkRoute(); const thingUpdateRoute = new SCThingUpdateRoute(); const readdirPromisified = promisify(readdir); const readFilePromisified = promisify(readFile); const httpClient = new HttpClient(); /** * Check if something contains things * * @param thing Thing to check */ function doesContainThings(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() export class ConnectorClientSpec { async after() { sandbox.restore(); } @test async bulk() { sandbox.on(httpClient, 'request', async (): Promise> => { return { body: { expiration: moment().add(1800, 'seconds').format(), source: 'foo', state: 'in progress', type: SCThingType.Message, uid: 'foo', }, headers: {}, statusCode: bulkRoute.statusCodeSuccess, }; }); expect(httpClient.request).not.to.have.been.called(); const connectorClient = new ConnectorClient(httpClient, 'http://localhost'); await connectorClient.bulk(SCThingType.Message, 'foo', 1800); expect(httpClient.request).to.have.been.first.called.with({ body: { expiration: moment().add(1800, 'seconds').format(), source: 'foo', type: SCThingType.Message, }, headers: { "Content-Type": "application/json", }, method: bulkRoute.method, url: new URL('http://localhost' + bulkRoute.getUrlPath()), }); } @test async bulkWithoutTimeout() { sandbox.on(httpClient, 'request', async (): Promise> => { return { body: { expiration: moment().add(3600, 'seconds').format(), source: 'foo', state: 'in progress', type: SCThingType.Message, uid: 'foo', }, headers: {}, statusCode: bulkRoute.statusCodeSuccess, }; }); expect(httpClient.request).not.to.have.been.called(); const connectorClient = new ConnectorClient(httpClient, 'http://localhost'); await connectorClient.bulk(SCThingType.Message, 'foo'); expect(httpClient.request).to.have.been.first.called.with({ body: { expiration: moment().add(3600, 'seconds').format(), source: 'foo', type: SCThingType.Message, }, headers: { "Content-Type": "application/json", }, method: bulkRoute.method, url: new URL('http://localhost' + bulkRoute.getUrlPath()), }); } @test async index() { const messages: SCMessage[] = [ { audiences: [ 'employees', ], categories: [ 'news' ], messageBody: 'Lorem ipsum.', name: 'foo', origin: { indexed: 'foo', name: 'foo', type: SCThingOriginType.Remote, }, type: SCThingType.Message, uid: 'foo', }, { audiences: [ 'employees', ], categories: [ 'news' ], messageBody: 'Lorem ipsum.', name: 'foo', origin: { indexed: 'foo', name: 'foo', type: SCThingOriginType.Remote, }, type: SCThingType.Message, uid: 'bar', }, ]; type responses = SCBulkResponse | SCBulkAddResponse | SCBulkDoneResponse; sandbox.on(httpClient, 'request', async (request: HttpClientRequest) : Promise> => { if (request.url.toString() === new URL('http://localhost' + bulkRoute.getUrlPath()).toString()) { return { body: { expiration: moment().add(3600, 'seconds').format(), source: 'copy', state: 'in progress', type: SCThingType.Message, uid: 'foo', }, headers: {}, statusCode: bulkRoute.statusCodeSuccess, }; } else if (request.url.toString() === new URL('http://localhost' + bulkAddRoute.getUrlPath({ UID: 'foo', })).toString()) { return { body: {}, headers: {}, statusCode: bulkAddRoute.statusCodeSuccess, }; } return { body: {}, headers: {}, statusCode: bulkDoneRoute.statusCodeSuccess, }; }); const connectorClient = new ConnectorClient(httpClient, 'http://localhost'); await connectorClient.index(messages, 'copy'); expect(httpClient.request).to.have.been.first.called.with({ body: { expiration: moment().add(3600, 'seconds').format(), source: 'copy', type: SCThingType.Message, }, headers: { "Content-Type": "application/json", }, method: bulkRoute.method, url: new URL('http://localhost' + bulkRoute.getUrlPath()), }); } @test async indexFails() { const connectorClient = new ConnectorClient(httpClient, 'http://localhost'); return connectorClient.index([]).should.be.rejectedWith(EmptyBulkError); } @test async indexWithoutSource() { const messages: SCMessage[] = [ { audiences: [ 'employees', ], categories: [ 'news' ], messageBody: 'Lorem ipsum.', name: 'foo', origin: { indexed: 'foo', name: 'foo', type: SCThingOriginType.Remote, }, type: SCThingType.Message, uid: 'foo', }, { audiences: [ 'employees', ], categories: [ 'news' ], messageBody: 'Lorem ipsum.', name: 'foo', origin: { indexed: 'foo', name: 'foo', type: SCThingOriginType.Remote, }, type: SCThingType.Message, uid: 'bar', }, ]; type responses = SCBulkResponse | SCBulkAddResponse | SCBulkDoneResponse; sandbox.on(httpClient, 'request', async (request: HttpClientRequest) : Promise> => { if (request.url.toString() === new URL('http://localhost' + bulkRoute.getUrlPath()).toString()) { return { body: { expiration: moment().add(3600, 'seconds').format(), source: 'stapps-api', state: 'in progress', type: SCThingType.Message, uid: 'foo', }, headers: {}, statusCode: bulkRoute.statusCodeSuccess, }; } else if (request.url.toString() === new URL('http://localhost' + bulkAddRoute.getUrlPath({ UID: 'foo', })).toString()) { return { body: {}, headers: {}, statusCode: bulkAddRoute.statusCodeSuccess, }; } return { body: {}, headers: {}, statusCode: bulkDoneRoute.statusCodeSuccess, }; }); const connectorClient = new ConnectorClient(httpClient, 'http://localhost'); await connectorClient.index(messages); expect(httpClient.request).to.have.been.first.called.with({ body: { expiration: moment().add(3600, 'seconds').format(), source: 'stapps-api', type: SCThingType.Message, }, headers: { "Content-Type": "application/json", }, method: bulkRoute.method, url: new URL('http://localhost' + bulkRoute.getUrlPath()), }); } @test makeUuid() { const uuid = ConnectorClient.makeUUID('foo', 'b-tu'); expect(uuid).to.be.equal('abad271e-d9e9-5802-b7bc-96d8a647b451'); expect(ConnectorClient.makeUUID('bar', 'b-tu')).not.to.be.equal(uuid); expect(ConnectorClient.makeUUID('foo', 'f-u')).not.to.be.equal(uuid); } @test makeUuidFails() { expect(() => { ConnectorClient.makeUUID('foo', 'b-u'); }).to.throw(NamespaceNotDefinedError); } @test async removeReferences() { const pathToTestFiles = resolve( __dirname, '..', 'node_modules', '@openstapps', 'core', 'test', 'resources', 'indexable' ); 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 checkInstance = clone()(testInstance); const testInstanceWithoutReferences = ConnectorClient.removeReferences(testInstance); expect(doesContainThings(testInstanceWithoutReferences)).to.be .equal(false, JSON.stringify( [testInstance, testInstanceWithoutReferences], null, 2, )); expect((testInstanceWithoutReferences as any).origin).to.be .equal(undefined, JSON.stringify( [testInstance, testInstanceWithoutReferences], null, 2, )); expect(testInstance).to.be.deep .equal(checkInstance, 'Removing the references of a thing could have side effects because no deep copy is used'); } } @test async removeUndefinedProperties() { const objectWithUndefinedProperties = {value: 'foo', novalue: undefined, nested: { value: 'foo', novalue: undefined}, }; const objectWithoutUndefinedProperties = {value: 'foo', nested: { value: 'foo'}, }; ConnectorClient.removeUndefinedProperties(objectWithUndefinedProperties); expect(objectWithUndefinedProperties).to.deep.equal(objectWithoutUndefinedProperties, JSON.stringify( [objectWithUndefinedProperties, objectWithoutUndefinedProperties], null, 2, )); } @test async update() { const message: SCMessage = { audiences: [ 'employees', ], categories: [ 'news' ], messageBody: 'Lorem ipsum.', name: 'foo', origin: { indexed: 'foo', name: 'foo', type: SCThingOriginType.Remote, }, type: SCThingType.Message, uid: 'foo', }; sandbox.on(httpClient, 'request', async (): Promise> => { return { body: {}, headers: {}, statusCode: thingUpdateRoute.statusCodeSuccess, }; }); expect(httpClient.request).not.to.have.been.called(); const connectorClient = new ConnectorClient(httpClient, 'http://localhost'); await connectorClient.update(message); expect(httpClient.request).to.have.been.called.with({ body: message, headers: { "Content-Type": "application/json", }, method: thingUpdateRoute.method, url: new URL('http://localhost' + thingUpdateRoute.getUrlPath({ TYPE: SCThingType.Message, UID: 'foo', })), }); } }