diff --git a/api/jest.config.ts b/api/jest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..09e402b93abdd342aa1ba638532a1805527a247d --- /dev/null +++ b/api/jest.config.ts @@ -0,0 +1,31 @@ +import type { Config } from '@jest/types'; +import * as fs from 'fs'; +import * as dotenv from 'dotenv'; + +['.env.defaults', '.env'].forEach((f) => dotenv.config({ path: f })); + +const srcPath = 'src/engine/connectors'; +const engine_type = process.env.ENGINE_TYPE; // if there no engine all tests will run + +export default async (): Promise<Config.InitialOptions> => { + const dirs = (await fs.promises.readdir(srcPath)) + .filter((dir) => dir !== engine_type) + .map((dir) => `${srcPath}/${dir}`); + + return { + moduleFileExtensions: ['js', 'json', 'ts'], + testPathIgnorePatterns: dirs, + rootDir: 'src', + testRegex: '.*\\.spec\\.ts$', + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + collectCoverageFrom: ['**/*.(t|j)s'], + coverageDirectory: '../coverage', + testEnvironment: 'node', + setupFiles: ['dotenv/config'], + moduleNameMapper: { + '^src/(.*)$': '<rootDir>/$1', + }, + }; +}; diff --git a/api/package-lock.json b/api/package-lock.json index 88182859b9a42510792332bff648111830b823b3..25f9900becd2762b1938002feb162fec7c2aad1b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -28,7 +28,7 @@ "devDependencies": { "@nestjs/cli": "^8.0.0", "@nestjs/schematics": "^8.0.0", - "@nestjs/testing": "^8.0.0", + "@nestjs/testing": "^8.2.2", "@types/express": "^4.17.13", "@types/jest": "^27.0.1", "@types/node": "^16.0.0", @@ -2084,13 +2084,13 @@ "dev": true }, "node_modules/@nestjs/testing": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.0.6.tgz", - "integrity": "sha512-HRXGM5RlGa+o+kxWI9DQCALndSvL3Remjg1cZVFp2w2s5eXRPpiFMo9puXtu9DSc4tz78xYcQGmEaeYNTB7gvg==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.2.2.tgz", + "integrity": "sha512-TeNdmjSMKKCw6Z4duu5h4FuGtBV0SXqM4qxLytFJ6ZobaODjqh85u9fWqJlthH+XuHslVYsmNBkCeNrgzyrL8A==", "dev": true, "dependencies": { "optional": "0.1.4", - "tslib": "2.3.0" + "tslib": "2.3.1" }, "funding": { "type": "opencollective", @@ -2111,6 +2111,12 @@ } } }, + "node_modules/@nestjs/testing/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/@nestjs/typeorm": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-8.0.2.tgz", @@ -12162,13 +12168,21 @@ } }, "@nestjs/testing": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.0.6.tgz", - "integrity": "sha512-HRXGM5RlGa+o+kxWI9DQCALndSvL3Remjg1cZVFp2w2s5eXRPpiFMo9puXtu9DSc4tz78xYcQGmEaeYNTB7gvg==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.2.2.tgz", + "integrity": "sha512-TeNdmjSMKKCw6Z4duu5h4FuGtBV0SXqM4qxLytFJ6ZobaODjqh85u9fWqJlthH+XuHslVYsmNBkCeNrgzyrL8A==", "dev": true, "requires": { "optional": "0.1.4", - "tslib": "2.3.0" + "tslib": "2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } } }, "@nestjs/typeorm": { diff --git a/api/package.json b/api/package.json index 1885442d70f4ac545ac7fc662ec9bef27d1d21de..9dfbc4ba56b531dc1bb627445150d9a1f8928e11 100644 --- a/api/package.json +++ b/api/package.json @@ -18,7 +18,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", + "test:e2e": "jest --config ./test/jest.e2e-config.ts", "prepare": "cd .. && husky install api/.husky" }, "dependencies": { @@ -41,7 +41,7 @@ "devDependencies": { "@nestjs/cli": "^8.0.0", "@nestjs/schematics": "^8.0.0", - "@nestjs/testing": "^8.0.0", + "@nestjs/testing": "^8.2.2", "@types/express": "^4.17.13", "@types/jest": "^27.0.1", "@types/node": "^16.0.0", @@ -60,22 +60,5 @@ "ts-node": "^10.0.0", "tsconfig-paths": "^3.10.1", "typescript": "^4.3.5" - }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "coverageDirectory": "../coverage", - "testEnvironment": "node" } } diff --git a/api/src/engine/connectors/exareme/tests/test-utilities.ts b/api/src/engine/connectors/exareme/tests/test-utilities.ts new file mode 100644 index 0000000000000000000000000000000000000000..116863a5dffb798ed9c6bbca23fdeeb48785c942 --- /dev/null +++ b/api/src/engine/connectors/exareme/tests/test-utilities.ts @@ -0,0 +1,77 @@ +import { IEngineService } from 'src/engine/engine.interfaces'; +import { Experiment } from 'src/engine/models/experiment/experiment.model'; +import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; + +const TIMEOUT_DURATION_SECONDS = 60 * 10; + +const TEST_PATHOLOGIES = { + dementia: { + code: 'dementia', + datasets: [ + { + code: 'desd-synthdata', + }, + { code: 'edsd' }, + { code: 'ppmi' }, + { code: 'fake_longitudinal' }, + ], + }, + mentalhealth: { + code: 'mentalhealth', + datasets: [{ code: 'demo' }], + }, + tbi: { + code: 'tbi', + datasets: [{ code: 'dummy_tbi' }], + }, +}; + +const createExperiment = async ( + input: ExperimentCreateInput, + service: IEngineService, +): Promise<Experiment | undefined> => { + return await service.createExperiment(input, false); +}; + +const waitForResult = ( + id: string, + service: IEngineService, +): Promise<Experiment> => + new Promise((resolve, reject) => { + let elapsed = 0; + const timerId = setInterval(async () => { + const experiment = await service.getExperiment(id); + + const loading = experiment ? experiment.status === 'pending' : true; + + if (!loading) { + clearInterval(timerId); + resolve(experiment); + } + + if (elapsed > TIMEOUT_DURATION_SECONDS) { + clearInterval(timerId); + reject( + `Query experiment ${experiment.id} timeout after ${TIMEOUT_DURATION_SECONDS} s`, + ); + } + + elapsed = elapsed + 0.3; + }, 300); + }); + +const uid = (): string => + 'xxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + + return v.toString(16); + }); + +export { + createExperiment, + uid, + waitForResult, + TEST_PATHOLOGIES, + TIMEOUT_DURATION_SECONDS, +}; diff --git a/api/src/engine/connectors/exareme/tests/units/calibration-belt.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/units/calibration-belt.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..34d674bd3c7877b7a91abe86bba9d1364073c7c7 --- /dev/null +++ b/api/src/engine/connectors/exareme/tests/units/calibration-belt.e2e-spec.ts @@ -0,0 +1,86 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppModule } from '../../../../../main/app.module'; +import { ENGINE_SERVICE } from '../../../../engine.constants'; +import { IEngineService } from '../../../../engine.interfaces'; +import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { + createExperiment, + TEST_PATHOLOGIES, + TIMEOUT_DURATION_SECONDS, + waitForResult, +} from '../test-utilities'; + +jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); + +describe('ExaremeService', () => { + let exaremeService: IEngineService; + + beforeEach(async () => { + const moduleRef: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + }); + + const modelSlug = `calibration-belt-${Math.round(Math.random() * 10000)}`; + const algorithmId = 'CALIBRATION_BELT'; + + const input: ExperimentCreateInput = { + name: modelSlug, + variables: ['mortality_gose'], + coVariables: ['mortality_core'], + datasets: TEST_PATHOLOGIES.tbi.datasets + .filter((d) => d.code !== 'fake_longitudinal') + .map((d) => d.code), + domain: TEST_PATHOLOGIES.tbi.code, + algorithm: { + id: algorithmId, + type: 'string', + parameters: [ + { + id: 'devel', + value: ['external'], + }, + { + id: 'max_deg', + value: ['4'], + }, + { + id: 'confLevels', + value: ['0.80,0.95'], + }, + { + id: 'thres', + value: ['0.95'], + }, + { + id: 'num_points', + value: ['200'], + }, + ], + }, + filter: '', + }; + + describe('Integration Test for experiment API', () => { + it(`create ${algorithmId}`, async () => { + const experiment = await createExperiment(input, exaremeService); + + expect(experiment).toBeTruthy(); + expect(experiment?.status).toStrictEqual('pending'); + + expect(experiment?.id).toBeTruthy(); + + const experimentResult = await waitForResult( + experiment?.id ?? '', + exaremeService, + ); + + expect(experimentResult.status).toStrictEqual('success'); + expect(experimentResult).toBeTruthy(); + + expect(experimentResult.results).toHaveLength(1); + }); + }); +}); diff --git a/api/test/app.e2e-spec.ts b/api/test/app.e2e-spec.ts index 50cda62332e9474925e819ff946358a9c40d1bf2..92622c06ac54f7020c4be59cc4f3751feffeef0b 100644 --- a/api/test/app.e2e-spec.ts +++ b/api/test/app.e2e-spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; +import { AppModule } from 'src/main/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; diff --git a/api/test/jest-e2e.json b/api/test/jest-e2e.json deleted file mode 100644 index e9d912f3e3cefc18505d3cd19b3a5a9f567f5de0..0000000000000000000000000000000000000000 --- a/api/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/api/test/jest.e2e-config.ts b/api/test/jest.e2e-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..cad149913075e7aa049a6a11ea40b7e96b92e476 --- /dev/null +++ b/api/test/jest.e2e-config.ts @@ -0,0 +1,31 @@ +import type { Config } from '@jest/types'; +import * as fs from 'fs'; +import * as dotenv from 'dotenv'; + +['.env.defaults', '.env'].forEach((f) => dotenv.config({ path: f })); + +const srcPath = 'src/engine/connectors'; +const engine_type = process.env.ENGINE_TYPE; // if there no engine all tests will run + +export default async (): Promise<Config.InitialOptions> => { + const dirs = (await fs.promises.readdir(srcPath)) + .filter((dir) => dir !== engine_type) + .map((dir) => `${srcPath}/${dir}`); + + return { + moduleFileExtensions: ['js', 'json', 'ts'], + testPathIgnorePatterns: dirs, + rootDir: '../src', + testRegex: '.e2e-spec.ts$', + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + collectCoverageFrom: ['**/*.(t|j)s'], + coverageDirectory: '../coverage', + testEnvironment: 'node', + setupFiles: ['dotenv/config'], + moduleNameMapper: { + '^src/(.*)$': '<rootDir>/$1', + }, + }; +};