mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-03-11 01:02:45 +00:00
feat: e2e connector
This commit is contained in:
59
backend/e2e-connector/src/cli.ts
Normal file
59
backend/e2e-connector/src/cli.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2022 Open 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import {Command} from 'commander';
|
||||
import {URL} from 'url';
|
||||
import waitOn from 'wait-on';
|
||||
import {HttpClient} from '@openstapps/api';
|
||||
import {version} from '../package.json';
|
||||
import {endToEndRun} from './end-to-end.js';
|
||||
|
||||
new Command()
|
||||
.command('<to>')
|
||||
.argument('<to>', 'The backend to test', url => new URL(url).toString(), 'http://localhost:3000')
|
||||
.version(version)
|
||||
.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/indexable',
|
||||
)
|
||||
.option('-w --waiton [resource]', 'wait-on resource parameter see "www.npmjs.com/wait-on"')
|
||||
.option('-r --reportPath [reportPath]', 'JUnit Report Path')
|
||||
// eslint-disable-next-line unicorn/prevent-abbreviations
|
||||
.action(async (to: string, e2eCommand: {samples: string; waiton?: string; reportPath?: string}) => {
|
||||
try {
|
||||
if (typeof e2eCommand.waiton === 'string') {
|
||||
Logger.info(`Waiting for availibilty of resource: ${e2eCommand.waiton}`);
|
||||
await waitOn({
|
||||
resources: [e2eCommand.waiton],
|
||||
timeout: 300_000,
|
||||
});
|
||||
Logger.info(`Resource became available`);
|
||||
}
|
||||
await endToEndRun(new HttpClient(), {
|
||||
to,
|
||||
samplesLocation: e2eCommand.samples,
|
||||
reportLocation: e2eCommand.reportPath,
|
||||
});
|
||||
Logger.ok('Done');
|
||||
} catch (error) {
|
||||
await Logger.error(error);
|
||||
}
|
||||
})
|
||||
.addHelpCommand()
|
||||
.parse(process.argv);
|
||||
20
backend/e2e-connector/src/compare-items.ts
Normal file
20
backend/e2e-connector/src/compare-items.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import junit from 'junit-report-builder';
|
||||
import {deepStrictEqual} from 'assert';
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import {TestState} from './test-state.js';
|
||||
|
||||
/**
|
||||
* Compares all samples (local and remote) with the same uid and throws if they're not deep equal
|
||||
*/
|
||||
export async function compareItems(state: TestState, suite: junit.TestSuite) {
|
||||
for (const localThing of state.localItems.values()) {
|
||||
await state.runTest(suite, `Should be the same for ${localThing.type} (${localThing.uid})`, async () => {
|
||||
if (!state.remoteItems.has(localThing.uid)) {
|
||||
throw new Error(`Did not retrieve expected SCThing with uid: ${localThing.uid}`);
|
||||
}
|
||||
const remoteThing = state.remoteItems.get(localThing.uid);
|
||||
deepStrictEqual(remoteThing, localThing, `Unexpected difference between original and retrieved sample`);
|
||||
});
|
||||
}
|
||||
Logger.info(`All samples retrieved from the backend have been compared`);
|
||||
}
|
||||
48
backend/e2e-connector/src/end-to-end.ts
Normal file
48
backend/e2e-connector/src/end-to-end.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2022 Open 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import {HttpClientInterface} from '@openstapps/api';
|
||||
import junit from 'junit-report-builder';
|
||||
import {indexSamples} from './index-samples.js';
|
||||
import {retrieveItems} from './retreive-items.js';
|
||||
import {compareItems} from './compare-items.js';
|
||||
import {E2EOptions, TestState} from './test-state.js';
|
||||
|
||||
/**
|
||||
* Function that can be used for integration tests.
|
||||
* Adds all the SCThings that getItemsFromSamples() returns to the backend.
|
||||
* Afterward, retrieves the items from backend and checks for differences with original ones.
|
||||
*/
|
||||
export async function endToEndRun(client: HttpClientInterface, options: E2EOptions): Promise<string[]> {
|
||||
const state = new TestState(client, options);
|
||||
const builder = junit.newBuilder();
|
||||
|
||||
await indexSamples(state, builder.testSuite().name('e2e index'));
|
||||
Logger.info(`All samples have been indexed via the backend`);
|
||||
|
||||
await retrieveItems(state, builder.testSuite().name('e2e retrieve'));
|
||||
Logger.info(`All samples have been retrieved from the backend`);
|
||||
|
||||
await compareItems(state, builder.testSuite().name('e2e compare'));
|
||||
|
||||
if (options.reportLocation) {
|
||||
builder.writeTo(options.reportLocation);
|
||||
}
|
||||
await (state.errors.length > 0
|
||||
? Logger.error(`\n${state.errors.length} failed test cases`)
|
||||
: Logger.ok('All tests passed.'));
|
||||
|
||||
return state.errors;
|
||||
}
|
||||
29
backend/e2e-connector/src/get-items-from-samples.ts
Normal file
29
backend/e2e-connector/src/get-items-from-samples.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {readdir, readFile} from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Get all SCThings from the predefined core test json files
|
||||
* @param samplesDirectory Filepath to the directory containing to the core test json files
|
||||
* @returns an Array of all the SCThings specified for test usage
|
||||
*/
|
||||
export async function getItemsFromSamples<T extends SCThings>(samplesDirectory: string): Promise<T[]> {
|
||||
const things: T[] = [];
|
||||
try {
|
||||
const fileNames = await readdir(samplesDirectory);
|
||||
for (const fileName of fileNames) {
|
||||
const filePath = path.join(samplesDirectory, fileName);
|
||||
if (filePath.endsWith('.json')) {
|
||||
const fileContent = await readFile(filePath, {encoding: 'utf8'});
|
||||
const schemaObject = JSON.parse(fileContent);
|
||||
if (schemaObject.errorNames.length === 0 && typeof schemaObject.instance.type === 'string') {
|
||||
things.push(schemaObject.instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return things;
|
||||
}
|
||||
33
backend/e2e-connector/src/index-samples.ts
Normal file
33
backend/e2e-connector/src/index-samples.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import junit from 'junit-report-builder';
|
||||
import {SCThings, SCThingType} from '@openstapps/core';
|
||||
import {getItemsFromSamples} from './get-items-from-samples.js';
|
||||
import {TestState} from './test-state.js';
|
||||
|
||||
/**
|
||||
* Function to add all the SCThings that getItemsFromSamples() returns to the backend
|
||||
*/
|
||||
export async function indexSamples(state: TestState, suite: junit.TestSuite): Promise<void> {
|
||||
const items = await getItemsFromSamples(state.options.samplesLocation);
|
||||
|
||||
if (items.length === 0) {
|
||||
throw new Error('Could not index samples. None were retrieved from the file system.');
|
||||
}
|
||||
|
||||
// sort items by type
|
||||
const itemMap: Map<SCThingType, SCThings[]> = new Map();
|
||||
for (const item of items) {
|
||||
if (!itemMap.has(item.type)) {
|
||||
itemMap.set(item.type, []);
|
||||
}
|
||||
const itemsOfSameType = itemMap.get(item.type) as SCThings[];
|
||||
itemsOfSameType.push(item);
|
||||
itemMap.set(item.type, itemsOfSameType);
|
||||
state.localItems.set(item.uid, item);
|
||||
}
|
||||
// add items depending on their type property with one type per bulk
|
||||
for (const type of itemMap.keys()) {
|
||||
await state.runTest(suite, `Should index ${type}`, async () =>
|
||||
state.api.index(itemMap.get(type) as SCThings[], 'stapps-core-sample-data'),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
backend/e2e-connector/src/retreive-items.ts
Normal file
30
backend/e2e-connector/src/retreive-items.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import junit from 'junit-report-builder';
|
||||
import {SCSearchRequest} from '@openstapps/core';
|
||||
import {TestState} from './test-state.js';
|
||||
|
||||
/**
|
||||
* Retrieves all samples previously index using the api
|
||||
*/
|
||||
export async function retrieveItems(state: TestState, suite: junit.TestSuite): Promise<void> {
|
||||
const singleItemSearchRequest: SCSearchRequest = {
|
||||
filter: {
|
||||
arguments: {
|
||||
field: 'uid',
|
||||
value: 'replace-me',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
};
|
||||
for (const {uid, type} of state.localItems.values()) {
|
||||
await state.runTest(suite, `Should find ${type} (${uid})`, async () => {
|
||||
singleItemSearchRequest.filter!.arguments.value = uid;
|
||||
const searchResponse = await state.api.search(singleItemSearchRequest);
|
||||
if (searchResponse.data.length !== 1) {
|
||||
throw new Error(
|
||||
`Search for single SCThing with uid: ${uid} returned ${searchResponse.data.length} results`,
|
||||
);
|
||||
}
|
||||
state.remoteItems.set(uid, searchResponse.data[0]);
|
||||
});
|
||||
}
|
||||
}
|
||||
66
backend/e2e-connector/src/test-state.ts
Normal file
66
backend/e2e-connector/src/test-state.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {ConnectorClient, HttpClientInterface} from '@openstapps/api';
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import junit from 'junit-report-builder';
|
||||
|
||||
export class TestState {
|
||||
localItems = new Map<string, SCThings>();
|
||||
|
||||
remoteItems = new Map<string, SCThings>();
|
||||
|
||||
errors: string[] = [];
|
||||
|
||||
api: ConnectorClient;
|
||||
|
||||
constructor(client: HttpClientInterface, readonly options: E2EOptions) {
|
||||
this.api = new ConnectorClient(client, options.to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test
|
||||
* @param suite The suite the test belongs to
|
||||
* @param name The name of the test
|
||||
* @param scope The scope in which the test is run
|
||||
*/
|
||||
async runTest(suite: junit.TestSuite, name: string, scope: () => Promise<void>) {
|
||||
const testCase = suite.testCase().name(name);
|
||||
process.stdout.addListener('data', testCase.standardOutput);
|
||||
process.stderr.addListener('data', testCase.standardError);
|
||||
|
||||
const start = performance.now();
|
||||
try {
|
||||
await scope();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
await Logger.error(error);
|
||||
testCase.failure(error.message);
|
||||
this.errors.push(error.message);
|
||||
}
|
||||
|
||||
process.stdout.removeListener('data', testCase.standardOutput);
|
||||
process.stderr.removeListener('data', testCase.standardError);
|
||||
|
||||
const end = performance.now();
|
||||
testCase.time((end - start) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to set up indexing core test files to backend
|
||||
*/
|
||||
export interface E2EOptions {
|
||||
/**
|
||||
* File path of the directory containing core test files
|
||||
*/
|
||||
samplesLocation: string;
|
||||
|
||||
/**
|
||||
* URL of the backend to index to
|
||||
*/
|
||||
to: string;
|
||||
|
||||
/**
|
||||
* Location of the report
|
||||
*/
|
||||
reportLocation?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user