diff --git a/src/cli.ts b/src/cli.ts index 93626964..447e5fa4 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,13 +12,14 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ +import {SCThingType} from '@openstapps/core'; import {Logger} from '@openstapps/logger'; import * as commander from 'commander'; import {readFileSync} from 'fs'; import {join} from 'path'; import {URL} from 'url'; import {copy} from './copy'; -import {indexSamples} from './e2e'; +import {e2eRun} from './e2e'; import {HttpClient} from './http-client'; const pkgJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json')) @@ -34,7 +35,7 @@ process.on('unhandledRejection', async (error) => { commander .command('e2e ') - .description('Run in end to end test mode. Indexing all test files from @openstapp/core to the backend') + .description('Run in end to end test mode. Indexing and afterwards retrieving all test files from @openstapp/core to the backend') .option('-s --samples [path]', 'Path to @openstapp/core test files', './node_modules/@openstapps/core/test/resources') .action(async (to, e2eCommand) => { let toURL = ''; @@ -49,7 +50,7 @@ commander actionDone = true; - indexSamples(client, {to: toURL, samples: e2eCommand.samples}) + e2eRun(client, {to: toURL, samplesLocation: e2eCommand.samples}) .then(() => { Logger.ok('Done'); }) @@ -108,7 +109,7 @@ commander from: fromURL, source: copyCommand.bulkSource, to: toURL, - type: type, + type: type as SCThingType, version: copyCommand.appVersion, }) .then(() => { diff --git a/src/e2e.ts b/src/e2e.ts index 699b21e9..80748be0 100644 --- a/src/e2e.ts +++ b/src/e2e.ts @@ -13,13 +13,18 @@ * this program. If not, see . */ -import {SCThings, SCThingType} from '@openstapps/core'; +import {SCSearchRequest, SCThings, SCThingType} from '@openstapps/core'; +import {Logger} from '@openstapps/logger'; +import {deepStrictEqual} from 'assert'; import {readdir, readFile} from 'fs'; import {join} from 'path'; import {promisify} from 'util'; import {ConnectorClient} from './connector-client'; import {HttpClientInterface} from './http-client-interface'; +const localItemMap: Map = new Map(); +const remoteItemMap: Map = new Map(); + /** * Options to set up indexing core test files to backend */ @@ -27,7 +32,7 @@ export interface E2EOptions { /** * File path of the directory containing core test files */ - samples: string; + samplesLocation: string; /** * URL of the backend to index to @@ -36,18 +41,73 @@ export interface E2EOptions { } /** - * Function to add all the SCThings that getItemsFromSamples() returns to the backend - * - * @param client HTTP client - * @param options Map of options + * Function that can be used for integration tests. + * Adds all the SCThings that getItemsFromSamples() returns to the backend. + * Afterwards retrieves the items from backend and checks for differences with original ones. */ -export async function indexSamples(client: HttpClientInterface, options: E2EOptions): Promise { - const api = new ConnectorClient(client, options.to); +export async function e2eRun(client: HttpClientInterface, options: E2EOptions): Promise { + localItemMap.clear(); + remoteItemMap.clear(); - const items = await getItemsFromSamples(options.samples); + const api = new ConnectorClient(client, options.to); + try { + await indexSamples(api, options); + Logger.info(`All samples have been indexed via the backend`); + + await retrieveItems(api); + Logger.info(`All samples have been retrieved from the backend`); + compareItems(); + } catch (error) { + throw error; + } +} + +/** + * Retieves all samples previously index using the api + */ +async function retrieveItems(api: ConnectorClient): Promise { + const singleItemSearchRequest: SCSearchRequest = { + filter: { + arguments: { + field: 'uid', + value: 'replace-me', + }, + type: 'value', + }, + }; + for (const uid of localItemMap.keys()) { + singleItemSearchRequest.filter!.arguments.value = uid; + const searchResonse = await api.search(singleItemSearchRequest); + if (searchResonse.data.length !== 1) { + throw Error(`Search for single SCThing with uid: ${uid} returned ${searchResonse.data.length} results`); + } + remoteItemMap.set(uid, searchResonse.data[0]); + } +} + +/** + * Compares all samples (local and remote) with the same uid and throws if they're not deep equal + */ +function compareItems() { + for (const localThing of localItemMap.values()) { + /* istanbul ignore next retrieveItems will throw before*/ + if (!remoteItemMap.has(localThing.uid)) { + throw Error(`Did not retrieve expected SCThing with uid: ${localThing.uid}`); + } + const remoteThing = remoteItemMap.get(localThing.uid); + deepStrictEqual(remoteThing, localThing, `Unexpected difference between original and retrieved sample`); + } + Logger.info(`All samples retrieved from the backend are the same (deep equal) as the original ones submitted`); +} +/** + * Function to add all the SCThings that getItemsFromSamples() returns to the backend + */ +async function indexSamples(api: ConnectorClient, options: E2EOptions): Promise { + + const items = await getItemsFromSamples(options.samplesLocation); if (items.length === 0) { - throw new Error('Could not index samples. None were retrived from the file system.'); + throw new Error('Could not index samples. None were retrieved from the file system.'); } try { @@ -57,17 +117,14 @@ export async function indexSamples(client: HttpClientInterface, options: E2EOpti if (!itemMap.has(item.type)) { itemMap.set(item.type, []); } - const currentItems = itemMap.get(item.type) as SCThings[]; - currentItems.push(item); - itemMap.set(item.type, currentItems); + const itemsOfSameType = itemMap.get(item.type) as SCThings[]; + itemsOfSameType.push(item); + itemMap.set(item.type, itemsOfSameType); + localItemMap.set(item.uid, item); } // add items depending on their type property with one type per bulk for (const type of itemMap.keys()) { - const currentBulk = await api.bulk(type, 'stapps-core-sample-data'); - for (const item of (itemMap.get(type) as SCThings[])) { - await currentBulk!.add(item); - } - await currentBulk.done(); + await api.index(itemMap.get(type) as SCThings[], 'stapps-core-sample-data'); } } catch (err) { throw err; diff --git a/test/e2e.spec.ts b/test/e2e.spec.ts index 56b77ba1..cfa1ccb0 100644 --- a/test/e2e.spec.ts +++ b/test/e2e.spec.ts @@ -12,6 +12,9 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ + +// tslint:disable-next-line: max-line-length +// tslint:disable: completed-docs no-implicit-dependencies prefer-function-over-method newline-per-chained-call member-ordering import { SCBulkAddResponse, SCBulkAddRoute, @@ -19,19 +22,23 @@ import { SCBulkDoneRoute, SCBulkResponse, SCBulkRoute, + SCSearchResponse, + SCSearchRoute, SCThing, + SCThings, } from '@openstapps/core'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as chaiSpies from 'chai-spies'; +import clone = require('fast-clone'); import {existsSync, mkdirSync, rmdirSync, unlinkSync} from 'fs'; +import {createFileSync} from 'fs-extra'; import {suite, test} from 'mocha-typescript'; import {join} from 'path'; -import {getItemsFromSamples, indexSamples} from '../src/e2e'; +import {e2eRun, getItemsFromSamples} from '../src/e2e'; import {ApiError} from '../src/errors'; import {HttpClient, RequestOptions, Response} from '../src/http-client'; import {RecursivePartial} from './client.spec'; -import {createFileSync} from 'fs-extra'; chai.should(); chai.use(chaiSpies); @@ -43,8 +50,12 @@ const bulkRoute = new SCBulkRoute(); const bulkAddRoute = new SCBulkAddRoute(); const bulkDoneRoute = new SCBulkDoneRoute(); +const searchRoute = new SCSearchRoute(); + const httpClient = new HttpClient(); +const storedThings: Map = new Map(); + @suite export class E2EConnectorSpec { async after() { @@ -52,18 +63,16 @@ export class E2EConnectorSpec { } @test - getCoreTestSamples() { - return getItemsFromSamples('./node_modules/@openstapps/core/test/resources') - .then((items: T[]) => { - // tslint:disable-next-line: no-unused-expression - chai.expect(items).to.not.be.a.instanceof(Error); - // tslint:disable-next-line: no-unused-expression - chai.expect(items).to.not.be.empty; - }); + async getCoreTestSamples() { + const items = await getItemsFromSamples('./node_modules/@openstapps/core/test/resources'); + // tslint:disable-next-line: no-unused-expression + chai.expect(items).to.not.be.a.instanceof(Error); + // tslint:disable-next-line: no-unused-expression + chai.expect(items).to.not.be.empty; } @test - getCoreTestSamplesShouldFail() { + async getCoreTestSamplesShouldFail() { return getItemsFromSamples('./nonexistantdirectory') .then((items: T[]) => { // tslint:disable-next-line: no-unused-expression @@ -72,11 +81,15 @@ export class E2EConnectorSpec { } @test - async index() { - type responses = Response; + async e2eRunSimulation() { + type responses = Response; + + let failOnCompare = false; + let failOnLookup = false; sandbox.on(httpClient, 'request', async (request: RequestOptions): Promise> => { - if (request.url.toString() === 'http://localhost' + bulkRoute.getUrlFragment().toString()) { + if (request.url.toString() === `http://localhost${bulkRoute.getUrlFragment().toString()}`) { + return { body: { state: 'in progress', @@ -84,21 +97,70 @@ export class E2EConnectorSpec { }, statusCode: bulkRoute.statusCodeSuccess, }; - } else if (request.url.toString() === 'http://localhost' + bulkAddRoute.getUrlFragment({ - UID: 'foo', - }).toString()) { + } + + if (request.url.toString() === `http://localhost${bulkAddRoute.getUrlFragment({UID: 'foo'}).toString()}`) { + storedThings.set(request.body.uid, clone(request.body)); + return { body: {}, statusCode: bulkAddRoute.statusCodeSuccess, }; } + if (request.url.toString() === `http://localhost${bulkDoneRoute.getUrlFragment({UID: 'foo'}).toString()}`) { + return { + body: {}, + statusCode: bulkDoneRoute.statusCodeSuccess, + }; + } + + if (request.url.toString() === `http://localhost${searchRoute.getUrlFragment().toString()}`) { + const thing = storedThings.get(request.body.filter.arguments.value); + if (failOnCompare) { + thing!.origin!.modified = 'altered'; + } + const returnThing = failOnLookup ? [] : [thing]; + const returnBody = { + data: returnThing, + facets: [], + pagination: { + count: returnThing.length, + offset: 0, + total: returnThing.length, + }, + stats: { + time: 42, + }, + }; + + return { + body: returnBody, + statusCode: searchRoute.statusCodeSuccess, + }; + } + return { body: {}, - statusCode: bulkDoneRoute.statusCodeSuccess, + statusCode: searchRoute.statusCodeSuccess, }; }); - await indexSamples(httpClient, {to: 'http://localhost', samples: './node_modules/@openstapps/core/test/resources'}); + + // tslint:disable-next-line: max-line-length + await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: './node_modules/@openstapps/core/test/resources'}); + + failOnLookup = true; + failOnCompare = false; + // tslint:disable-next-line: max-line-length + await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: './node_modules/@openstapps/core/test/resources'}) + .should.be.rejectedWith('Search for single SCThing with uid'); + + failOnLookup = false; + failOnCompare = true; + // tslint:disable-next-line: max-line-length + await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: './node_modules/@openstapps/core/test/resources'}) + .should.be.rejectedWith('Unexpected difference'); + } @test @@ -106,36 +168,40 @@ export class E2EConnectorSpec { type responses = Response; sandbox.on(httpClient, 'request', async (): Promise> => { + return { body: {}, statusCode: Number.MAX_SAFE_INTEGER, }; }); - return indexSamples(httpClient, {to: 'http://localhost', samples: './node_modules/@openstapps/core/test/resources'}) + + // tslint:disable-next-line: max-line-length + return e2eRun(httpClient, {to: 'http://localhost', samplesLocation: './node_modules/@openstapps/core/test/resources'}) .should.be.rejectedWith(ApiError); } @test async indexShouldFailDirectoryWithoutData() { const emptyDirPath = join(__dirname, 'emptyDir'); - if(!existsSync(emptyDirPath)) + if (!existsSync(emptyDirPath)) { mkdirSync(emptyDirPath); - await indexSamples(httpClient, {to: 'http://localhost', samples: emptyDirPath}) - .should.be.rejectedWith('Could not index samples. None were retrived from the file system.'); + } + await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: emptyDirPath}) + .should.be.rejectedWith('Could not index samples. None were retrieved from the file system.'); rmdirSync(emptyDirPath); } @test async indexShouldFailDirectoryWithoutJsonData() { const somewhatFilledDirPath = join(__dirname, 'somewhatFilledDir'); - if(!existsSync(somewhatFilledDirPath)) + if (!existsSync(somewhatFilledDirPath)) { mkdirSync(somewhatFilledDirPath); + } const nonJsonFile = join (somewhatFilledDirPath, 'nonjson.txt'); createFileSync(nonJsonFile); - await indexSamples(httpClient, {to: 'http://localhost', samples: somewhatFilledDirPath}) - .should.be.rejectedWith('Could not index samples. None were retrived from the file system.'); + await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: somewhatFilledDirPath}) + .should.be.rejectedWith('Could not index samples. None were retrieved from the file system.'); unlinkSync(nonJsonFile); rmdirSync(somewhatFilledDirPath); } - }