feat: tests

This commit is contained in:
2023-04-21 12:08:35 +02:00
parent 8cb9285462
commit d8c79256c9
140 changed files with 2100 additions and 2693 deletions

View File

@@ -22,11 +22,11 @@
},
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"test": "nyc mocha 'test/**/*.spec.ts'"
"test": "c8 mocha"
},
"dependencies": {
"@krlwlfrt/async-pool": "0.7.0",
@@ -58,7 +58,6 @@
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/body-parser": "1.19.2",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
@@ -75,7 +74,7 @@
"fs-extra": "10.1.0",
"mocha": "10.2.0",
"nock": "13.3.1",
"nyc": "15.1.0",
"c8": "7.13.0",
"ts-node": "10.9.1",
"tsup": "6.7.0",
"typedoc": "0.23.28",

View File

@@ -1,10 +1,10 @@
export * from './bulk.js'
export * from './client.js'
export * from './connector-client.js'
export * from './copy.js'
export * from './e2e.js'
export * from './errors.js'
export * from './http-client.js'
export * from './http-client-interface.js'
export * from './plugin.js'
export * from './plugin-client.js'
export * from './bulk.js';
export * from './client.js';
export * from './connector-client.js';
export * from './copy.js';
export * from './e2e.js';
export * from './errors.js';
export * from './http-client.js';
export * from './http-client-interface.js';
export * from './plugin.js';
export * from './plugin-client.js';

View File

@@ -24,29 +24,27 @@ import {expect} from 'chai';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import {suite, test} from '@testdeck/mocha';
import moment from 'moment';
import {Bulk} from '../src/bulk.js';
import {Client} from '../src/client.js';
import {BulkWithMultipleTypesError} from '../src/errors.js';
import {HttpClient} from '../src/http-client.js';
import {HttpClient, Bulk, Client, BulkWithMultipleTypesError} from '../src/index.js';
chai.should();
chai.use(chaiSpies);
chai.use(chaiAsPromised);
const sandbox = chai.spy.sandbox();
describe('Bulk', function () {
const sandbox = chai.spy.sandbox();
const bulkAddRoute = new SCBulkAddRoute();
const bulkDoneRoute = new SCBulkDoneRoute();
const bulkAddRoute = new SCBulkAddRoute();
const bulkDoneRoute = new SCBulkDoneRoute();
const httpClient = new HttpClient();
const client = new Client(httpClient, 'http://localhost');
const httpClient = new HttpClient();
const client = new Client(httpClient, 'http://localhost');
@suite()
export class BulkSpec {
@test
async add() {
afterEach(function () {
sandbox.restore();
})
it('should add', async function () {
sandbox.on(client, 'invokeRoute', () => {
return {};
});
@@ -82,10 +80,9 @@ export class BulkSpec {
},
dish,
);
}
});
@test
async addFails() {
it('should fail add', async function () {
const bulk = new Bulk(SCThingType.Dish, client, {
expiration: moment().add(3600, 'seconds').format(),
source: 'foo',
@@ -108,15 +105,10 @@ export class BulkSpec {
uid: 'foo',
};
return bulk.add(message).should.be.rejectedWith(BulkWithMultipleTypesError);
}
await bulk.add(message).should.be.rejectedWith(BulkWithMultipleTypesError);
});
async after() {
sandbox.restore();
}
@test
async construct() {
it('should construct', function () {
expect(() => {
return new Bulk(SCThingType.Dish, client, {
expiration: moment().add(3600, 'seconds').format(),
@@ -126,10 +118,9 @@ export class BulkSpec {
uid: 'bar',
});
}).not.to.throw();
}
});
@test
async done() {
it('should done', async function () {
sandbox.on(client, 'invokeRoute', () => {
return {};
});
@@ -149,5 +140,5 @@ export class BulkSpec {
expect(client.invokeRoute).to.have.been.first.called.with(bulkDoneRoute, {
UID: 'bar',
});
}
}
});
});

View File

@@ -28,11 +28,7 @@ import {expect} from 'chai';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import {suite, test} from '@testdeck/mocha';
import {Client} from '../src/client.js';
import {ApiError, OutOfRangeError} from '../src/errors.js';
import {HttpClient} from '../src/http-client.js';
import {HttpClientResponse} from '../src/http-client-interface.js';
import {ApiError, OutOfRangeError, Client, HttpClient, HttpClientResponse} from '../src/index.js';
chai.should();
chai.use(chaiSpies);
@@ -84,21 +80,18 @@ async function invokeIndexRouteFails(): Promise<RecursivePartial<HttpClientRespo
};
}
@suite()
export class ClientSpec {
async after() {
describe('Client', function () {
afterEach(function () {
sandbox.restore();
}
});
@test
async construct() {
it('should construct', function () {
expect(() => {
return new Client(httpClient, 'http://localhost');
}).not.to.throw();
}
});
@test
async constructWithHeaders() {
it('should construct with headers', async function () {
sandbox.on(httpClient, 'request', invokeIndexRoute);
expect(httpClient.request).not.to.have.been.first.called();
@@ -115,10 +108,9 @@ export class ClientSpec {
method: indexRoute.method,
url: new URL('http://localhost' + indexRoute.getUrlPath()),
});
}
});
@test
async getThing() {
it('should get thing', async function () {
const message: SCMessage = {
audiences: ['employees'],
categories: ['news'],
@@ -174,10 +166,9 @@ export class ClientSpec {
method: searchRoute.method,
url: new URL('http://localhost' + searchRoute.getUrlPath()),
});
}
});
@test
async getThingFailsByEmptyResponse() {
it('should fail getThing by empty response', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCSearchResponse>> => {
return {
body: {
@@ -202,10 +193,9 @@ export class ClientSpec {
const client = new Client(httpClient, 'http://localhost');
return client.getThing('bar').should.be.rejected;
}
});
@test
async getThingFailsByUid() {
it('should fail getThing by uid', async function () {
const message: SCMessage = {
audiences: ['employees'],
categories: ['news'],
@@ -244,10 +234,9 @@ export class ClientSpec {
const client = new Client(httpClient, 'http://localhost');
return client.getThing('bar').should.be.rejected;
}
});
@test
async handshake() {
it('should handshake', async function () {
sandbox.on(httpClient, 'request', invokeIndexRoute);
expect(httpClient.request).not.to.have.been.first.called();
@@ -263,10 +252,9 @@ export class ClientSpec {
method: indexRoute.method,
url: new URL('http://localhost' + indexRoute.getUrlPath()),
});
}
});
@test
async handshakeFails() {
it('should fail handshake', async function () {
sandbox.on(httpClient, 'request', invokeIndexRoute);
expect(httpClient.request).not.to.have.been.first.called();
@@ -274,10 +262,9 @@ export class ClientSpec {
const client = new Client(httpClient, 'http://localhost');
return client.handshake('bar.bar.dummy').should.be.rejectedWith(ApiError);
}
});
@test
async invokePlugin() {
it('should invoke plugin', async function () {
sandbox.on(
httpClient,
'request',
@@ -303,13 +290,12 @@ export class ClientSpec {
await client.invokePlugin('unsupportedPlugin').should.be.rejectedWith(ApiError, /.*supportedPlugin.*/gim);
// again with cached feature definitions
return client
await client
.invokePlugin('supportedPlugin')
.should.not.be.rejectedWith(ApiError, /.*supportedPlugin.*/gim);
}
});
@test
async invokePluginUnavailable() {
it('should invoke unavailable plugin', async function () {
sandbox.on(
httpClient,
'request',
@@ -350,10 +336,9 @@ export class ClientSpec {
);
// again with cached feature definitions
return client.invokePlugin('supportedPlugin').should.be.rejectedWith(ApiError, /.*supportedPlugin.*/gim);
}
});
@test
async invokeRoute() {
it('should invoke route', async function () {
sandbox.on(httpClient, 'request', invokeIndexRoute);
expect(httpClient.request).not.to.have.been.first.called();
@@ -369,10 +354,9 @@ export class ClientSpec {
method: indexRoute.method,
url: new URL('http://localhost' + indexRoute.getUrlPath()),
});
}
});
@test
async invokeRouteFails() {
it('should fail to invoke route', async function () {
sandbox.on(httpClient, 'request', invokeIndexRouteFails);
expect(httpClient.request).not.to.have.been.first.called();
@@ -380,10 +364,9 @@ export class ClientSpec {
const client = new Client(httpClient, 'http://localhost');
return client.invokeRoute(indexRoute).should.be.rejectedWith(ApiError);
}
});
@test
async multiSearch() {
it('should multi search', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCMultiSearchResponse>> => {
return {
body: {
@@ -430,10 +413,9 @@ export class ClientSpec {
method: multiSearchRoute.method,
url: new URL('http://localhost' + multiSearchRoute.getUrlPath()),
});
}
});
@test
async multiSearchWithPreflight() {
it('should multi search with preflight', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCMultiSearchResponse>> => {
return {
body: {
@@ -488,10 +470,9 @@ export class ClientSpec {
method: multiSearchRoute.method,
url: new URL('http://localhost' + multiSearchRoute.getUrlPath()),
});
}
});
@test
nextWindow() {
it('should next window', async function () {
let searchRequest: SCSearchRequest = {size: 30};
const searchResponse: SCSearchResponse = {
data: [],
@@ -515,10 +496,9 @@ export class ClientSpec {
expect(() => {
Client.nextWindow(searchRequest, searchResponse);
}).to.throw(OutOfRangeError);
}
});
@test
async search() {
it('should search', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCSearchResponse>> => {
return {
body: {
@@ -551,10 +531,9 @@ export class ClientSpec {
method: searchRoute.method,
url: new URL('http://localhost' + searchRoute.getUrlPath()),
});
}
});
@test
async searchNext() {
it('should search next', async function () {
const searchResponse: SCSearchResponse = {
data: [],
facets: [],
@@ -589,10 +568,9 @@ export class ClientSpec {
method: searchRoute.method,
url: new URL('http://localhost' + searchRoute.getUrlPath()),
});
}
});
@test
async searchWithPreflight() {
it('should search with preflight', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCSearchResponse>> => {
return {
body: {
@@ -633,5 +611,5 @@ export class ClientSpec {
method: searchRoute.method,
url: new URL('http://localhost' + searchRoute.getUrlPath()),
});
}
}
});
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-null */
/*
* Copyright (C) 2018 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -12,7 +13,6 @@
* 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 {asyncPool} from '@krlwlfrt/async-pool/lib/async-pool';
import {
isThing,
SCBulkAddResponse,
@@ -33,16 +33,12 @@ 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.js';
import {EmptyBulkError, NamespaceNotDefinedError} from '../src/errors.js';
import {HttpClient} from '../src/http-client.js';
import {HttpClientRequest, HttpClientResponse} from '../src/http-client-interface.js';
import {ConnectorClient, EmptyBulkError, NamespaceNotDefinedError, HttpClient, HttpClientRequest, HttpClientResponse} from '../src/index.js';
import path from "path";
import {fileURLToPath} from "url";
import {readdir, readFile} from "fs/promises";
chai.should();
chai.use(chaiSpies);
@@ -55,9 +51,6 @@ const bulkDoneRoute = new SCBulkDoneRoute();
const bulkRoute = new SCBulkRoute();
const thingUpdateRoute = new SCThingUpdateRoute();
const readdirPromisified = promisify(readdir);
const readFilePromisified = promisify(readFile);
const httpClient = new HttpClient();
/**
@@ -76,14 +69,12 @@ function doesContainThings<T extends SCThingWithoutReferences>(thing: T): boolea
}, false);
}
@suite()
export class ConnectorClientSpec {
async after() {
describe('ConnectorClient', function () {
afterEach(function () {
sandbox.restore();
}
});
@test
async bulk() {
it('should bulk', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCBulkResponse>> => {
return {
body: {
@@ -115,10 +106,9 @@ export class ConnectorClientSpec {
method: bulkRoute.method,
url: new URL('http://localhost' + bulkRoute.getUrlPath()),
});
}
});
@test
async bulkWithoutTimeout() {
it('should bulk without timeout', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCBulkResponse>> => {
return {
body: {
@@ -150,10 +140,9 @@ export class ConnectorClientSpec {
method: bulkRoute.method,
url: new URL('http://localhost' + bulkRoute.getUrlPath()),
});
}
});
@test
async index() {
it('should index', async function () {
const messages: SCMessage[] = [
{
audiences: ['employees'],
@@ -240,16 +229,14 @@ export class ConnectorClientSpec {
method: bulkRoute.method,
url: new URL('http://localhost' + bulkRoute.getUrlPath()),
});
}
});
@test
async indexFails() {
it('should fail to index', async function () {
const connectorClient = new ConnectorClient(httpClient, 'http://localhost');
return connectorClient.index([]).should.be.rejectedWith(EmptyBulkError);
}
await connectorClient.index([]).should.be.rejectedWith(EmptyBulkError);
});
@test
async indexWithoutSource() {
it('should index without source', async function () {
const messages: SCMessage[] = [
{
audiences: ['employees'],
@@ -336,28 +323,25 @@ export class ConnectorClientSpec {
method: bulkRoute.method,
url: new URL('http://localhost' + bulkRoute.getUrlPath()),
});
}
});
@test
makeUuid() {
it('should make uuid', async function () {
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() {
it('should fail making a uuid', async function (){
expect(() => {
ConnectorClient.makeUUID('foo', 'b-u');
}).to.throw(NamespaceNotDefinedError);
}
});
@test
async removeReferences() {
const pathToTestFiles = resolve(
__dirname,
it('should remove references', async function () {
const pathToTestFiles = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'node_modules',
'@openstapps',
@@ -367,14 +351,14 @@ export class ConnectorClientSpec {
'indexable',
);
const testFiles = await readdirPromisified(pathToTestFiles);
const testFiles = await readdir(pathToTestFiles);
const testInstances = await asyncPool(5, testFiles, async testFile => {
const buffer = await readFilePromisified(join(pathToTestFiles, testFile));
const testInstances = await Promise.all(testFiles.map(async testFile => {
const buffer = await readFile(path.join(pathToTestFiles, testFile));
const content = JSON.parse(buffer.toString());
return content.instance;
});
}));
for (const testInstance of testInstances) {
const checkInstance = clone()(testInstance);
@@ -384,6 +368,7 @@ export class ConnectorClientSpec {
false,
JSON.stringify([testInstance, testInstanceWithoutReferences], null, 2),
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((testInstanceWithoutReferences as any).origin).to.be.equal(
undefined,
JSON.stringify([testInstance, testInstanceWithoutReferences], null, 2),
@@ -393,10 +378,9 @@ export class ConnectorClientSpec {
'Removing the references of a thing could have side effects because no deep copy is used',
);
}
}
});
@test
async removeUndefinedProperties() {
it('should remove undefined properties', async function () {
const objectWithUndefinedProperties = {
value: 'foo',
novalue: undefined,
@@ -417,10 +401,9 @@ export class ConnectorClientSpec {
objectWithoutUndefinedProperties,
JSON.stringify([objectWithUndefinedProperties, objectWithoutUndefinedProperties], null, 2),
);
}
});
@test
async update() {
it('should update', async function () {
const message: SCMessage = {
audiences: ['employees'],
categories: ['news'],
@@ -462,5 +445,5 @@ export class ConnectorClientSpec {
}),
),
});
}
}
});
});

View File

@@ -27,12 +27,9 @@ import {
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import {suite, test} from '@testdeck/mocha';
import moment from 'moment';
import {copy} from '../src/copy.js';
import {ApiError} from '../src/errors.js';
import {HttpClient, RequestOptions, Response} from '../src/http-client.js';
import {RecursivePartial} from './client.spec';
import {copy, ApiError, HttpClient, RequestOptions, Response} from '../src/index.js';
import {RecursivePartial} from './client.spec.js';
chai.should();
chai.use(chaiSpies);
@@ -47,14 +44,12 @@ const searchRoute = new SCSearchRoute();
const httpClient = new HttpClient();
@suite()
export class CopySpec {
async after() {
describe('Copy', function () {
afterEach(function () {
sandbox.restore();
}
});
@test
async copy() {
it('should copy', async function () {
type responses = Response<SCBulkAddResponse | SCBulkDoneResponse | SCBulkResponse | SCSearchResponse>;
sandbox.on(
@@ -133,10 +128,9 @@ export class CopySpec {
type: SCThingType.Dish,
version: 'foo.bar.foobar',
});
}
});
@test
async copyShouldFail() {
it('should fail to copy', async function () {
type responses = Response<SCBulkAddResponse | SCBulkDoneResponse | SCBulkResponse | SCSearchResponse>;
sandbox.on(
@@ -206,7 +200,7 @@ export class CopySpec {
},
);
return copy(httpClient, {
await copy(httpClient, {
batchSize: 5,
from: 'http://foo.bar',
source: 'stapps-copy',
@@ -214,5 +208,5 @@ export class CopySpec {
type: SCThingType.Dish,
version: 'foo.bar.foobar',
}).should.be.rejectedWith(ApiError);
}
}
});
});

View File

@@ -12,9 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
// tslint:disable-next-line: max-line-length
// tslint:disable: completed-docs no-implicit-dependencies prefer-function-over-method newline-per-chained-call member-ordering
// eslint-disable-next-line unicorn/prevent-abbreviations
import {
SCBulkAddResponse,
SCBulkAddRoute,
@@ -32,12 +30,12 @@ import chaiSpies from 'chai-spies';
import clone = require('rfdc');
import {existsSync, mkdirSync, rmdirSync, unlinkSync} from 'fs';
import {createFileSync} from 'fs-extra';
import {suite, test} from '@testdeck/mocha';
import {join} from 'path';
import {e2eRun, getItemsFromSamples} from '../src/e2e.js';
import {ApiError} from '../src/errors.js';
import {HttpClient, RequestOptions, Response} from '../src/http-client.js';
import {RecursivePartial} from './client.spec';
// eslint-disable-next-line unicorn/prevent-abbreviations
import {e2eRun, getItemsFromSamples, ApiError, HttpClient, RequestOptions, Response} from '../src/index.js';
import {RecursivePartial} from './client.spec.js';
import {expect} from "chai";
import path from "path";
import {fileURLToPath} from "url";
chai.should();
chai.use(chaiSpies);
@@ -55,26 +53,21 @@ const httpClient = new HttpClient();
const storedThings: Map<string, SCThings> = new Map();
@suite
export class E2EConnectorSpec {
async after() {
describe('e2e Connector', function () {
afterEach(function () {
sandbox.restore();
}
});
@test
async getCoreTestSamples() {
it('should get core test samples', async function () {
const items = await getItemsFromSamples('./node_modules/@openstapps/core/test/resources');
// tslint:disable-next-line: no-unused-expression
chai.expect(items).to.not.be.empty;
}
expect(items).to.not.be.empty;
});
@test
async getCoreTestSamplesShouldFail() {
await chai.expect(getItemsFromSamples('./nonexistantdirectory')).to.be.rejectedWith(Error);
}
it('should fail to get core test samples', async function () {
await chai.expect(getItemsFromSamples('./non-existent-directory')).to.be.rejectedWith(Error);
});
@test
async e2eRunSimulation() {
it('should run e2e simulation', async function () {
type responses = Response<SCBulkAddResponse | SCBulkDoneResponse | SCBulkResponse | SCSearchResponse>;
let failOnCompare = false;
@@ -167,10 +160,9 @@ export class E2EConnectorSpec {
to: 'http://localhost',
samplesLocation: './node_modules/@openstapps/core/test/resources',
}).should.be.rejectedWith('Unexpected difference');
}
});
@test
async indexShouldFail() {
it('should fail to index', async function () {
type responses = Response<SCBulkAddResponse | SCBulkDoneResponse | SCBulkResponse>;
sandbox.on(httpClient, 'request', async (): Promise<RecursivePartial<responses>> => {
@@ -185,33 +177,31 @@ export class E2EConnectorSpec {
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)) {
mkdirSync(emptyDirPath);
it('should fail to index directory without data', async function () {
const emptyDirectoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'emptyDir');
if (!existsSync(emptyDirectoryPath)) {
mkdirSync(emptyDirectoryPath);
}
await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: emptyDirPath}).should.be.rejectedWith(
await e2eRun(httpClient, {to: 'http://localhost', samplesLocation: emptyDirectoryPath}).should.be.rejectedWith(
'Could not index samples. None were retrieved from the file system.',
);
rmdirSync(emptyDirPath);
}
rmdirSync(emptyDirectoryPath);
});
@test
async indexShouldFailDirectoryWithoutJsonData() {
const somewhatFilledDirPath = join(__dirname, 'somewhatFilledDir');
if (!existsSync(somewhatFilledDirPath)) {
mkdirSync(somewhatFilledDirPath);
it('should fail to index directory without json data', async function () {
const somewhatFilledDirectoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'somewhatFilledDir');
if (!existsSync(somewhatFilledDirectoryPath)) {
mkdirSync(somewhatFilledDirectoryPath);
}
const nonJsonFile = join(somewhatFilledDirPath, 'nonjson.txt');
const nonJsonFile = path.join(somewhatFilledDirectoryPath, 'nonjson.txt');
createFileSync(nonJsonFile);
await e2eRun(httpClient, {
to: 'http://localhost',
samplesLocation: somewhatFilledDirPath,
samplesLocation: somewhatFilledDirectoryPath,
}).should.be.rejectedWith('Could not index samples. None were retrieved from the file system.');
unlinkSync(nonJsonFile);
rmdirSync(somewhatFilledDirPath);
}
}
rmdirSync(somewhatFilledDirectoryPath);
});
});

View File

@@ -16,8 +16,7 @@ import chai from 'chai';
import {expect} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import {suite, test} from '@testdeck/mocha';
import {ApiError} from '../src/errors.js';
import {ApiError} from '../src/index.js';
chai.should();
chai.use(chaiSpies);
@@ -25,36 +24,32 @@ chai.use(chaiAsPromised);
const sandbox = chai.spy.sandbox();
@suite()
export class ErrorsSpec {
async after() {
describe('Errors', function () {
afterEach(function () {
sandbox.restore();
}
});
@test
async shouldAddAdditionalData() {
it('should add additional data', function () {
const error = new ApiError({
additionalData: 'Lorem ipsum',
});
expect(error.toString()).to.contain('Lorem ipsum');
}
});
@test
async shouldAddRemoteStackTrace() {
it('should add remote stack-trace', async function () {
const error = new ApiError({
stack: 'Lorem ipsum',
});
expect(error.toString()).to.contain('Lorem ipsum');
}
});
@test
async shouldSetName() {
it('should set name', async function () {
const error = new ApiError({
name: 'Foo',
});
expect(error.name).to.be.equal('Foo');
}
}
});
});

View File

@@ -13,27 +13,23 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {suite, test} from '@testdeck/mocha';
import nock from 'nock';
import {HttpClient} from '../src/http-client.js';
import {HttpClient} from '../src/index.js';
// TODO: use after each to clean up the nock (then there is no need for numerated resource links)
@suite()
export class HttpClientSpec {
@test
async construct() {
describe('HttpClient', function () {
afterEach(function () {
nock.cleanAll();
})
it('should construct', function () {
expect(() => {
return new HttpClient();
}).not.to.throw();
}
});
async after() {
nock.cleanAll();
}
@test
async request() {
it('should request', async function () {
const client = new HttpClient();
nock('http://www.example.com').get('/resource').reply(200, 'foo');
@@ -43,10 +39,9 @@ export class HttpClientSpec {
});
expect(response.body).to.be.equal('foo');
}
});
@test
async requestWithBody() {
it('should request with body', async function () {
const client = new HttpClient();
nock('http://www.example.com').get('/resource').reply(200, 'foo');
@@ -56,12 +51,11 @@ export class HttpClientSpec {
});
expect(response.body).to.be.equal('foo');
}
});
@test
async requestWithError() {
it('should request with error', async function () {
const client = new HttpClient();
let caughtErr;
let caughtError;
nock('http://www.example.com').get('/resource').replyWithError('foo');
@@ -72,15 +66,14 @@ export class HttpClientSpec {
},
url: new URL('http://www.example.com/resource'),
});
} catch (err) {
caughtErr = err;
} catch (error) {
caughtError = error;
}
expect(caughtErr).not.to.be.undefined;
}
expect(caughtError).not.to.be.undefined;
});
@test
async requestWithHeaders() {
it('should request with headers', async function () {
const client = new HttpClient();
nock('http://www.example.com').get('/resource').reply(200, 'foo');
@@ -93,10 +86,9 @@ export class HttpClientSpec {
});
expect(response.body).to.be.equal('foo');
}
});
@test
async requestWithMethodGet() {
it('should request with method GET', async function () {
const client = new HttpClient();
nock('http://www.example.com').get('/resource').reply(200, 'foo');
@@ -107,10 +99,9 @@ export class HttpClientSpec {
});
expect(response.body).to.be.equal('foo');
}
});
@test
async requestWithMethodPost() {
it('should request with method POST', async function () {
const client = new HttpClient();
nock('http://www.example.com').post('/resource').reply(200, 'foo');
@@ -121,5 +112,5 @@ export class HttpClientSpec {
});
expect(response.body).to.be.equal('foo');
}
}
});
});

View File

@@ -16,10 +16,7 @@ import {SCPluginRegisterRequest, SCPluginRegisterResponse, SCPluginRegisterRoute
import chai from 'chai';
import {expect} from 'chai';
import chaiSpies from 'chai-spies';
import {suite, test, timeout} from '@testdeck/mocha';
import {HttpClient} from '../src/http-client.js';
import {HttpClientResponse} from '../src/http-client-interface.js';
import {PluginClient} from '../src/plugin-client.js';
import {HttpClient, HttpClientResponse, PluginClient} from '../src/index.js';
import {TestPlugin} from './plugin-resources/test-plugin.js';
chai.use(chaiSpies);
@@ -27,21 +24,16 @@ chai.use(chaiSpies);
const sandbox = chai.spy.sandbox();
const httpClient = new HttpClient();
const pluginRegisterRoute = new SCPluginRegisterRoute();
const pluginClient = new PluginClient(httpClient, 'http://localhost');
@suite(timeout(10000))
export class PluginClientSpec {
static plugin: TestPlugin;
describe('PluginClient', function () {
this.timeout(10_000);
static async after() {
await this.plugin.close();
}
let plugin: TestPlugin;
static async before() {
this.plugin = new TestPlugin(
beforeEach(async function () {
plugin = new TestPlugin(
4000,
'',
'',
@@ -51,19 +43,19 @@ export class PluginClientSpec {
getSchema: () => {
/***/
},
} as any,
} as never,
'',
'',
'',
);
}
});
async after() {
afterEach(async function () {
await plugin.close();
sandbox.restore();
}
});
@test
async registerPlugin() {
it('should register the plugin', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCPluginRegisterResponse>> => {
return {
body: {
@@ -76,16 +68,16 @@ export class PluginClientSpec {
expect(httpClient.request).not.to.have.been.called();
await pluginClient.registerPlugin(PluginClientSpec.plugin);
await pluginClient.registerPlugin(plugin);
const request: SCPluginRegisterRequest = {
action: 'add',
plugin: {
address: PluginClientSpec.plugin.fullUrl,
name: PluginClientSpec.plugin.name,
requestSchema: PluginClientSpec.plugin.requestSchema,
responseSchema: PluginClientSpec.plugin.responseSchema,
route: PluginClientSpec.plugin.route,
address: plugin.fullUrl,
name: plugin.name,
requestSchema: plugin.requestSchema,
responseSchema: plugin.responseSchema,
route: plugin.route,
},
};
@@ -97,10 +89,9 @@ export class PluginClientSpec {
method: pluginRegisterRoute.method,
url: new URL(`http://localhost${pluginRegisterRoute.getUrlPath()}`),
});
}
});
@test
async unregisterPlugin() {
it('should unregister the plugin', async function () {
sandbox.on(httpClient, 'request', async (): Promise<HttpClientResponse<SCPluginRegisterResponse>> => {
return {
body: {
@@ -113,11 +104,11 @@ export class PluginClientSpec {
expect(httpClient.request).not.to.have.been.called();
await pluginClient.unregisterPlugin(PluginClientSpec.plugin);
await pluginClient.unregisterPlugin(plugin);
const request: SCPluginRegisterRequest = {
action: 'remove',
route: PluginClientSpec.plugin.route,
route: plugin.route,
};
expect(httpClient.request).to.have.been.first.called.with({
@@ -128,5 +119,5 @@ export class PluginClientSpec {
method: pluginRegisterRoute.method,
url: new URL(`http://localhost${pluginRegisterRoute.getUrlPath()}`),
});
}
}
});
});

View File

@@ -13,36 +13,35 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Converter} from '@openstapps/core-tools/lib/schema';
import {Converter} from '@openstapps/core-tools';
import chai from 'chai';
import {expect} from 'chai';
import chaiSpies from 'chai-spies';
import {readFileSync} from 'fs';
import {suite, test, timeout} from '@testdeck/mocha';
import {resolve} from 'path';
import {HttpClient} from '../src/http-client.js';
import {HttpClient} from '../src/index.js';
import {TestPlugin} from './plugin-resources/test-plugin.js';
import path from "path";
import {readFile} from "fs/promises";
import {fileURLToPath} from "url";
chai.use(chaiSpies);
process.on('unhandledRejection', err => {
throw err;
process.on('unhandledRejection', error => {
throw error;
});
const sandbox = chai.spy.sandbox();
const httpClient = new HttpClient();
@suite(timeout(20000))
export class PluginSpec {
static testPlugin: TestPlugin;
const dirname = path.dirname(fileURLToPath(import.meta.url));
static async after() {
PluginSpec.testPlugin.close();
}
describe('Plugin', function () {
this.timeout(20_000);
static async before() {
PluginSpec.testPlugin = new TestPlugin(
let testPlugin: TestPlugin;
beforeEach(function () {
testPlugin = new TestPlugin(
4000,
'',
'',
@@ -52,22 +51,22 @@ export class PluginSpec {
getSchema: () => {
/***/
},
} as any,
} as never,
'',
'',
'',
);
}
})
async after() {
afterEach(async function () {
await testPlugin.close();
sandbox.restore();
}
})
@test
async construct() {
it('should construct', async function () {
const converter = new Converter(
__dirname,
resolve(__dirname, 'plugin-resources', 'test-plugin-response.ts'),
dirname,
path.resolve(dirname, 'plugin-resources', 'test-plugin-response.ts'),
);
sandbox.on(converter, 'getSchema', schemaName => {
@@ -80,20 +79,19 @@ export class PluginSpec {
'http://B',
'/C', // this doesn't matter for our tests, it's only something that affects the backend
'http://D',
// @ts-ignore fake converter is not a converter
converter,
'PluginTestRequest',
'PluginTestResponse',
JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json')).toString()).version,
JSON.parse(await readFile(path.resolve(dirname, '..', 'package.json'), 'utf8')).version,
);
expect(constructTestPlugin.port).to.be.equal(4001);
expect(constructTestPlugin.name).to.be.equal('A');
expect(constructTestPlugin.url).to.be.equal('http://B');
expect(constructTestPlugin.route).to.be.equal('/C');
// @ts-ignore backendUrl is protected
// @ts-expect-error private property
expect(constructTestPlugin.backendUrl).to.be.equal('http://D');
// schemas are already covered, together with the directory and version
// @ts-ignore active is private
// @ts-expect-error private property
expect(constructTestPlugin.active).to.be.equal(false);
expect(constructTestPlugin.requestSchema.$id).to.be.equal('PluginTestRequest');
expect(constructTestPlugin.responseSchema.$id).to.be.equal('PluginTestResponse');
@@ -103,15 +101,14 @@ export class PluginSpec {
url: new URL('http://localhost:4001'),
});
// onRouteInvoke is a protected method, but we need to access it from the outside to test it
// @ts-ignore
// @ts-expect-error protected method
expect(constructTestPlugin.onRouteInvoke).not.to.have.been.called();
await constructTestPlugin.close();
sandbox.restore(constructTestPlugin, 'onRouteInvoke');
}
});
@test
async fullUrl() {
it('should have full url', async function () {
const constructTestPlugin = new TestPlugin(
4001,
'',
@@ -122,37 +119,35 @@ export class PluginSpec {
getSchema: () => {
/***/
},
} as any,
} as never,
'',
'',
'',
);
expect(constructTestPlugin.fullUrl).to.be.equal('http://B:4001');
await constructTestPlugin.close();
}
});
@test
async start() {
PluginSpec.testPlugin.start();
it('should start', async function () {
testPlugin.start();
sandbox.on(PluginSpec.testPlugin, 'onRouteInvoke');
sandbox.on(testPlugin, 'onRouteInvoke');
await httpClient.request({
url: new URL('http://localhost:4000'),
});
// onRouteInvoke is a protected method, but we need to access it from the outside to test it
// @ts-ignore
expect(PluginSpec.testPlugin.onRouteInvoke).to.have.been.called();
}
// @ts-expect-error protected method
expect(testPlugin.onRouteInvoke).to.have.been.called();
});
@test
async stop() {
it('should stop', async function () {
// simulate a normal use case by first starting the plugin and then stopping it
PluginSpec.testPlugin.start();
PluginSpec.testPlugin.stop();
testPlugin.start();
testPlugin.stop();
sandbox.on(PluginSpec.testPlugin, 'onRouteInvoke');
sandbox.on(testPlugin, 'onRouteInvoke');
const response = await httpClient.request({
url: new URL('http://localhost:4000'),
@@ -160,7 +155,7 @@ export class PluginSpec {
await expect(response.statusCode).to.be.equal(404);
// onRouteInvoke is a protected method, but we need to access it from the outside to test it
// @ts-ignore
expect(PluginSpec.testPlugin.onRouteInvoke).not.to.have.been.called();
}
}
// @ts-expect-error protected method
expect(testPlugin.onRouteInvoke).not.to.have.been.called();
});
});

View File

@@ -6,20 +6,20 @@
"main": "lib/index.js",
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"test": "nyc mocha 'test/**/*.spec.ts'"
"test": "c8 mocha"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/node": "18.15.3",
"@types/chai": "4.3.4",
"@types/mocha": "10.0.1",
"@types/node": "18.15.3",
"c8": "7.13.0",
"chai": "4.3.7",
"mocha": "10.2.0",
"ts-node": "10.9.1",
@@ -41,8 +41,5 @@
"@openstapps"
]
},
"nyc": {
"extends": "@openstapps/nyc-config"
},
"exports": "./lib/index.js"
}

View File

@@ -12,8 +12,7 @@
* 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 {chunk} from '../src/chunk.js';
import {chunk} from '../src/index.js';
import {expect} from 'chai';
describe('chunk', function () {

View File

@@ -12,7 +12,7 @@
* 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 {differenceBy} from '../src/difference.js';
import {differenceBy} from '../src/index.js';
import {expect} from 'chai';
describe('differenceBy', function () {

View File

@@ -12,7 +12,7 @@
* 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 {get} from '../src/get.js';
import {get} from '../src/index.js';
import {expect} from 'chai';
describe('get', function () {

View File

@@ -12,7 +12,7 @@
* 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 {groupBy, groupByStable, groupByProperty} from '../src/group-by.js';
import {groupBy, groupByStable, groupByProperty} from '../src/index.js';
import {expect} from 'chai';
describe('groupBy', () => {

View File

@@ -12,7 +12,7 @@
* 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 {keyBy} from '../src/key-by.js';
import {keyBy} from '../src/index.js';
import {expect} from 'chai';
describe('keyBy', function () {

View File

@@ -12,7 +12,7 @@
* 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 {mapValues} from '../src/map-values.js';
import {mapValues} from '../src/index.js';
import {expect} from 'chai';
describe('map-values', () => {

View File

@@ -12,7 +12,7 @@
* 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 {minBy} from '../src/min.js';
import {minBy} from '../src/index.js';
import {expect} from 'chai';
describe('minBy', function () {

View File

@@ -12,7 +12,7 @@
* 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 {omit} from '../src/omit.js';
import {omit} from '../src/index.js';
import {expect} from 'chai';
describe('omit', function () {

View File

@@ -12,7 +12,7 @@
* 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 {partition} from '../src/partition.js';
import {partition} from '../src/index.js';
import {expect} from 'chai';
describe('partition', function () {

View File

@@ -12,7 +12,7 @@
* 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 {pick} from '../src/pick.js';
import {pick} from '../src/index.js';
import {expect} from 'chai';
describe('pick', function () {

View File

@@ -12,7 +12,7 @@
* 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 {shuffle} from '../src/shuffle.js';
import {shuffle} from '../src/index.js';
import {expect} from 'chai';
describe('shuffle', function () {

View File

@@ -12,7 +12,7 @@
* 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 {stringSort, stringSortBy} from '../src/string-sort.js';
import {stringSort, stringSortBy} from '../src/index.js';
import {expect} from 'chai';
describe('stringSort', () => {

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {sum, sumBy} from '../src/sum.js';
import {sum, sumBy} from '../src/index.js';
describe('sum', () => {
it('should return the sum of all elements in the collection', () => {

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {Tree, treeGroupBy} from '../src/tree-group.js';
import {Tree, treeGroupBy} from '../src/index.js';
interface TestItem {
id: number;

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {uniqBy} from '../src/uniq.js';
import {uniqBy} from '../src/index.js';
describe('uniq', function () {
it('should return an array with unique values', function () {

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {zip} from '../src/zip.js';
import {zip} from '../src/index.js';
describe('zip', function () {
it('should zip arrays together', function () {

View File

@@ -46,7 +46,6 @@ Inside of a script in `package.json` or if the npm package is installed globally
openstapps-core-tools schema src/core lib/schema
```
## How to use the validator?
### Using the validator programatically

View File

@@ -27,26 +27,25 @@
},
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"plantuml-restart": "docker restart plantuml-server",
"plantuml-start": "docker run --name plantuml-server -d -p 8080:8080 registry.gitlab.com/openstapps/core-tools:latest",
"plantuml-stop": "docker stop plantuml-server",
"test": "nyc mocha 'test/**/*.spec.ts'"
"test": "c8 mocha"
},
"dependencies": {
"@openstapps/collection-utils": "workspace:*",
"@openstapps/logger": "workspace:*",
"@openstapps/easy-ast": "workspace:*",
"@openstapps/logger": "workspace:*",
"ajv": "8.12.0",
"re2": "1.18.0",
"better-ajv-errors": "1.2.0",
"chai": "4.3.7",
"commander": "10.0.0",
"deepmerge": "4.3.1",
"del": "6.1.1",
"eslint": "8.33.0",
"flatted": "3.2.7",
"fs-extra": "10.1.0",
"glob": "10.2.1",
@@ -56,18 +55,14 @@
"mustache": "4.2.0",
"openapi-types": "12.1.0",
"plantuml-encoder": "1.4.0",
"re2": "1.18.0",
"toposort": "2.0.2",
"ts-json-schema-generator": "1.2.0",
"ts-node": "10.9.1",
"typescript": "4.8.4"
"ts-json-schema-generator": "1.2.0"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/chai": "4.3.4",
"@types/fs-extra": "9.0.13",
"@types/glob": "8.0.1",
@@ -75,10 +70,13 @@
"@types/mocha": "10.0.1",
"@types/mustache": "4.2.2",
"@types/node": "18.15.3",
"chai": "4.3.7",
"mocha": "10.2.0",
"c8": "7.13.0",
"nock": "13.3.0",
"tsup": "6.7.0",
"typedoc": "0.23.28"
"ts-node": "10.9.1",
"typescript": "4.8.4"
},
"tsup": {
"entry": [
@@ -99,8 +97,5 @@
"eslintIgnore": [
"resources",
"openapi"
],
"nyc": {
"extends": "@openstapps/nyc-config"
}
]
}

View File

@@ -17,7 +17,6 @@ import {Command} from 'commander';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {copy} from 'fs-extra';
import path from 'path';
import {mkdirPromisified, readFilePromisified} from './common.js';
import {lightweightDefinitionsFromPath, lightweightProjectFromPath} from '@openstapps/easy-ast';
import {openapi3Template} from './resources/openapi-303-template.js';
import {gatherRouteInformation, generateOpenAPIForRoute} from './routes.js';
@@ -27,6 +26,7 @@ import {UMLConfig} from './uml/uml-config.js';
import {capitalize} from './util/string.js';
import {validateFiles, writeReport} from './validate.js';
import {fileURLToPath} from 'url';
import {mkdir, readFile} from 'fs/promises';
// handle unhandled promise rejections
process.on('unhandledRejection', async (reason: unknown) => {
@@ -42,7 +42,7 @@ const commander = new Command('openstapps-core-tools');
// eslint-disable-next-line unicorn/prefer-module
commander.version(
JSON.parse(
readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json')).toString(),
readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json')).toString(),
).version,
);
@@ -100,7 +100,7 @@ commander
// copy schema json schema files
try {
if (!existsSync(outDirectorySchemasPath)) {
await mkdirPromisified(outDirectorySchemasPath, {
await mkdir(outDirectorySchemasPath, {
recursive: true,
});
}
@@ -134,7 +134,7 @@ commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourceP
Logger.info(`Found ${validatableTypes.length} type(s) to generate schemas for.`);
await mkdirPromisified(schemaPath, {
await mkdir(schemaPath, {
recursive: true,
});
@@ -150,7 +150,7 @@ commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourceP
Logger.info(`Using ${corePackageJsonPath} to determine version for schemas.`);
const buffer = await readFilePromisified(corePackageJsonPath);
const buffer = await readFile(corePackageJsonPath);
const corePackageJson = JSON.parse(buffer.toString());
const coreVersion = corePackageJson.version;

View File

@@ -13,17 +13,8 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Logger} from '@openstapps/logger';
import {existsSync, mkdir, readFile, unlink, writeFile} from 'fs';
import glob from 'glob';
import {platform} from 'os';
import {promisify} from 'util';
import path from 'path';
export const globPromisified = promisify(glob.Glob);
export const mkdirPromisified = promisify(mkdir);
export const readFilePromisified = promisify(readFile);
export const writeFilePromisified = promisify(writeFile);
export const unlinkPromisified = promisify(unlink);
import {existsSync} from 'fs';
/**
* Get path that contains a tsconfig.json
@@ -33,21 +24,15 @@ export const unlinkPromisified = promisify(unlink);
export function getTsconfigPath(startPath: string): string {
let tsconfigPath = startPath;
// see https://stackoverflow.com/questions/9652043/identifying-the-file-system-root-with-node-js
const root = platform() === 'win32' ? process.cwd().split(path.sep)[0] : '/';
// repeat until a tsconfig.json is found
while (!existsSync(path.join(tsconfigPath, 'tsconfig.json'))) {
if (tsconfigPath === root) {
const parent = path.resolve(tsconfigPath, '..');
if (tsconfigPath === parent) {
throw new Error(
`Reached file system root ${root} while searching for 'tsconfig.json' in ${startPath}!`,
`Reached file system root ${parent} while searching for 'tsconfig.json' in ${startPath}!`,
);
}
// pop last directory
const tsconfigPathParts = tsconfigPath.split(path.sep);
tsconfigPathParts.pop();
tsconfigPath = tsconfigPathParts.join(path.sep);
tsconfigPath = parent;
}
Logger.info(`Using 'tsconfig.json' from ${tsconfigPath}.`);

View File

@@ -1,11 +1,11 @@
export * from './validate.js'
export * from './types/validator.js'
export * from './validate.js';
export * from './types/validator.js';
export * from './uml/uml-config.js'
export * from './uml/create-diagram.js'
export * from './uml/uml-config.js';
export * from './uml/create-diagram.js';
export * from './routes.js'
export * from './types/routes.js'
export * from './routes.js';
export * from './types/routes.js';
export * from './schema.js'
export * from './types/schema.js'
export * from './schema.js';
export * from './types/schema.js';

View File

@@ -13,7 +13,11 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {OpenAPIV3} from 'openapi-types';
import {isLightweightClass, lightweightProjectFromPath, LightweightProjectWithIndex} from '@openstapps/easy-ast';
import {
isLightweightClass,
lightweightProjectFromPath,
LightweightProjectWithIndex,
} from '@openstapps/easy-ast';
import {RouteInstanceWithMeta, RouteWithMetaInformation} from './types/routes.js';
import {rejectNil} from './util/collections.js';
import {capitalize} from './util/string.js';

View File

@@ -22,7 +22,7 @@ import {getTsconfigPath} from './common.js';
import {definitionsOf, lightweightProjectFromPath} from '@openstapps/easy-ast';
import {isSchemaWithDefinitions} from './util/guards.js';
import path from 'path';
import re2 from './types/re2.js';
import re2 from 're2';
/**
* StAppsCore converter
@@ -64,7 +64,7 @@ export class Converter {
this.generator = new SchemaGenerator(program, createParser(program, config), createFormatter(config));
// create Ajv instance
this.schemaValidator = new Ajv.default({code: {regExp: re2}});
this.schemaValidator = new Ajv.default({code: {regExp: re2 as never}});
}
/**

View File

@@ -1,6 +0,0 @@
import re2 from 're2';
type Re2 = typeof re2 & {code: string};
(re2 as Re2).code = 'require("lib/types/re2").default';
export default re2 as Re2;

View File

@@ -13,7 +13,6 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Logger} from '@openstapps/logger';
import {createWriteStream} from 'fs';
import * as request from 'got';
import {
expandTypeValue,
@@ -22,9 +21,10 @@ import {
LightweightClassDefinition,
LightweightDefinition,
LightweightProperty,
LightweightType
LightweightType,
} from '@openstapps/easy-ast';
import {UMLConfig} from './uml-config.js';
import {writeFile} from 'fs/promises';
/**
* Converts the lightweight class/enum definitions according to the configuration,
@@ -81,8 +81,8 @@ export async function createDiagramFromString(
plantUmlBaseURL: string,
outputFile = `Diagram-${new Date().toISOString()}`,
) {
// eslint-disable-next-line @typescript-eslint/no-var-requires,unicorn/prefer-module
const plantumlEncoder = require('plantuml-encoder');
// @ts-expect-error no declarations
const plantumlEncoder = await import('plantuml-encoder');
const plantUMLCode = plantumlEncoder.encode(`@startuml\n${modelPlantUMLCode}\n@enduml`);
const url = `${plantUmlBaseURL}/svg/${plantUMLCode}`;
let response;
@@ -100,13 +100,10 @@ export async function createDiagramFromString(
throw error;
}
// attach file extension
const fileName = `${outputFile}.svg`;
try {
createWriteStream(fileName).write(response.body);
Logger.log(`Writen data to file: ${fileName}`);
} catch {
throw new Error('Could not write file. Are you missing permissions?');
}
const fileName = `${outputFile.replace(/[^\w-]/g, '_')}.svg`;
await writeFile(fileName, response.body);
Logger.log(`Writen data to file: ${fileName}`);
return fileName;
}

View File

@@ -17,7 +17,7 @@
*/
export function rejectNil<T>(array: Array<T | undefined | null>): T[] {
// eslint-disable-next-line unicorn/no-null
return array.filter(it => it == null) as T[];
return array.filter(it => it != null) as T[];
}
/**

View File

@@ -15,16 +15,17 @@
import {Logger} from '@openstapps/logger';
import Ajv from 'ajv';
import betterAjvErrors, {IOutputError} from 'better-ajv-errors';
import {PathLike} from 'fs';
import type {PathLike} from 'fs';
import {readFile, writeFile} from 'fs/promises';
import {JSONSchema7} from 'json-schema';
import mustache from 'mustache';
import {Schema} from 'ts-json-schema-generator';
import {globPromisified, readFilePromisified, writeFilePromisified} from './common.js';
import {ExpectedValidationErrors, ValidationError, ValidationResult} from './types/validator.js';
import {isThingWithType} from './util/guards.js';
import path from 'path';
import re2 from './types/re2.js';
import {toPosixPath} from './util/posix-path.js';
import re2 from 're2';
import {glob} from 'glob';
import {fileURLToPath} from 'url';
/**
* StAppsCore validator
@@ -35,13 +36,13 @@ export class Validator {
*/
private readonly ajv = new Ajv.default({
verbose: true,
code: {regExp: re2},
code: {regExp: re2 as never},
});
/**
* Map of schema names to schemas
*/
private readonly schemas: {[type: string]: Schema} = {};
private readonly schemas: { [type: string]: Schema } = {};
/**
* A wrapper function for Ajv that transforms the error into the compatible old error
@@ -58,8 +59,9 @@ export class Validator {
*
* @param schemaDirectory Path to directory that contains schema files
*/
public async addSchemas(schemaDirectory: PathLike): Promise<string[]> {
const schemaFiles = await globPromisified(path.posix.join(toPosixPath(schemaDirectory), '*.json'));
public async addSchemas(schemaDirectory: string): Promise<string[]> {
const searchGlob = path.posix.join(schemaDirectory.replaceAll(path.sep, path.posix.sep), '*.json');
const schemaFiles = await glob(searchGlob);
if (schemaFiles.length === 0) {
throw new Error(`No schema files in ${schemaDirectory.toString()}!`);
@@ -70,7 +72,7 @@ export class Validator {
await Promise.all(
schemaFiles.map(async (file: string) => {
// read schema file
const buffer = await readFilePromisified(file);
const buffer = await readFile(file);
// add schema to map
this.schemas[path.basename(file, '.json')] = JSON.parse(buffer.toString());
@@ -92,7 +94,7 @@ export class Validator {
if (schema === undefined) {
if (isThingWithType(instance)) {
// schema name can be inferred from type string
const schemaSuffix = (instance as {type: string}).type
const schemaSuffix = (instance as { type: string }).type
.split(' ')
.map((part: string) => {
return part.slice(0, 1).toUpperCase() + part.slice(1);
@@ -175,8 +177,8 @@ export async function validateFiles(
const v = new Validator();
await v.addSchemas(schemaDirectory);
// get list of files to test
const testFiles = await globPromisified(path.join(resourcesDirectory, '*.json'));
// get a list of files to test
const testFiles = await glob(path.posix.join(resourcesDirectory.replaceAll(path.sep, path.posix.sep), '*.json'), {absolute: true});
if (testFiles.length === 0) {
throw new Error(`No test files in ${resourcesDirectory}!`);
@@ -191,7 +193,7 @@ export async function validateFiles(
testFiles.map(async (testFile: string) => {
const testFileName = path.basename(testFile);
const buffer = await readFilePromisified(path.join(resourcesDirectory, testFileName));
const buffer = await readFile(testFile);
// read test description from file
const testDescription = JSON.parse(buffer.toString());
@@ -260,12 +262,14 @@ export async function validateFiles(
* @param errors Errors that occurred in validation
*/
export async function writeReport(reportPath: PathLike, errors: ExpectedValidationErrors): Promise<void> {
// eslint-disable-next-line unicorn/prefer-module
let buffer = await readFilePromisified(path.resolve(__dirname, '..', 'resources', 'file.html.mustache'));
let buffer = await readFile(
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'file.html.mustache'),
);
const fileTemplate = buffer.toString();
// eslint-disable-next-line unicorn/prefer-module
buffer = await readFilePromisified(path.resolve(__dirname, '..', 'resources', 'error.html.mustache'));
buffer = await readFile(
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'error.html.mustache'),
);
const errorTemplate = buffer.toString();
let output = '';
@@ -295,11 +299,12 @@ export async function writeReport(reportPath: PathLike, errors: ExpectedValidati
});
}
// eslint-disable-next-line unicorn/prefer-module
buffer = await readFilePromisified(path.resolve(__dirname, '..', 'resources', 'report.html.mustache'));
buffer = await readFile(
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'report.html.mustache'),
);
const reportTemplate = buffer.toString();
await writeFilePromisified(
await writeFile(
reportPath,
mustache.render(reportTemplate, {
report: output,

View File

@@ -15,9 +15,9 @@
*/
import {Logger} from '@openstapps/logger';
import {expect} from 'chai';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {cwd} from 'process';
import {getTsconfigPath} from '../src/common.js';
import path from 'path';
import {fileURLToPath} from 'url';
process.on('unhandledRejection', (reason: unknown): void => {
if (reason instanceof Error) {
@@ -26,10 +26,10 @@ process.on('unhandledRejection', (reason: unknown): void => {
process.exit(1);
});
@suite(timeout(20_000), slow(10_000))
export class CommonSpec {
@test
async getTsconfigPath() {
expect(getTsconfigPath(__dirname)).to.be.equal(cwd());
}
}
describe('common', function () {
describe('getTsconfigPath', function () {
it('should get tsconfig path', function () {
expect(getTsconfigPath(path.dirname(fileURLToPath(import.meta.url)))).to.be.equal(process.cwd());
});
});
});

View File

@@ -14,36 +14,32 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {existsSync, unlinkSync} from 'fs';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {createDiagram, createDiagramFromString} from '../src/uml/create-diagram.js';
import {UMLConfig} from '../src/uml/uml-config.js';
import {LightweightDefinition, lightweightDefinitionsFromPath} from '@openstapps/easy-ast';
import {unlink} from 'fs/promises';
import {createDiagram, createDiagramFromString} from '../src/index.js';
import {UMLConfig} from '../src/index.js';
import {lightweightDefinitionsFromPath} from '@openstapps/easy-ast';
import nock = require('nock');
import path from 'path';
import {existsSync} from 'fs';
import {fileURLToPath} from 'url';
@suite(timeout(15_000), slow(5000))
export class CreateDiagramSpec {
plantUmlConfig: UMLConfig;
describe('CreateDiagram', function () {
this.timeout(15_000);
this.slow(5000);
definitions: LightweightDefinition[];
const plantUmlConfig: UMLConfig = {
definitions: [],
showAssociations: true,
showEnumValues: true,
showInheritance: true,
showInheritedProperties: true,
showOptionalProperties: true,
showProperties: true,
};
constructor() {
this.plantUmlConfig = {
definitions: [],
showAssociations: true,
showEnumValues: true,
showInheritance: true,
showInheritedProperties: true,
showOptionalProperties: true,
showProperties: true,
};
const definitions = lightweightDefinitionsFromPath('./test/model');
this.definitions = lightweightDefinitionsFromPath('./test/model');
}
@test
async shouldRefuseRequest() {
it('should refuse request', async function () {
const testPlantUmlCode = 'class Test{\n}';
try {
await createDiagramFromString(testPlantUmlCode, 'http://plantuml:8080');
@@ -54,7 +50,7 @@ export class CreateDiagramSpec {
new Error('getaddrinfo ENOTFOUND plantuml').message,
]).to.include((error as NodeJS.ErrnoException).message);
}
}
});
/**
* This test will only test the functionality of the method
@@ -63,26 +59,25 @@ export class CreateDiagramSpec {
* - Writing the response to a file
* This test will not check the file content
*/
@test
async shouldCreateDiagrams() {
it('should create diagrams', async function () {
nock('http://plantuml:8080')
.persist()
.get(() => true)
.reply(200, 'This will be the file content');
let fileName = await createDiagram(this.definitions, this.plantUmlConfig, 'http://plantuml:8080');
let filePath = path.resolve(__dirname, '..', fileName);
expect(await existsSync(filePath)).to.equal(true);
let fileName = await createDiagram(definitions, plantUmlConfig, 'http://plantuml:8080');
let filePath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', fileName);
expect(existsSync(filePath)).to.be.true;
await unlinkSync(fileName);
this.plantUmlConfig.showAssociations = false;
await unlink(fileName);
plantUmlConfig.showAssociations = false;
this.plantUmlConfig.showInheritance = false;
fileName = await createDiagram(this.definitions, this.plantUmlConfig, 'http://plantuml:8080');
filePath = path.resolve(__dirname, '..', fileName);
expect(await existsSync(filePath)).to.equal(true);
await unlinkSync(fileName);
plantUmlConfig.showInheritance = false;
fileName = await createDiagram(definitions, plantUmlConfig, 'http://plantuml:8080');
filePath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', fileName);
expect(existsSync(filePath)).to.be.true;
await unlink(fileName);
nock.cleanAll();
}
}
});
});

View File

@@ -15,9 +15,9 @@
*/
import {Logger} from '@openstapps/logger';
import {expect} from 'chai';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {Converter} from '../src/schema.js';
import path from 'path';
import {fileURLToPath} from 'url';
process.on('unhandledRejection', (error: unknown) => {
if (error instanceof Error) {
@@ -26,11 +26,14 @@ process.on('unhandledRejection', (error: unknown) => {
process.exit(1);
});
@suite(timeout(40_000), slow(10_000))
export class SchemaSpec {
@test
async getSchema() {
const converter = new Converter(path.join(__dirname, '..', 'src', 'resources'));
describe('Schema', function () {
this.timeout(40_000);
this.slow(10_000);
it('should create schema', function () {
const converter = new Converter(
path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src', 'resources'),
);
const schema = converter.getSchema('Foo', '0.0.1');
expect(schema).to.be.deep.equal({
@@ -77,5 +80,5 @@ export class SchemaSpec {
required: ['lorem', 'type'],
type: 'object',
});
}
}
});
});

View File

@@ -15,14 +15,14 @@
*/
import {Logger} from '@openstapps/logger';
import {expect} from 'chai';
import {existsSync, mkdirSync, writeFileSync} from 'fs';
import {existsSync} from 'fs';
import {JSONSchema7 as Schema} from 'json-schema';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import rimraf from 'rimraf';
import {Foo} from '../src/resources/foo.js';
import {Converter} from '../src/schema.js';
import {Validator} from '../src/validate.js';
import path from 'path';
import {fileURLToPath} from 'url';
import {rm, mkdir, writeFile} from 'fs/promises';
import {Converter} from '../src/index.js';
process.on('unhandledRejection', (error: unknown) => {
if (error instanceof Error) {
@@ -31,57 +31,55 @@ process.on('unhandledRejection', (error: unknown) => {
process.exit(1);
});
const tmpdir = path.join(__dirname, 'tmp');
const tmpdir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'tmp');
const fooInstance: Foo = {
lorem: 'ipsum',
type: 'Foo',
};
@suite(timeout(40_000), slow(5000))
export class ValidateSpec {
static converter: Converter;
describe('Validator', function () {
this.timeout(40_000);
this.slow(5000);
static schema: Schema;
let schema: Schema;
let converter: Converter;
static before() {
this.converter = new Converter(path.join(__dirname, '..', 'src', 'resources'));
this.schema = this.converter.getSchema('Foo', '0.0.1');
beforeEach(async function () {
converter = new Converter(
path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src', 'resources'),
);
schema = converter.getSchema('Foo', '0.0.1');
if (!existsSync(tmpdir)) {
mkdirSync(tmpdir);
await mkdir(tmpdir);
}
writeFileSync(path.join(tmpdir, 'SCFoo.json'), JSON.stringify(this.schema, undefined, 2));
}
await writeFile(path.join(tmpdir, 'SCFoo.json'), JSON.stringify(schema, undefined, 2));
});
static after() {
rimraf(tmpdir, error => {
// tslint:disable-next-line: no-unused-expression
afterEach(async function () {
try {
await rm(tmpdir, {recursive: true});
} catch (error) {
expect(error, `Unable to remove temporary directory for tests at: ${tmpdir}`).to.be.null;
});
}
}
});
@test
async validateBySchemaIdentifyingString() {
it('should validate by schema identifying string', async function () {
const validator = new Validator();
await validator.addSchemas(tmpdir);
const validationResult = validator.validate(fooInstance, 'SCFoo');
// tslint:disable-next-line: no-unused-expression
expect(validationResult.errors, JSON.stringify(validationResult.errors, undefined, 2)).to.be.empty;
}
});
@test
async validateBySchemaInstance() {
it('should validate by schema instance', async function () {
const validator = new Validator();
const validationResult = validator.validate(fooInstance, ValidateSpec.schema);
// tslint:disable-next-line: no-unused-expression
const validationResult = validator.validate(fooInstance, schema);
expect(validationResult.errors, JSON.stringify(validationResult.errors, undefined, 2)).to.be.empty;
}
});
@test
async validateIntrinsic() {
it('should validate intrinsic', async function () {
const validator = new Validator();
await validator.addSchemas(tmpdir);
const validationResult = validator.validate(fooInstance);
// tslint:disable-next-line: no-unused-expression
expect(validationResult.errors, JSON.stringify(validationResult.errors, undefined, 2)).to.be.empty;
}
}
});
});

View File

@@ -26,15 +26,15 @@
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"build": "tsup --dts && pnpm run mappings && pnpm run schema",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"mappings": "openstapps-es-mapping-generator mapping ../core/src -i minlength,pattern,see,tjs-format -m lib/mappings/mappings.json -a lib/mappings/aggregations.json",
"mappings-integration": "openstapps-es-mapping-generator put-es-templates lib/mappings/mappings.json http://elasticsearch:9200/",
"schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/@openstapps/core-tools/lib/cli.js schema src lib/schema",
"test": "nyc mocha --recursive 'test/*.spec.ts'"
"schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/@openstapps/core-tools/lib/app.js schema src lib/schema",
"test": "c8 mocha"
},
"dependencies": {
"@openstapps/core-tools": "workspace:*",
@@ -51,7 +51,7 @@
"@openstapps/logger": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@openstapps/easy-ast": "workspace:*",
"@types/chai": "4.3.4",
"@types/json-patch": "0.0.30",
"@types/json-schema": "7.0.11",
@@ -62,8 +62,7 @@
"chai": "4.3.7",
"conditional-type-checks": "1.0.6",
"mocha": "10.2.0",
"nyc": "15.1.0",
"rimraf": "4.4.0",
"c8": "7.13.0",
"source-map-support": "0.5.21",
"surge": "0.23.1",
"ts-node": "10.9.1",
@@ -115,12 +114,10 @@
"resources",
"openapi"
],
"nyc": {
"extends": "@openstapps/nyc-config"
},
"openstapps-configuration": {
"overrides": [
"lint"
"lint",
"build"
]
}
}

View File

@@ -13,7 +13,7 @@
* 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 equal from 'fast-deep-equal/es6';
import equal from 'fast-deep-equal/es6/index.js';
import clone from 'rfdc';
import {SCLanguageCode} from './general/i18n.js';
import {isThing} from './guards.js';

View File

@@ -13,41 +13,31 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {lightweightProjectFromPath} from '@openstapps/core-tools/lib/easy-ast/easy-ast';
import {LightweightProject} from '@openstapps/core-tools/lib/easy-ast/types/lightweight-project';
import {lightweightProjectFromPath, LightweightProject} from '@openstapps/easy-ast';
import {expect} from 'chai';
import {reduce} from 'lodash';
process.on('unhandledRejection', err => {
throw err;
process.on('unhandledRejection', error => {
throw error;
});
describe('Mapping Compatibility', () => {
let project: LightweightProject;
before(function () {
this.timeout(15000);
this.slow(10000);
this.timeout(15_000);
this.slow(10_000);
project = lightweightProjectFromPath('src');
});
it('non-exported definitions should not have duplicate names across files', () => {
reduce(
project,
(result, file) =>
reduce(
file,
(result2, _, key) => {
expect(result2[key]).to.be.undefined;
return {
[key]: true,
...result2,
};
},
result,
),
{} as Record<string, boolean>,
);
const names = new Set<string>();
for (const file in project) {
for (const definition in project[file]) {
expect(names).not.to.include(definition);
names.add(definition);
}
}
});
});

View File

@@ -0,0 +1,33 @@
import {SCBuildingWithoutReferences, SCThingType} from '../../src/index.js';
export const building: SCBuildingWithoutReferences = {
address: {
addressCountry: 'base-address.addressCountry',
addressLocality: 'base-address.addressLocality',
postalCode: 'base-address.postalCode',
streetAddress: 'base-address.streetAddress',
},
categories: ['office', 'education'],
floors: ['base-floor0', 'base-floor1'],
geo: {
point: {
coordinates: [12, 13],
type: 'Point',
},
},
name: 'base-space-name',
translations: {
de: {
address: {
addressCountry: 'de-address.addressCountry',
addressLocality: 'de-address.addressLocality',
postalCode: 'de-address.postalCode',
streetAddress: 'de-address.streetAddress',
},
floors: ['de-floor0', 'de-floor1'],
name: 'de-space-name',
},
},
type: SCThingType.Building,
uid: '540862f3-ea30-5b8f-8678-56b4dc217140',
};

View File

@@ -0,0 +1,9 @@
import {SCBulkResponse, SCThingType} from '../../src/index.js';
export const bulkResponse: SCBulkResponse = {
expiration: '2009-06-30T18:30:00+02:00 ',
source: 'bar',
state: 'done',
type: SCThingType.Dish,
uid: 'foo',
};

View File

@@ -0,0 +1,25 @@
import {SCSearchResponse} from '../../src/index.js';
import {dishWithTranslation} from './dish-with-translation.js';
export const dishWithTranslationSearchResponse: SCSearchResponse = {
data: [dishWithTranslation],
facets: [
{
buckets: [
{
count: 1,
key: 'key',
},
],
field: 'field',
},
],
pagination: {
count: 1,
offset: 0,
total: 1,
},
stats: {
time: 1,
},
};

View File

@@ -0,0 +1,17 @@
import {SCDish, SCThingOriginType, SCThingType} from '../../src/index.js';
export const dishWithTranslation: SCDish = {
categories: ['appetizer'],
name: 'foo',
origin: {
created: '',
type: SCThingOriginType.User,
},
translations: {
de: {
name: 'Foo',
},
},
type: SCThingType.Dish,
uid: 'bar',
};

View File

@@ -0,0 +1,35 @@
import {SCDish, SCThingOriginType, SCThingType} from '../../src/index.js';
import {building} from './building.js';
export const dish: SCDish = {
categories: ['main dish', 'dessert'],
characteristics: [{name: 'base-characteristic0'}, {name: 'base-characteristic1'}],
name: 'base-dish-name',
offers: [
{
availability: 'in stock',
inPlace: building,
prices: {
default: 23.42,
},
provider: {
name: 'base-provider',
type: SCThingType.Organization,
uid: '540862f3-ea30-5b8f-8678-56b4dc217141',
},
},
],
origin: {
indexed: '1970-01-01T00:00:00.000Z',
name: 'dish-connector',
type: SCThingOriginType.Remote,
},
translations: {
de: {
characteristics: [{name: 'de-characteristic0'}, {name: 'de-characteristic1'}],
name: 'de-dish-name',
},
},
type: SCThingType.Dish,
uid: '540862f3-ea30-5b8f-8678-56b4dc217140',
};

View File

@@ -0,0 +1,13 @@
import {SCThingOriginType} from '../../src/index.js';
import {SCDish} from '../../lib/index.js';
export const notADish: Omit<SCDish, 'type'> & {type: 'foobar'} = {
categories: ['appetizer'],
name: 'foo',
origin: {
created: '',
type: SCThingOriginType.User,
},
type: 'foobar',
uid: 'bar',
};

View File

@@ -0,0 +1,18 @@
import {SCSetting, SCSettingInputType, SCThingOriginType, SCThingType} from '../../src/index.js';
export const setting: SCSetting = {
categories: ['profile'],
defaultValue: 'student',
description: 'base-description',
inputType: SCSettingInputType.SingleChoice,
name: 'group',
order: 1,
origin: {
indexed: '2018-11-11T14:30:00Z',
name: 'Dummy',
type: SCThingOriginType.Remote,
},
type: SCThingType.Setting,
uid: '2c97aa36-4aa2-43de-bc5d-a2b2cb3a530e',
values: ['student', 'employee', true, 42],
};

View File

@@ -16,18 +16,17 @@ import {
isLightweightClass,
isLightweightEnum,
isUnionType,
} from '@openstapps/core-tools/lib/easy-ast/ast-util';
import {LightweightAliasDefinition} from '@openstapps/core-tools/lib/easy-ast/types/lightweight-alias-definition';
import {LightweightProjectWithIndex} from '@openstapps/core-tools/lib/easy-ast/types/lightweight-project';
import {LightweightType} from '@openstapps/core-tools/lib/easy-ast/types/lightweight-type';
import {LightweightClassDefinition} from '@openstapps/core-tools/src/easy-ast/types/lightweight-class-definition';
import {LightweightDefinition} from '@openstapps/core-tools/src/easy-ast/types/lightweight-definition';
import {LightweightProperty} from '@openstapps/core-tools/src/easy-ast/types/lightweight-property';
LightweightAliasDefinition,
LightweightProjectWithIndex,
LightweightType,
LightweightClassDefinition,
LightweightDefinition,
LightweightProperty,
} from '@openstapps/easy-ast';
import {expect} from 'chai';
import {assign, chain, clone, flatMap, isNil, reduce, reject, some} from 'lodash';
process.on('unhandledRejection', err => {
throw err;
process.on('unhandledRejection', error => {
throw error;
});
describe('Features', () => {
@@ -37,8 +36,8 @@ describe('Features', () => {
let thingsWithoutReferences: LightweightClassDefinition[];
before(function () {
this.timeout(15000);
this.slow(10000);
this.timeout(15_000);
this.slow(10_000);
project = new LightweightProjectWithIndex('src');
@@ -51,9 +50,7 @@ describe('Features', () => {
referenceName: 'SCDiff',
});
expect(
thingsReflection.type?.specificationTypes?.every(it => typeof it.referenceName !== 'undefined'),
).to.be.true;
expect(thingsReflection.type?.specificationTypes?.map(it => it.referenceName)).not.to.include(undefined);
thingNames = thingsReflection.type?.specificationTypes?.map(type => type.referenceName!) ?? [];
things = thingNames.map(it => project.definitions[it]).filter(isLightweightClass);
thingsWithoutReferences = thingNames
@@ -64,15 +61,22 @@ describe('Features', () => {
const inheritedProperties = function (
classLike: LightweightClassDefinition,
): Record<string, LightweightProperty> | undefined {
return reduce(
[...(classLike.implementedDefinitions ?? []), ...(classLike.extendedDefinitions ?? [])],
(obj, extension) => {
const object = project.definitions[extension.referenceName ?? ''];
const extendClause = [
...(classLike.implementedDefinitions ?? []),
...(classLike.extendedDefinitions ?? []),
];
const properties = {...classLike.properties};
return assign(obj, isLightweightClass(object) ? inheritedProperties(object) : obj);
},
clone(classLike.properties),
);
for (const definition of extendClause) {
const object = project.definitions[definition.referenceName!];
if (isLightweightClass(object)) {
Object.assign(properties, inheritedProperties(object));
} else {
Object.assign(properties, object);
}
}
return properties;
};
it('should have an origin', () => {
@@ -82,42 +86,36 @@ describe('Features', () => {
});
it('should not have duplicate names', () => {
reduce(
project.files,
(fileResult, file) =>
reduce(
file,
(definitionResult, definition: LightweightDefinition) => {
expect(definitionResult[definition.name]).to.be.undefined;
definitionResult[definition.name] = true; // something that's not undefined
const names = new Set<string>();
return definitionResult;
},
fileResult,
),
{} as Record<string, true>,
);
for (const fileName in project.files) {
const file = project.files[fileName];
for (const definition in file) {
const definitionName = file[definition].name;
expect(names).not.to.include(definitionName);
names.add(definitionName);
}
}
});
it('should not have properties referencing SCThing', () => {
const allPropertyReferenceNames: (property: LightweightProperty) => string[] = property =>
reject(
[property.type.referenceName!, ...flatMap(property.properties, allPropertyReferenceNames)],
isNil,
);
[
property.type.referenceName!,
...Object.values(property.properties ?? []).flatMap(allPropertyReferenceNames),
].filter(it => !!it);
const typeHasSCThingReferences: (type?: LightweightType) => boolean = type =>
type?.referenceName
? hasSCThingReferences(project.definitions[type.referenceName])
: some(type?.specificationTypes, typeHasSCThingReferences);
: type?.specificationTypes?.some(typeHasSCThingReferences) === true;
const hasSCThingReferences: (definition?: LightweightDefinition) => boolean = definition =>
isLightweightClass(definition)
? chain(inheritedProperties(definition))
.flatMap(it => flatMap(it.properties, allPropertyReferenceNames))
? Object.values(inheritedProperties(definition) ?? [])
.flatMap(it => Object.values(it.properties ?? []).flatMap(allPropertyReferenceNames))
.map(it => project.definitions[it] as LightweightDefinition)
.some(it => it.name === 'SCThing' || hasSCThingReferences(it))
.value()
: definition
? typeHasSCThingReferences(definition.type)
: false;
@@ -127,16 +125,18 @@ describe('Features', () => {
}
});
/**
* Checks if a definition is an SCThing
*/
function extendsSCThing(definition?: LightweightDefinition): boolean {
return isLightweightClass(definition)
? chain([
? [
...((definition as LightweightClassDefinition).extendedDefinitions ?? []),
...((definition as LightweightClassDefinition).implementedDefinitions ?? []),
])
]
.map(it => it.referenceName)
.reject(isNil)
.filter(it => !!it)
.some(it => it === 'SCThing' || extendsSCThing(project.definitions[it!]))
.value()
: false;
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-null */
/*
* Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -12,12 +13,7 @@
* 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 {slow, suite, test, timeout} from '@testdeck/mocha';
import {SCBulkResponse} from '../src/protocol/routes/bulk-request.js';
import {SCMultiSearchResponse} from '../src/protocol/routes/search-multi.js';
import {SCSearchResponse} from '../src/protocol/routes/search.js';
import {SCThingOriginType, SCThingType} from '../src/things/abstract/thing.js';
import {SCDish} from '../src/things/dish.js';
import {SCMultiSearchResponse} from '../src/index.js';
import {expect} from 'chai';
import {
isBulkResponse,
@@ -26,117 +22,104 @@ import {
isThing,
isThingWithTranslations,
} from '../src/guards.js';
import {bulkResponse} from './dummy/bulk-response.js';
import {dishWithTranslation} from './dummy/dish-with-translation.js';
import {dishWithTranslationSearchResponse} from './dummy/dish-with-translation-search-response.js';
import {notADish} from './dummy/not-a-dish.js';
import {SCBulkResponse} from '../src/protocol/routes/bulk-request.js';
import {SCSearchResponse} from '../src/protocol/routes/search.js';
import {SCMultiSearchResponse} from '../src/protocol/routes/search-multi.js';
import {SCThingOriginType, SCThingType} from '../src/things/abstract/thing.js';
import {SCDish} from '../src/things/dish';
import {SCDish} from '../src/things/dish.js';
@suite(timeout(10000), slow(5000))
export class GuardsSpec {
static bulkResponse: SCBulkResponse = {
expiration: '2009-06-30T18:30:00+02:00 ',
source: 'bar',
state: 'done',
type: SCThingType.Dish,
uid: 'foo',
};
describe('Guards', function () {
this.timeout(10_000);
this.slow(5000);
static dishWithTranslation: SCDish = {
categories: ['appetizer'],
name: 'foo',
origin: {
created: '',
type: SCThingOriginType.User,
},
translations: {
de: {
name: 'Foo',
},
},
type: SCThingType.Dish,
uid: 'bar',
};
describe('isBulkResponse', function () {
it(`should not accept nullish values`, function () {
expect(isBulkResponse(null)).to.be.false;
});
static notADish = {
categories: ['appetizer'],
name: 'foo',
origin: {
created: '',
type: SCThingOriginType.User,
},
type: 'foobar',
uid: 'bar',
};
it('should not accept a dish', function () {
expect(isBulkResponse(dishWithTranslation)).to.be.false;
});
static searchResponse: SCSearchResponse = {
data: [GuardsSpec.dishWithTranslation],
facets: [
{
buckets: [
{
count: 1,
key: 'key',
},
],
field: 'field',
},
],
pagination: {
count: 1,
offset: 0,
total: 1,
},
stats: {
time: 1,
},
};
it('should accept a bulk', function () {
expect(isBulkResponse(bulkResponse)).to.be.true;
});
});
@test
public isBulkResponse() {
expect(isBulkResponse(null)).to.be.equal(false);
expect(isBulkResponse(GuardsSpec.dishWithTranslation)).to.be.equal(false);
expect(isBulkResponse(GuardsSpec.bulkResponse)).to.be.equal(true);
}
@test
public isMultiSearchResponse() {
describe('isMultiSearchResponse', function () {
const multiSearchResponse: SCMultiSearchResponse = {
foo: GuardsSpec.searchResponse,
foo: dishWithTranslationSearchResponse,
};
expect(isMultiSearchResponse(multiSearchResponse)).to.be.equal(true);
const notAMultiSearchResponse = {...multiSearchResponse, ...{bar: 'baz'}};
expect(isMultiSearchResponse(notAMultiSearchResponse)).to.be.equal(false);
delete multiSearchResponse.foo;
expect(isMultiSearchResponse(multiSearchResponse)).to.be.equal(false);
}
@test
public isSearchResponse() {
const notASearchResponse = {...GuardsSpec.searchResponse};
// @ts-ignore
delete notASearchResponse.pagination;
expect(isSearchResponse(notASearchResponse)).to.be.equal(false);
// @ts-ignore
delete notASearchResponse.data;
expect(isSearchResponse(notASearchResponse)).to.be.equal(false);
expect(isSearchResponse(null)).to.be.equal(false);
expect(isSearchResponse(GuardsSpec.searchResponse)).to.be.equal(true);
}
it('should accept a multi search response', function () {
expect(isMultiSearchResponse(multiSearchResponse)).to.be.true;
});
@test
public isThing() {
expect(isThing('foo')).to.be.equal(false);
expect(isThing({type: 'foo'})).to.be.equal(false);
expect(isThing(GuardsSpec.notADish)).to.be.equal(false);
expect(isThing(GuardsSpec.dishWithTranslation)).to.be.equal(true);
}
it('should not accept a multi search response with invalid search requests', function () {
const notAMultiSearchResponse = {...multiSearchResponse, bar: 'baz'};
expect(isMultiSearchResponse(notAMultiSearchResponse)).to.be.false;
});
@test
public isThingWithTranslations() {
const dishWithoutTranslation = {...GuardsSpec.dishWithTranslation};
delete dishWithoutTranslation.translations;
expect(isThingWithTranslations(dishWithoutTranslation)).to.be.equal(false);
expect(isThingWithTranslations(GuardsSpec.dishWithTranslation)).to.be.equal(true);
}
}
it('should not accept empty responses', function () {
expect(isMultiSearchResponse({})).to.be.false;
});
});
describe('isSearchResponse', function () {
it('should accept a search response', function () {
expect(isSearchResponse(dishWithTranslationSearchResponse)).to.be.true;
});
it('should not accept nullish values', function () {
expect(isSearchResponse(null)).to.be.false;
});
it('should not accept a response without pagination', function () {
const response = {...dishWithTranslationSearchResponse};
// @ts-expect-error this is on purpose of course
delete response.pagination;
expect(isSearchResponse(response)).to.be.false;
});
it('should not accept a response without data', function () {
const response = {...dishWithTranslationSearchResponse};
// @ts-expect-error this is on purpose of course
delete response.data;
expect(isSearchResponse(response)).to.be.false;
});
});
describe('isThing', function () {
it('should not accept strings', function () {
expect(isThing('foo')).to.be.false;
});
it('should not accept objects with arbitrary type values', function () {
expect(isThing({type: 'foo'})).to.be.false;
});
it('should not accept things with missing props', function () {
expect(isThing(notADish)).to.be.false;
});
it('should accept valid things', function () {
expect(isThing(dishWithTranslation)).to.be.true;
});
});
describe('isThingWithTranslations', function () {
it('should not accept things without translations', function () {
const dishWithoutTranslation = {...dishWithTranslation};
delete dishWithoutTranslation.translations;
expect(isThingWithTranslations(dishWithoutTranslation)).to.be.false;
});
it('should accept things with translations', function () {
expect(isThingWithTranslations(dishWithTranslation)).to.be.true;
});
});
});

View File

@@ -14,69 +14,51 @@
*/
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {expect} from 'chai';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {SCBulkRoute} from '../src/protocol/routes/bulk-request.js';
import {SCBulkAddRoute} from '../src/protocol/routes/bulk-add.js';
import {SCThingUpdateRoute} from '../src/protocol/routes/thing-update.js';
import {SCBulkRoute} from '../src/index.js';
import {SCBulkAddRoute} from '../src/index.js';
import {SCThingUpdateRoute} from '../src/index.js';
@suite(timeout(10000), slow(5000))
export class RoutesSpec {
@test
public bulkAddRouteUrlPath() {
const bulkAddRoute = new SCBulkAddRoute();
describe('Routes', function () {
this.timeout(10_000);
this.slow(5000);
it('should produce correct BulkAddRoute url path', function () {
expect(
bulkAddRoute.getUrlPath({
new SCBulkAddRoute().getUrlPath({
UID: '540862f3-ea30-5b8f-8678-56b4dc217140',
}),
).to.equal('/bulk/540862f3-ea30-5b8f-8678-56b4dc217140');
}
});
@test
public bulkRouteUrlPath() {
const bulkRoute = new SCBulkRoute();
expect(bulkRoute.getUrlPath()).to.equal('/bulk');
}
@test
public thingUpdateRouteUrlPath() {
const thingUpdateRoute = new SCThingUpdateRoute();
it('should produce correct BlukRoute url path', function () {
expect(new SCBulkRoute().getUrlPath()).to.equal('/bulk');
});
it('should produce correct ThingUpdateRoute url path', function () {
expect(
thingUpdateRoute.getUrlPath({
new SCThingUpdateRoute().getUrlPath({
TYPE: 'dish',
UID: '540862f3-ea30-5b8f-8678-56b4dc217140',
}),
).to.equal('/dish/540862f3-ea30-5b8f-8678-56b4dc217140');
}
});
@test
public tooManyParameters() {
const thingUpdateRoute = new SCThingUpdateRoute();
const fn = () => {
thingUpdateRoute.getUrlPath({
it('should throw an error if too many parameters are provided', function () {
expect(() =>
new SCThingUpdateRoute().getUrlPath({
FOO: 'bar',
TYPE: 'dish',
UID: '540862f3-ea30-5b8f-8678-56b4dc217140',
});
};
}),
).to.throw('Extraneous parameters provided.');
});
expect(fn).to.throw('Extraneous parameters provided.');
}
@test
public wrongParameters() {
const thingUpdateRoute = new SCThingUpdateRoute();
const fn = () => {
thingUpdateRoute.getUrlPath({
it('should throw an error if wrong parameters are provided', function () {
expect(() =>
new SCThingUpdateRoute().getUrlPath({
TYPO: 'dish',
UID: '540862f3-ea30-5b8f-8678-56b4dc217140',
});
};
expect(fn).to.throw("Parameter 'TYPE' not provided.");
}
}
}),
).to.throw("Parameter 'TYPE' not provided.");
});
});

View File

@@ -1,29 +1,22 @@
import {validateFiles, writeReport} from '@openstapps/core-tools/lib/validate';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {validateFiles, writeReport} from '@openstapps/core-tools';
import {expect} from 'chai';
import {mkdirSync} from 'fs';
import {join, resolve} from 'path';
import {mkdir} from 'fs/promises';
import path from 'path';
@suite(timeout(15000), slow(10000))
export class SchemaSpec {
@test
async 'validate against test files'() {
const errorsPerFile = {
...(await validateFiles(resolve('lib', 'schema'), resolve('test', 'resources'))),
...(await validateFiles(resolve('lib', 'schema'), resolve('test', 'resources', 'indexable'))),
};
describe('Schema', function () {
this.timeout(15_000);
this.slow(10_000);
let unexpected = false;
Object.keys(errorsPerFile).forEach(file => {
unexpected = unexpected || errorsPerFile[file].some(error => !error.expected);
});
it('should validate against test files', async function () {
const errorsPerFile = await validateFiles(path.resolve('lib', 'schema'), path.resolve('test', 'resources'));
mkdirSync('report', {
recursive: true,
});
await mkdir('report', {recursive: true});
await writeReport(path.join('report', 'index.html'), errorsPerFile);
await writeReport(join('report', 'index.html'), errorsPerFile);
expect(unexpected).to.be.equal(false);
}
}
for (const file of Object.keys(errorsPerFile)) {
for (const error of errorsPerFile[file]) {
expect(error.expected).to.be.true;
}
}
});
});

View File

@@ -15,93 +15,11 @@
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {expect} from 'chai';
import clone from 'rfdc';
import {SCThingOriginType, SCThingRemoteOrigin, SCThingType} from '../src/things/abstract/thing.js';
import {SCBuildingWithoutReferences} from '../src/things/building.js';
import {SCDish, SCDishMeta} from '../src/things/dish.js';
import {SCSetting, SCSettingInputType} from '../src/things/setting.js';
import {SCThingTranslator} from '../src/translator.js';
const building: SCBuildingWithoutReferences = {
address: {
addressCountry: 'base-address.addressCountry',
addressLocality: 'base-address.addressLocality',
postalCode: 'base-address.postalCode',
streetAddress: 'base-address.streetAddress',
},
categories: ['office', 'education'],
floors: ['base-floor0', 'base-floor1'],
geo: {
point: {
coordinates: [12.0, 13.0],
type: 'Point',
},
},
name: 'base-space-name',
translations: {
de: {
address: {
addressCountry: 'de-address.addressCountry',
addressLocality: 'de-address.addressLocality',
postalCode: 'de-address.postalCode',
streetAddress: 'de-address.streetAddress',
},
floors: ['de-floor0', 'de-floor1'],
name: 'de-space-name',
},
},
type: SCThingType.Building,
uid: '540862f3-ea30-5b8f-8678-56b4dc217140',
};
const dish: SCDish = {
categories: ['main dish', 'dessert'],
characteristics: [{name: 'base-characteristic0'}, {name: 'base-characteristic1'}],
name: 'base-dish-name',
offers: [
{
availability: 'in stock',
inPlace: building,
prices: {
default: 23.42,
},
provider: {
name: 'base-provider',
type: SCThingType.Organization,
uid: '540862f3-ea30-5b8f-8678-56b4dc217141',
},
},
],
origin: {
indexed: '1970-01-01T00:00:00.000Z',
name: 'dish-connector',
type: SCThingOriginType.Remote,
},
translations: {
de: {
characteristics: [{name: 'de-characteristic0'}, {name: 'de-characteristic1'}],
name: 'de-dish-name',
},
},
type: SCThingType.Dish,
uid: '540862f3-ea30-5b8f-8678-56b4dc217140',
};
const setting: SCSetting = {
categories: ['profile'],
defaultValue: 'student',
description: 'base-description',
inputType: SCSettingInputType.SingleChoice,
name: 'group',
order: 1,
origin: {
indexed: '2018-11-11T14:30:00Z',
name: 'Dummy',
type: SCThingOriginType.Remote,
},
type: SCThingType.Setting,
uid: '2c97aa36-4aa2-43de-bc5d-a2b2cb3a530e',
values: ['student', 'employee', true, 42],
};
import {SCThingRemoteOrigin} from '../src/index.js';
import {SCDishMeta} from '../src/index.js';
import {SCThingTranslator} from '../src/index.js';
import {dish} from './dummy/dish.js';
import {setting} from './dummy/setting.js';
const translator = new SCThingTranslator('de');
const translatorEN = new SCThingTranslator('en');
@@ -111,242 +29,207 @@ const translatorWithFallback = new SCThingTranslator('tt');
const translatedThingDE = translator.translate(dish);
const translatedThingFallback = translatorWithFallback.translate(dish);
@suite(timeout(10000), slow(5000))
export class TranslationSpecInplace {
@test
public directEnumSingleValue() {
expect(translator.translatedAccess(setting).inputType()).to.equal('einfache Auswahl');
}
describe('Translator', function () {
this.timeout(10_000);
this.slow(5000);
@test
public directStringLiteralType() {
expect(translator.translatedAccess(dish).type()).to.equal('Essen');
expect(translatedThingDE.type).to.equal('Essen');
}
@test
public directStringProperty() {
expect(translator.translatedAccess(dish).name()).to.equal('de-dish-name');
expect(translatedThingDE.name).to.equal('de-dish-name');
}
@test
public directArrayOfString() {
expect(translator.translatedAccess(dish).characteristics()).to.deep.equal([
{name: 'de-characteristic0'},
{name: 'de-characteristic1'},
]);
expect(translatedThingDE.characteristics).to.deep.equal([
{name: 'de-characteristic0'},
{name: 'de-characteristic1'},
]);
}
@test
public directArrayOfStringSubscript() {
expect(translator.translatedAccess(dish).characteristics[1]()).to.deep.equal({
name: 'de-characteristic1',
describe('direct', function () {
it('should translate enum single value', function () {
expect(translator.translatedAccess(setting).inputType).to.equal('einfache Auswahl');
});
expect(translatedThingDE.characteristics![1]).to.deep.equal({name: 'de-characteristic1'});
}
@test
public directMetaArrayOfString() {
expect(translator.translatedAccess(dish).categories()).to.deep.equal(['Hauptgericht', 'Nachtisch']);
expect(translatedThingDE.categories).to.deep.equal(['Hauptgericht', 'Nachtisch']);
}
@test
public directMetaArrayOfStringSubscript() {
expect(translator.translatedAccess(dish).categories[1]()).to.equal('Nachtisch');
expect(translatedThingDE.categories[1]).to.equal('Nachtisch');
}
@test
public nestedStringLiteralType() {
expect(translator.translatedAccess(dish).offers[0].inPlace.type()).to.equal('Gebäude');
expect(translatedThingDE.offers![0].inPlace!.type).to.equal('Gebäude');
}
@test
public nestedStringProperty() {
expect(translator.translatedAccess(dish).offers[0].inPlace.name()).to.equal('de-space-name');
expect(translatedThingDE.offers![0].inPlace!.name).to.equal('de-space-name');
}
@test
public nestedMetaArrayOfString() {
expect(translator.translatedAccess(dish).offers[0].inPlace.categories()).to.deep.equal([
'Büro',
'Bildung',
]);
expect(translatedThingDE.offers![0].inPlace!.categories).to.deep.equal(['Büro', 'Bildung']);
}
@test
public nestedMetaArrayOfStringSubscript() {
expect(translator.translatedAccess(dish).offers[0].inPlace.categories[1]()).to.equal('Bildung');
expect(translatedThingDE.offers![0].inPlace!.categories[1]).to.equal('Bildung');
}
@test
public directStringLiteralTypeFallback() {
expect(translatorWithFallback.translatedAccess(dish).type()).to.equal('dish');
expect(translatedThingFallback.type).to.equal('dish');
}
@test
public directStringPropertyFallback() {
expect(translatorWithFallback.translatedAccess(dish).name()).to.equal('base-dish-name');
expect(translatedThingFallback.name).to.equal('base-dish-name');
}
@test
public directArrayOfStringSubscriptFallback() {
expect(translatorWithFallback.translatedAccess(dish).characteristics[1]()).to.deep.equal({
name: 'base-characteristic1',
it('should translate string literal type', function () {
expect(translator.translatedAccess(dish).type).to.equal('Essen');
expect(translatedThingDE.type).to.equal('Essen');
});
expect(translatedThingFallback.characteristics![1]).to.deep.equal({name: 'base-characteristic1'});
}
@test
public directMetaArrayOfStringFallback() {
expect(translatorWithFallback.translatedAccess(dish).categories()).to.deep.equal([
'main dish',
'dessert',
]);
expect(translatedThingFallback.categories).to.deep.equal(['main dish', 'dessert']);
}
it('should translate string property', function () {
expect(translator.translatedAccess(dish).name).to.equal('de-dish-name');
expect(translatedThingDE.name).to.equal('de-dish-name');
});
@test
public directMetaArrayOfStringSubscriptFallback() {
expect(translatorWithFallback.translatedAccess(dish).categories[1]()).to.equal('dessert');
expect(translatedThingFallback.categories[1]).to.equal('dessert');
}
it('should translate array of strings', function () {
expect(translator.translatedAccess(dish).characteristics).to.deep.equal([
{name: 'de-characteristic0'},
{name: 'de-characteristic1'},
]);
expect(translatedThingDE.characteristics).to.deep.equal([
{name: 'de-characteristic0'},
{name: 'de-characteristic1'},
]);
});
@test
public nestedStringLiteralTypeFallback() {
expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.type()).to.equal('building');
expect(translatedThingFallback.offers![0].inPlace!.type).to.equal('building');
}
it('should translate array of strings subscript', function () {
expect(translator.translatedAccess(dish).characteristics?.[1]).to.deep.equal({
name: 'de-characteristic1',
});
expect(translatedThingDE.characteristics![1]).to.deep.equal({name: 'de-characteristic1'});
});
@test
public nestedStringPropertyFallback() {
expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.name()).to.equal(
'base-space-name',
);
expect(translatedThingFallback.offers![0].inPlace!.name).to.equal('base-space-name');
}
it('should translate meta array of string', function () {
expect(translator.translatedAccess(dish).categories).to.deep.equal(['Hauptgericht', 'Nachtisch']);
expect(translatedThingDE.categories).to.deep.equal(['Hauptgericht', 'Nachtisch']);
});
@test
public nestedMetaArrayOfStringFallback() {
expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.categories()).to.deep.equal([
'office',
'education',
]);
expect(translatedThingFallback.offers![0].inPlace!.categories).to.deep.equal(['office', 'education']);
}
it('should translate meta array of strings subscript', function () {
expect(translator.translatedAccess(dish).categories[1]).to.equal('Nachtisch');
expect(translatedThingDE.categories[1]).to.equal('Nachtisch');
});
});
@test
public nestedMetaArrayOfStringSubscriptFallback() {
expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.categories[1]()).to.equal(
'education',
);
expect(translatedThingFallback.offers![0].inPlace!.categories[1]).to.equal('education');
}
describe('nested', function () {
it('should translate string literal type', function () {
expect(translator.translatedAccess(dish).offers?.[0].inPlace?.type).to.equal('Gebäude');
expect(translatedThingDE.offers![0].inPlace!.type).to.equal('Gebäude');
});
@test
public directStringLiteralTypeUndefined() {
const undefinedThing = eval('(x) => undefined;');
expect(translator.translatedAccess(undefinedThing())('defaultValue')).to.equal('defaultValue');
expect(translator.translatedAccess(dish).name('defaultValue')).to.not.equal('defaultValue');
}
it('should translate nested string property', function () {
expect(translator.translatedAccess(dish).offers?.[0].inPlace?.name).to.equal('de-space-name');
expect(translatedThingDE.offers![0].inPlace!.name).to.equal('de-space-name');
});
@test
public nestedMetaArrayOfStringSubscriptUndefined() {
const workingTranslation = eval(
"translator.translatedAccess(dish).offers[0].inPlace.categories[1]('printer');",
);
const defaultValueTranslation = eval(
"translator.translatedAccess(dish).offers[0].inPlace.categories[1234]('printer');",
);
it('should translate meta array of strings', function () {
expect(translator.translatedAccess(dish).offers?.[0].inPlace?.categories).to.deep.equal([
'Büro',
'Bildung',
]);
expect(translatedThingDE.offers![0].inPlace!.categories).to.deep.equal(['Büro', 'Bildung']);
});
expect(defaultValueTranslation).to.equal('printer');
expect(workingTranslation).to.not.equal('printer');
}
it('should translate meta array of strings subscript', function () {
expect(translator.translatedAccess(dish).offers?.[0].inPlace?.categories[1]).to.equal('Bildung');
expect(translatedThingDE.offers![0].inPlace!.categories[1]).to.equal('Bildung');
});
});
@test
public reaccessWithChangedSourceOmitsLRUCache() {
describe('direct (fallback)', function () {
it('should translate string literal types', function () {
expect(translatorWithFallback.translatedAccess(dish).type).to.equal('dish');
expect(translatedThingFallback.type).to.equal('dish');
});
it('should translate string property', function () {
expect(translatorWithFallback.translatedAccess(dish).name).to.equal('base-dish-name');
expect(translatedThingFallback.name).to.equal('base-dish-name');
});
it('should translate array of strings subscript', function () {
expect(translatorWithFallback.translatedAccess(dish).characteristics?.[1]).to.deep.equal({
name: 'base-characteristic1',
});
expect(translatedThingFallback.characteristics![1]).to.deep.equal({name: 'base-characteristic1'});
});
it('should translate meta array of strings', function () {
expect(translatorWithFallback.translatedAccess(dish).categories).to.deep.equal([
'main dish',
'dessert',
]);
expect(translatedThingFallback.categories).to.deep.equal(['main dish', 'dessert']);
});
it('should translate meta array of string subscript', function () {
expect(translatorWithFallback.translatedAccess(dish).categories[1]).to.equal('dessert');
expect(translatedThingFallback.categories[1]).to.equal('dessert');
});
});
describe('nested (fallback)', function () {
it('should translate string literal type', function () {
expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.type).to.equal('building');
expect(translatedThingFallback.offers![0].inPlace!.type).to.equal('building');
});
it('should translate string property', function () {
expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.name).to.equal(
'base-space-name',
);
expect(translatedThingFallback.offers![0].inPlace!.name).to.equal('base-space-name');
});
it('should translate meta array of string', function () {
expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.categories).to.deep.equal([
'office',
'education',
]);
expect(translatedThingFallback.offers![0].inPlace!.categories).to.deep.equal(['office', 'education']);
});
it('should translate meta array of strings subscript', function () {
expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.categories[1]).to.equal(
'education',
);
expect(translatedThingFallback.offers![0].inPlace!.categories[1]).to.equal('education');
});
});
it('should omit LRU cache with changed source', function () {
const translatorDE = new SCThingTranslator('de');
const dishCopy = clone()(dish);
const translatedDish = translatorDE.translatedAccess(dish);
const distructivelyTranslatedDish = translatorDE.translate(dish);
const destructivelyTranslatedDish = translatorDE.translate(dish);
(dishCopy.origin as SCThingRemoteOrigin).name = 'tranlator.spec';
(dishCopy.origin as SCThingRemoteOrigin).name = 'translator.spec';
expect(translatorDE.translatedAccess(dishCopy)).not.to.deep.equal(translatedDish);
expect(translatorDE.translate(dishCopy)).not.to.equal(distructivelyTranslatedDish);
}
expect(translatorDE.translate(dishCopy)).not.to.equal(destructivelyTranslatedDish);
});
@test
public changingTranslatorLanguageFlushesItsLRUCache() {
it('should flush its LRU cache with changed translator language', function () {
const translatorDE = new SCThingTranslator('de');
expect(translatorDE.translatedAccess(dish).name()).to.equal('de-dish-name');
expect(translatorDE.translatedAccess(dish).name).to.equal('de-dish-name');
expect(translatorDE.translate(dish).name).to.equal('de-dish-name');
translatorDE.language = 'en';
expect(translatorDE.translatedAccess(dish).name()).to.equal('base-dish-name');
expect(translatorDE.translatedAccess(dish).name).to.equal('base-dish-name');
expect(translatorDE.translate(dish).name).to.equal('base-dish-name');
}
});
@test
public forceTranslatorLRUCacheToOverflow() {
it('should force translator LRU cache to overflow', function () {
const translatorDE = new SCThingTranslator('de');
// Make sure to add more elements to the translator cache than the maximum cache capacity. See Translator.ts
for (let i = 0; i < 201; i++) {
const anotherDish = Object.assign({}, dish);
anotherDish.uid = String(i);
expect(translatorDE.translatedAccess(anotherDish).name()).to.equal('de-dish-name');
expect(translatorDE.translatedAccess(anotherDish).name).to.equal('de-dish-name');
}
}
}
});
});
@suite(timeout(10000), slow(5000))
export class MetaTranslationSpec {
@test
public consistencyWithMetaClass() {
describe('MetaTranslator', function () {
this.timeout(10_000);
this.slow(5000);
it('should have consistency with meta class', function () {
const dishMetaTranslationsDE = translator.translatedPropertyNames(dish.type);
const dishMetaTranslationsEN = translatorEN.translatedPropertyNames(dish.type);
expect(dishMetaTranslationsEN).to.not.deep.equal(dishMetaTranslationsDE);
expect(dishMetaTranslationsDE).to.deep.equal(new SCDishMeta().fieldTranslations.de);
expect(dishMetaTranslationsEN).to.deep.equal(new SCDishMeta().fieldTranslations.en);
}
});
@test
public retrieveTranslatedPropertyValueType() {
it('should retrieve translated property value type', function () {
const dishTypeDE = translator.translatedPropertyValue(dish.type, 'type');
const dishTypeEN = translatorEN.translatedPropertyValue(dish.type, 'type', undefined);
const dishTypeBASE = translatorWithFallback.translatedPropertyValue(dish.type, 'type');
expect(dishTypeDE).to.deep.equal(new SCDishMeta().fieldValueTranslations.de.type);
expect(dishTypeEN).to.deep.equal(new SCDishMeta().fieldValueTranslations.en.type);
expect(dishTypeBASE).to.deep.equal(new SCDishMeta().fieldValueTranslations.en.type);
}
});
@test
public retrieveTranslatedPropertyValueNested() {
it('should retrieve translated property value nested', function () {
const dishTypeDE = translator.translatedPropertyValue(dish.type, 'categories', 'main dish');
const dishTypeEN = translatorEN.translatedPropertyValue(dish.type, 'categories', 'main dish');
const dishTypeBASE = translatorWithFallback.translatedPropertyValue(dish.type, 'categories', 'main dish');
expect(dishTypeDE).to.deep.equal(new SCDishMeta().fieldValueTranslations.de.categories['main dish']);
expect(dishTypeEN).to.deep.equal(dish.categories[0]);
expect(dishTypeBASE).to.deep.equal(dish.categories[0]);
}
});
@test
public thingWithoutMetaClass() {
it('should translate thing without meta class', function () {
const dishCopy = clone()(dish);
const typeNonExistant = eval("(x) => x + 'typeNonExistant';");
// this will assign a non existant SCThingType to dishCopy
dishCopy.type = typeNonExistant();
const typeNonExistent = eval("(x) => x + 'typeNonExistent';");
// this will assign a non-existent SCThingType to dishCopy
dishCopy.type = typeNonExistent();
const dishMetaTranslationsDE = translator.translatedPropertyNames(dishCopy.type);
expect(dishMetaTranslationsDE).to.be.undefined;
}
}
});
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -13,35 +14,35 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {assert, Has, IsAny, IsNever, NotHas} from 'conditional-type-checks';
import {SCThing, SCThingWithoutReferences} from '../src/things/abstract/thing.js';
import {SCAcademicEvent, SCAcademicEventWithoutReferences} from '../src/things/academic-event.js';
import {SCArticle, SCArticleWithoutReferences} from '../src/things/article.js';
import {SCAssessment, SCAssessmentWithoutReferences} from '../src/things/assessment.js';
import {SCBook, SCBookWithoutReferences} from '../src/things/book.js';
import {SCBuilding, SCBuildingWithoutReferences} from '../src/things/building.js';
import {SCCatalog, SCCatalogWithoutReferences} from '../src/things/catalog.js';
import {SCContactPoint, SCContactPointWithoutReferences} from '../src/things/contact-point.js';
import {SCCourseOfStudy, SCCourseOfStudyWithoutReferences} from '../src/things/course-of-study.js';
import {SCDateSeries, SCDateSeriesWithoutReferences} from '../src/things/date-series.js';
import {SCDiff, SCDiffWithoutReferences} from '../src/things/diff.js';
import {SCDish, SCDishWithoutReferences} from '../src/things/dish.js';
import {SCFavorite, SCFavoriteWithoutReferences} from '../src/things/favorite.js';
import {SCFloor, SCFloorWithoutReferences} from '../src/things/floor.js';
import {SCMessage, SCMessageWithoutReferences} from '../src/things/message.js';
import {SCOrganization, SCOrganizationWithoutReferences} from '../src/things/organization.js';
import {SCPerson, SCPersonWithoutReferences} from '../src/things/person.js';
import {SCPointOfInterest, SCPointOfInterestWithoutReferences} from '../src/things/point-of-interest.js';
import {SCRoom, SCRoomWithoutReferences} from '../src/things/room.js';
import {SCSemester, SCSemesterWithoutReferences} from '../src/things/semester.js';
import {SCSetting, SCSettingWithoutReferences} from '../src/things/setting.js';
import {SCSportCourse, SCSportCourseWithoutReferences} from '../src/things/sport-course.js';
import {SCStudyModule, SCStudyModuleWithoutReferences} from '../src/things/study-module.js';
import {SCTicket, SCTicketWithoutReferences} from '../src/things/ticket.js';
import {SCToDo, SCToDoWithoutReferences} from '../src/things/todo.js';
import {SCTour, SCTourWithoutReferences} from '../src/things/tour.js';
import {SCVideo, SCVideoWithoutReferences} from '../src/things/video.js';
import {SCPeriodical, SCPeriodicalWithoutReferences} from '../src/things/periodical.js';
import {SCPublicationEvent, SCPublicationEventWithoutReferences} from '../src/things/publication-event.js';
import {SCThing, SCThingWithoutReferences} from '../src/index.js';
import {SCAcademicEvent, SCAcademicEventWithoutReferences} from '../src/index.js';
import {SCArticle, SCArticleWithoutReferences} from '../src/index.js';
import {SCAssessment, SCAssessmentWithoutReferences} from '../src/index.js';
import {SCBook, SCBookWithoutReferences} from '../src/index.js';
import {SCBuilding, SCBuildingWithoutReferences} from '../src/index.js';
import {SCCatalog, SCCatalogWithoutReferences} from '../src/index.js';
import {SCContactPoint, SCContactPointWithoutReferences} from '../src/index.js';
import {SCCourseOfStudy, SCCourseOfStudyWithoutReferences} from '../src/index.js';
import {SCDateSeries, SCDateSeriesWithoutReferences} from '../src/index.js';
import {SCDiff, SCDiffWithoutReferences} from '../src/index.js';
import {SCDish, SCDishWithoutReferences} from '../src/index.js';
import {SCFavorite, SCFavoriteWithoutReferences} from '../src/index.js';
import {SCFloor, SCFloorWithoutReferences} from '../src/index.js';
import {SCMessage, SCMessageWithoutReferences} from '../src/index.js';
import {SCOrganization, SCOrganizationWithoutReferences} from '../src/index.js';
import {SCPerson, SCPersonWithoutReferences} from '../src/index.js';
import {SCPointOfInterest, SCPointOfInterestWithoutReferences} from '../src/index.js';
import {SCRoom, SCRoomWithoutReferences} from '../src/index.js';
import {SCSemester, SCSemesterWithoutReferences} from '../src/index.js';
import {SCSetting, SCSettingWithoutReferences} from '../src/index.js';
import {SCSportCourse, SCSportCourseWithoutReferences} from '../src/index.js';
import {SCStudyModule, SCStudyModuleWithoutReferences} from '../src/index.js';
import {SCTicket, SCTicketWithoutReferences} from '../src/index.js';
import {SCToDo, SCToDoWithoutReferences} from '../src/index.js';
import {SCTour, SCTourWithoutReferences} from '../src/index.js';
import {SCVideo, SCVideoWithoutReferences} from '../src/index.js';
import {SCPeriodical, SCPeriodicalWithoutReferences} from '../src/index.js';
import {SCPublicationEvent, SCPublicationEventWithoutReferences} from '../src/index.js';
/**
* Check if E extends T

View File

@@ -10,34 +10,33 @@
"types": "./lib/index.d.ts",
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"test": "nyc mocha 'test/**/*.spec.ts'"
"test": "c8 mocha"
},
"dependencies": {
"@openstapps/collection-utils": "workspace:*",
"@openstapps/logger": "workspace:*",
"glob": "10.2.1",
"typescript": "4.8.4"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/chai": "4.3.4",
"@types/fs-extra": "9.0.13",
"@types/mocha": "10.0.1",
"@types/node": "18.15.3",
"c8": "7.13.0",
"chai": "4.3.7",
"mocha": "10.2.0",
"nock": "13.3.0",
"ts-node": "10.9.1",
"tsup": "6.7.0"
},
"tsup": {
"entry": [
"src/app.ts",
"src/index.ts"
],
"sourcemap": true,
@@ -54,8 +53,5 @@
"eslintIgnore": [
"resources",
"openapi"
],
"nyc": {
"extends": "@openstapps/nyc-config"
}
]
}

View File

@@ -12,7 +12,7 @@
* 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 * as ts from 'typescript';
import ts from 'typescript';
import {cleanupEmpty} from './util.js';
import {LightweightComment} from './types/lightweight-comment.js';
@@ -33,7 +33,7 @@ export function extractComment(node: ts.Node): LightweightComment | undefined {
? undefined
: cleanupEmpty({
shortSummary: comment?.[0],
description: comment?.[comment.length - 1],
description: comment?.slice(1).join('\n\n'),
tags: jsDocument?.tags?.map(tag =>
cleanupEmpty({
name: tag.tagName?.escapedText ?? 'UNRESOLVED_NAME',

View File

@@ -14,7 +14,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import ts from 'typescript';
import {cleanupEmpty, mapNotNil, rejectNil, expandPathToFilesSync} from './util.js';
import {cleanupEmpty, expandPathToFilesSync, mapNotNil, rejectNil} from './util.js';
import {
extractComment,
filterChildrenTo,
@@ -77,7 +77,7 @@ class LightweightDefinitionBuilder {
constructor(sourcePath: string | string[], readonly includeComments: boolean) {
const rootNames = Array.isArray(sourcePath)
? sourcePath
: expandPathToFilesSync(path.resolve(sourcePath), file => file.endsWith('ts'));
: expandPathToFilesSync(path.resolve(sourcePath), it => it.endsWith('.ts'));
this.program = ts.createProgram({
rootNames: rootNames,
@@ -121,7 +121,7 @@ class LightweightDefinitionBuilder {
classLike: ts.ClassDeclaration | ts.InterfaceDeclaration,
): LightweightClassDefinition {
const heritages = mapValues(
groupBy([...classLike.heritageClauses!], it => it.token.toString()),
groupBy([...(classLike.heritageClauses || [])], it => it.token.toString()),
heritages => heritages.flatMap(it => it.types),
);
@@ -162,23 +162,25 @@ class LightweightDefinitionBuilder {
collectProperties(
members: ts.NodeArray<ts.ClassElement | ts.TypeElement>,
): Record<string, LightweightProperty> {
return keyBy(
filterNodeTo(members as ts.NodeArray<ts.ClassElement | ts.TypeElement>, isProperty).map(property =>
cleanupEmpty({
comment: this.includeComments ? extractComment(property) : undefined,
name: resolvePropertyName(property.name) ?? property.getText(),
type: this.lightweightTypeAtNode(property),
properties: this.collectProperties((property.type as ts.TypeLiteralNode)?.members),
optional: ts.isPropertyDeclaration(property)
? property.questionToken === undefined
? undefined
: true
: undefined,
}),
),
it => it.name,
);
): Record<string, LightweightProperty> | undefined {
return members
? keyBy(
filterNodeTo(members as ts.NodeArray<ts.ClassElement | ts.TypeElement>, isProperty).map(property =>
cleanupEmpty({
comment: this.includeComments ? extractComment(property) : undefined,
name: resolvePropertyName(property.name) ?? property.getText(),
type: this.lightweightTypeAtNode(property),
properties: this.collectProperties((property.type as ts.TypeLiteralNode)?.members),
optional: ts.isPropertyDeclaration(property)
? property.questionToken === undefined
? undefined
: true
: undefined,
}),
),
it => it.name,
)
: undefined;
}
private lightweightTypeAtNode(node: ts.Node): LightweightType {

View File

@@ -12,14 +12,14 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from './easy-ast.js'
export * from './ast-util.js'
export * from './easy-ast.js';
export * from './ast-util.js';
export * from './types/lightweight-alias-definition.js'
export * from './types/lightweight-class-definition.js'
export * from './types/lightweight-comment.js'
export * from './types/lightweight-definition.js'
export * from './types/lightweight-definition-kind.js'
export * from './types/lightweight-project.js'
export * from './types/lightweight-property.js'
export * from './types/lightweight-type.js'
export * from './types/lightweight-alias-definition.js';
export * from './types/lightweight-class-definition.js';
export * from './types/lightweight-comment.js';
export * from './types/lightweight-definition.js';
export * from './types/lightweight-definition-kind.js';
export * from './types/lightweight-project.js';
export * from './types/lightweight-property.js';
export * from './types/lightweight-type.js';

View File

@@ -12,15 +12,36 @@
* 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 path from "path";
import {readdirSync, statSync} from "fs";
import {readdirSync, statSync} from 'fs';
import path from 'path';
/**
* Expand a path to a list of all files deeply contained in it
*/
export function expandPathToFilesSync(sourcePath: string, accept: (fileName: string) => boolean): string[] {
const fullPath = path.resolve(sourcePath);
const directory = statSync(fullPath);
return directory.isDirectory()
? readdirSync(fullPath).flatMap(fragment =>
expandPathToFilesSync(path.resolve(sourcePath, fragment), accept),
)
: [fullPath].filter(accept);
}
/**
* Take a Windows path and make a Unix path out of it
*/
export function toUnixPath(pathString: string): string {
return pathString.replaceAll(path.sep, path.posix.sep);
}
/**
* Filters only defined elements
*/
export function rejectNil<T>(array: Array<T | undefined | null>): T[] {
// eslint-disable-next-line unicorn/no-null
return array.filter(it => it == null) as T[];
return array.filter(it => it != null) as T[];
}
/**
@@ -45,17 +66,3 @@ export function cleanupEmpty<T extends object>(object: T): T {
}
return out;
}
/**
* Expand a path to a list of all files deeply contained in it
*/
export function expandPathToFilesSync(sourcePath: string, accept: (fileName: string) => boolean): string[] {
const fullPath = path.resolve(sourcePath);
const directory = statSync(fullPath);
return directory.isDirectory()
? readdirSync(fullPath).flatMap(fragment =>
expandPathToFilesSync(path.resolve(sourcePath, fragment), accept),
)
: [fullPath].filter(accept);
}

View File

@@ -12,7 +12,7 @@
* 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 {LightweightFile} from '../../src/easy-ast/types/lightweight-project.js';
import {LightweightFile} from '../src/index.js';
export interface EasyAstSpecType {
testName: string;

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 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 {lightweightProjectFromPath} from '../src/index.js';
import {expect} from 'chai';
import {expandPathToFilesSync, toUnixPath} from '../src/util.js';
import type {EasyAstSpecType} from './easy-ast-spec-type.js';
const projectPath = './test/project';
const tests = await Promise.all(
expandPathToFilesSync(projectPath, file => file.endsWith('ast-test.ts')).map(async it => ({
path: toUnixPath(it),
config: await import(`file://${it}`).then(it => it.testConfig as EasyAstSpecType),
})),
);
describe('Easy AST', async function () {
it('should build the project', function () {
const project = lightweightProjectFromPath(projectPath, true);
expect(Object.keys(project).length).to.equal(tests.length);
});
const project = lightweightProjectFromPath(projectPath, true);
for (const {path, config} of tests) {
it(config.testName, function () {
const projectAtPath = project[path];
expect(projectAtPath).not.to.be.undefined;
for (const key in projectAtPath) {
if (key.startsWith('$')) delete projectAtPath[key];
}
expect(projectAtPath).to.be.deep.equal(config.expected);
});
}
});

View File

@@ -1,35 +0,0 @@
/*
* Copyright (C) 2021 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 {expandPathToFilesSync, toUnixPath} from '../src/util.js';
import {EasyAstSpecType} from './easy-ast-spec-type.js';
import {lightweightProjectFromPath} from '../src/easy-ast.js';
import {expect} from 'chai';
describe('Easy AST', async () => {
const project = lightweightProjectFromPath('./test/easy-ast', true);
for (const file of expandPathToFilesSync('./test/easy-ast', file => file.endsWith('ast-test.ts'))) {
try {
const test = (await import(file))['testConfig'] as EasyAstSpecType;
it(test.testName, () => {
expect(omitBy(project[toUnixPath(file)], (_value, key) => key.startsWith('$'))).to.be.deep.equal(
test.expected,
);
});
} catch (error) {
console.error(error);
}
}
});

View File

@@ -13,11 +13,13 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused type
type TestTypeAlias = number | string;
// @ts-expect-error unused type
enum TestEnum {
Foo,
Bar,

View File

@@ -13,14 +13,18 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
interface Random {}
// @ts-expect-error unused type
type TestArrayGeneric = Array<string>;
// @ts-expect-error unused type
type TestArrayLiteral = number[];
// @ts-expect-error unused type
type TestArrayReferenceGeneric = Array<Random>;
// @ts-expect-error unused type
type TestArrayReferenceLiteral = Random[];
export const testConfig: EasyAstSpecType = {

View File

@@ -13,13 +13,15 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused type
interface TestInterface {
foo: number;
}
// @ts-expect-error unused type
class TestClass {
bar: string = 'test';
}

View File

@@ -13,8 +13,8 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
/**
* Class comment
@@ -25,6 +25,7 @@ import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-de
*
* @classTag classParameter1 classParameter2
*/
// @ts-expect-error unused type
interface TestInterface {
/**
* Property comment
@@ -47,6 +48,7 @@ interface TestInterface {
*
* @classTag classParameter1 classParameter2
*/
// @ts-expect-error unused type
class TestClass {
/**
* Property comment
@@ -69,6 +71,7 @@ class TestClass {
*
* @enumTag enumParameter1
*/
// @ts-expect-error unused type
enum TestAlias {}
export const testConfig: EasyAstSpecType = {

View File

@@ -13,13 +13,14 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
interface Test1<T = number> {
foo: T;
}
// @ts-expect-error unused type
interface Test2 {
bar: Test1;
}

View File

@@ -13,14 +13,16 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused type
enum TestAuto {
Foo,
Bar,
}
// @ts-expect-error unused type
enum TestSpecified {
YES = 'yes',
NO = 'no',

View File

@@ -13,11 +13,12 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
interface $Random {}
// @ts-expect-error unused type
interface Generics {
baz: Foo<number, $Random>;
}

View File

@@ -13,15 +13,17 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
interface $Random {}
// @ts-expect-error unused
interface IndexSignatureObject {
[key: string]: $Random;
}
// @ts-expect-error unused
interface IndexSignaturePrimitive {
[key: string]: number;
}

View File

@@ -13,8 +13,8 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
interface $BaseInterface<T> {
foo: T;
@@ -26,6 +26,7 @@ interface $BaseInterface2 {
class $BaseClass {}
// @ts-expect-error unused
class InheritingClass extends $BaseClass implements $BaseInterface<number>, $BaseInterface2 {
bar: string = '';

View File

@@ -13,9 +13,10 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused
interface NestedObject {
nested: {
deeplyNested: {

View File

@@ -13,9 +13,10 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused
interface Test {
number_type: number;
string_type: string;

View File

@@ -13,9 +13,10 @@
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused
interface Foo<T extends Bar<string>> {
bar: T;
}

View File

@@ -13,11 +13,11 @@
"author": "Thea Schöbl <dev@theaninova.de>",
"scripts": {
"build": "rimraf lib && tsc",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint -c .eslintrc --ignore-path .eslintignore --ext .ts src/ test/",
"lint:fix": "eslint --fix -c .eslintrc --ignore-path .eslintignore --ext .ts src/ test/",
"test": "mocha --require ts-node/register test/*.spec.ts"
"test": "c8 mocha"
},
"dependencies": {
"@elastic/elasticsearch": "8.4.0",
@@ -38,9 +38,10 @@
"@types/rimraf": "3.0.2",
"chai": "4.3.7",
"mocha": "10.2.0",
"c8": "7.13.0",
"nock": "13.3.0",
"rimraf": "5.0.0",
"ts-node": "10.9.1",
"rimraf": "5.0.0"
},
"prettier": "@openstapps/prettier-config"
}

View File

@@ -17,9 +17,9 @@ import {mkdirSync, readFileSync, writeFileSync} from 'fs';
import got from 'got';
import path from 'path';
import {exit} from 'process';
import {generateTemplate} from './mapping.js';
import {getProjectReflection} from './project-reflection.js';
import {ElasticsearchTemplateCollection} from './types/mapping.js';
import {generateTemplate} from './mapping';
import {getProjectReflection} from './project-reflection';
import {ElasticsearchTemplateCollection} from './types/mapping';
// handle unhandled promise rejections
process.on('unhandledRejection', async (reason: unknown) => {

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {MappingFloatNumberProperty} from '@elastic/elasticsearch/lib/api/types';
import {ElasticsearchTypemap} from '../types/mapping.js';
import {ElasticsearchTypemap} from '../types/mapping';
export const PARSE_ERROR = 'PARSE_ERROR' as MappingFloatNumberProperty['type'];
export const MISSING_PREMAP = 'MISSING_PREMAP' as MappingFloatNumberProperty['type'];

View File

@@ -1,6 +1,10 @@
export * from './mapping'
export * from './project-reflection'
export * from './config/premap'
export * from './config/fieldmap'
export * from './config/settings'
export * from './config/typemap'
export * from './mapping';
export * from './project-reflection';
export * from './config/premap';
export * from './config/fieldmap';
export * from './config/settings';
export * from './config/typemap';
export * from './types/mapping';
export * from './types/aggregation';

View File

@@ -33,13 +33,13 @@ import {
TypeParameterType,
UnionType,
} from 'typedoc/dist/lib/models';
import {fieldmap, filterableMap, filterableTagName} from './config/fieldmap.js';
import {premaps} from './config/premap.js';
import {settings} from './config/settings.js';
import {dynamicTypes, isTagType, MISSING_PREMAP, PARSE_ERROR, TYPE_CONFLICT, typemap} from './config/typemap.js';
import {AggregationSchema, ESNestedAggregation} from './types/aggregation.js';
import {fieldmap, filterableMap, filterableTagName} from './config/fieldmap';
import {premaps} from './config/premap';
import {settings} from './config/settings';
import {dynamicTypes, isTagType, MISSING_PREMAP, PARSE_ERROR, TYPE_CONFLICT, typemap} from './config/typemap';
import {AggregationSchema, ESNestedAggregation} from './types/aggregation';
import {ElasticsearchTemplateCollection,
MappingGenTemplate} from './types/mapping.js';
MappingGenTemplate} from './types/mapping';
import * as console from 'console';
let dynamicTemplates: Record<string, MappingDynamicTemplate>[] = [];

View File

@@ -12,7 +12,6 @@
* 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 {readdirSync, statSync} from 'fs';
import path from 'path';
import {MapAggTest} from './mapping-model/map-agg-test.js';
@@ -48,8 +47,8 @@ describe('ES Aggregation Gen', async () => {
it(test.testName, function () {
magAppInstance.testInterfaceAgainstPath(test);
});
} catch (error: any) {
await Logger.error('UNHANDLED REJECTION', error.stack);
} catch (error) {
console.error('UNHANDLED REJECTION', (error as any).stack);
process.exit(1);
}
}

View File

@@ -12,7 +12,6 @@
* 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 {ThingType} from './types.js';
import {MapAggTestOptions} from '../../map-agg-test-options.js';

View File

@@ -54,7 +54,7 @@ export const testConfig: MapAggTestOptions = {
dynamic: 'strict',
properties: {
baz: {
type: 'float',
type: ElasticsearchDataType.float,
},
},
},

View File

@@ -12,7 +12,6 @@
* 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 {readdirSync, statSync} from 'fs';
import path from 'path';
import {MapAggTest} from './mapping-model/map-agg-test.js';
@@ -49,8 +48,8 @@ describe('ES Mapping Gen', async () => {
it(test.testName, function () {
magAppInstance.testInterfaceAgainstPath(test);
});
} catch (error: any) {
await Logger.error('UNHANDLED REJECTION', error.stack);
} catch (error) {
console.error('UNHANDLED REJECTION', (error as any).stack);
process.exit(1);
}
}

View File

@@ -16,8 +16,8 @@
},
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/"
},

View File

@@ -35,7 +35,7 @@ import {
} from './types.js';
import got from 'got';
export * from './types.js'
export * from './types.js';
/**
* Sleep for a number of milliseconds

View File

@@ -1,32 +0,0 @@
## Summary
(Summarize the bug encountered concisely)
## Steps to reproduce
(How one can reproduce the issue - this is very important)
## Example Project
(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
## What is the current bug behavior?
(What actually happens)
## What is the expected correct behavior?
(What you should see instead)
## Relevant logs and/or screenshots
(Paste any relevant logs - please use code blocks (```) to format console output,
logs, and code as it's very hard to read otherwise.)
## Possible fixes
(If you can, link to the line of code that might be responsible for the problem)
/label ~meeting

View File

@@ -15,25 +15,22 @@
"types": "./lib/index.d.ts",
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"format": "prettier . --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"test": "nyc mocha 'test/**/*.spec.ts'"
"test": "c8 mocha"
},
"dependencies": {
"@types/nodemailer": "6.4.7",
"chalk": "4.1.2",
"chalk": "5.2.0",
"flatted": "3.2.7",
"moment": "2.29.4",
"nodemailer": "6.9.1"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/chai": "4.3.4",
"@types/chai-as-promised": "7.1.5",
"@types/chai-spies": "1.0.3",
@@ -43,7 +40,7 @@
"chai-as-promised": "7.1.1",
"chai-spies": "1.0.0",
"mocha": "10.2.0",
"nyc": "15.1.0",
"c8": "7.13.0",
"ts-node": "10.9.1",
"tsup": "6.7.0",
"typedoc": "0.23.28",
@@ -63,8 +60,5 @@
"extends": [
"@openstapps"
]
},
"nyc": {
"extends": "@openstapps/nyc-config"
}
}

View File

@@ -1,9 +1,9 @@
export * from './logger.js'
export * from './common.js'
export * from './smtp.js'
export * from './transformation.js'
export * from './transport.js'
export * from './logger.js';
export * from './common.js';
export * from './smtp.js';
export * from './transformation.js';
export * from './transport.js';
export * from './transformations/add-log-level.js'
export * from './transformations/colorize.js'
export * from './transformations/timestamp.js'
export * from './transformations/add-log-level.js';
export * from './transformations/colorize.js';
export * from './transformations/timestamp.js';

View File

@@ -13,6 +13,8 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import chalk from 'chalk';
// eslint-disable-next-line unicorn/import-style
import type {ChalkInstance} from 'chalk';
import {LogLevel} from '../logger.js';
import {Transformation} from '../transformation.js';
@@ -31,7 +33,8 @@ export class Colorize implements Transformation {
* @param logLevelToColor Map from log level to color transformation to apply
*/
constructor(
private readonly logLevelToColor: {[k in LogLevel]: chalk.Chalk} = {
// not entirely sure why we can't just use the functions directly here
private readonly logLevelToColor: {[k in LogLevel]: ChalkInstance} = {
ERROR: chalk.bold.red,
INFO: chalk.cyan,
LOG: chalk.white,

View File

@@ -13,18 +13,15 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {suite, test} from '@testdeck/mocha';
import {
deleteUndefinedProperties,
isNodeEnvironment,
isProductiveEnvironment,
isProductiveNodeEnvironment,
} from '../src/common.js';
} from '../src/index.js';
@suite()
export class CommonSpec {
@test
deleteUndefinedProperties1() {
describe('common', function () {
it('should should delete undefined properties (1)', function () {
expect(
deleteUndefinedProperties({
a: 2,
@@ -39,20 +36,18 @@ export class CommonSpec {
c: 3,
},
});
}
});
@test
deleteUndefinedProperties2() {
it('should delete undefined properties (2)', function () {
expect(
deleteUndefinedProperties({
a: undefined,
b: undefined,
}),
).to.deep.equal({});
}
});
@test
deleteUndefinedProperties3() {
it('should delete undefined properties (3)', function () {
expect(
deleteUndefinedProperties({
a: 2,
@@ -64,49 +59,41 @@ export class CommonSpec {
b: 'foo',
c: 'bar',
});
}
});
@test
isNodeEnvironment() {
it('should detect node environment', function () {
expect(isNodeEnvironment()).to.be.equal(true);
const savedProcess = process;
// @ts-ignore
// @ts-expect-error override this
process = undefined;
expect(isNodeEnvironment()).to.be.equal(false);
process = savedProcess;
}
});
@test
isProductiveEnvironment() {
const nodeEnv = process.env.NODE_ENV;
it('should detect production environment', function () {
const nodeEnvironment = process.env.NODE_ENV;
process.env.NODE_ENV = '';
expect(isProductiveEnvironment()).to.be.equal(false);
process.env.NODE_ENV = 'production';
expect(isProductiveEnvironment()).to.be.equal(true);
process.env.NODE_ENV = nodeEnv;
}
process.env.NODE_ENV = nodeEnvironment;
});
@test
isProductiveNodeEnvironment() {
const nodeEnv = process.env.NODE_ENV;
it('should detect node production environment', function () {
const nodeEnvironment = process.env.NODE_ENV;
process.env.NODE_ENV = '';
expect(isProductiveNodeEnvironment()).to.be.equal(false);
process.env.NODE_ENV = 'production';
expect(isProductiveNodeEnvironment()).to.be.equal(true);
process.env.NODE_ENV = nodeEnv;
}
}
process.env.NODE_ENV = nodeEnvironment;
});
});

View File

@@ -1,4 +1,4 @@
import {Transport, VerifiableTransport} from '../src/transport.js';
import {Transport, VerifiableTransport} from '../src/index.js';
export class DummyTransport extends Transport {
send(subject: string, message: string): Promise<string> {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
* Copyright (C) 2018, 2020 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -16,41 +17,35 @@ import chai from 'chai';
import {expect} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import {suite, test} from '@testdeck/mocha';
import {Logger} from '../src/logger.js';
import {AddLogLevel} from '../src/transformations/add-log-level.js';
import {Colorize} from '../src/transformations/colorize.js';
import {DummyTransport} from './dummyTransport.js';
import {Logger, AddLogLevel, Colorize} from '../src/index.js';
import {DummyTransport} from './dummy-transport.js';
import path from 'node:path';
import chalk from 'chalk';
chai.should();
chai.use(chaiSpies);
chai.use(chaiAsPromised);
@suite()
export class LoggerSpec {
static 'sandbox': ChaiSpies.Sandbox;
chalk.level = 2;
static 'before'() {
LoggerSpec.sandbox = chai.spy.sandbox();
}
describe('Logger', function () {
const sandbox = chai.spy.sandbox();
'before'() {
beforeEach(function () {
Logger.setTransformations([new AddLogLevel()]);
}
});
'after'() {
LoggerSpec.sandbox.restore();
}
afterEach(function () {
sandbox.restore();
});
@test
async 'default log level'() {
it('should read default log level', async function () {
expect((Logger as any).getLevel('LOG')).to.be.equal(31);
expect((Logger as any).getLevel('EXIT')).to.be.equal(0);
}
});
@test
async 'error'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
it('should error', async function () {
const spy = sandbox.on(console, 'error', () => {
// noop
});
@@ -59,15 +54,14 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[ERROR]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
});
@test
async 'error in productive environment'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
it('should error in productive environment', async function () {
const spy = sandbox.on(console, 'error', () => {
// noop
});
const nodeEnv = process.env.NODE_ENV;
const nodeEnvironment = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
await Logger.error('Foobar').should.be.rejectedWith(Error);
@@ -87,12 +81,11 @@ export class LoggerSpec {
Logger.setTransport();
process.env.NODE_ENV = nodeEnv;
}
process.env.NODE_ENV = nodeEnvironment;
});
@test
async 'error without output'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
it('should error without output', async function () {
const spy = sandbox.on(console, 'error', () => {
// noop
});
@@ -103,24 +96,24 @@ export class LoggerSpec {
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called();
}
});
@test
async 'error with Error'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
it('should error with Error', async function () {
const spy = sandbox.on(console, 'error', () => {
// noop
});
// eslint-disable-next-line unicorn/error-message
await Logger.error(new Error());
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('Error');
expect(spy.__spy.calls[0][0]).to.contain(process.cwd());
}
const directory = process.cwd().replaceAll(path.sep, path.posix.sep);
expect(spy.__spy.calls[0][0]).to.contain(directory);
});
@test
'info'() {
const spy = LoggerSpec.sandbox.on(console, 'info', () => {
it('should info', function () {
const spy = sandbox.on(console, 'info', () => {
// noop
});
@@ -129,23 +122,22 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[INFO]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
});
@test
async 'exits'() {
const infoSpy = LoggerSpec.sandbox.on(console, 'info', () => {
it('should exit', async function () {
const infoSpy = sandbox.on(console, 'info', () => {
// noop
});
const logSpy = LoggerSpec.sandbox.on(console, 'log', () => {
const logSpy = sandbox.on(console, 'log', () => {
// noop
});
const warnSpy = LoggerSpec.sandbox.on(console, 'warn', () => {
const warnSpy = sandbox.on(console, 'warn', () => {
// noop
});
const errorSpy = LoggerSpec.sandbox.on(console, 'error', () => {
const errorSpy = sandbox.on(console, 'error', () => {
// noop
});
const processSpy = LoggerSpec.sandbox.on(process, 'exit', () => {
const processSpy = sandbox.on(process, 'exit', () => {
// noop
});
@@ -173,11 +165,10 @@ export class LoggerSpec {
expect(processSpy).to.have.been.called.exactly(5);
process.env.STAPPS_EXIT_LEVEL = exitLevel;
}
});
@test
'info without output'() {
const spy = LoggerSpec.sandbox.on(console, 'info', () => {
it('should info without output', function () {
const spy = sandbox.on(console, 'info', () => {
// noop
});
@@ -188,10 +179,9 @@ export class LoggerSpec {
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called;
}
});
@test
'initialized'() {
it('should be initialized', function () {
Logger.setTransport(new DummyTransport());
expect(() => {
@@ -199,11 +189,10 @@ export class LoggerSpec {
}).not.to.throw();
Logger.setTransport();
}
});
@test
'initialized in productive environment'() {
const nodeEnv = process.env.NODE_ENV;
it('should be initialized in productive environment', function () {
const nodeEnvironment = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
Logger.setTransport(new DummyTransport());
@@ -218,7 +207,7 @@ export class LoggerSpec {
Logger.initialized();
}).to.throw();
const spy = LoggerSpec.sandbox.on(console, 'warn', () => {
const spy = sandbox.on(console, 'warn', () => {
// noop
});
@@ -232,14 +221,13 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
process.env.NODE_ENV = nodeEnv;
}
process.env.NODE_ENV = nodeEnvironment;
});
@test
'is compatible with log aggregation in productive environment'() {
it('should be compatible with log aggregation in productive environment', function () {
Logger.setTransformations([new AddLogLevel(), new Colorize()]);
let spy = LoggerSpec.sandbox.on(console, 'log', () => {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -248,7 +236,7 @@ export class LoggerSpec {
expect(spy).to.have.been.called.once;
expect(spy.__spy.calls[0][0]).to.equal('\u001B[37m[LOG] Foo\u001B[39m\n\u001B[37mbar\u001B[39m');
const nodeEnv = process.env.NODE_ENV;
const nodeEnvironment = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
process.env.ALLOW_NO_TRANSPORT = 'true';
@@ -259,13 +247,12 @@ export class LoggerSpec {
expect(spy).to.have.been.called.twice;
expect(spy.__spy.calls[1][0]).to.equal('[LOG] Foo bar');
process.env.NODE_ENV = nodeEnv;
process.env.NODE_ENV = nodeEnvironment;
delete process.env.ALLOW_NO_TRANSPORT;
}
});
@test
'log'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
it('should log', function () {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -274,11 +261,10 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[LOG]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
});
@test
'log without output'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
it('should log without output', function () {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -289,11 +275,10 @@ export class LoggerSpec {
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).to.not.have.been.called();
}
});
@test
'ok'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
it('should ok', function () {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -302,11 +287,10 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[OK]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
});
@test
'ok without output'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
it('should ok without output', function () {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -317,19 +301,17 @@ export class LoggerSpec {
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called();
}
});
@test
'setTransport'() {
it('should set transport', function () {
expect(() => {
Logger.setTransport(new DummyTransport());
Logger.setTransport();
}).not.to.throw();
}
});
@test
'stringify'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
it('should stringify', function () {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -338,11 +320,10 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('foo');
expect(spy.__spy.calls[0][0]).to.contain('bar');
}
});
@test
'stringify object'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
it('should stringify object', function () {
const spy = sandbox.on(console, 'log', () => {
// noop
});
@@ -353,11 +334,10 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('foo');
expect(spy.__spy.calls[0][0]).to.contain('bar');
}
});
@test
'warn'() {
const spy = LoggerSpec.sandbox.on(console, 'warn', () => {
it('should warn', function () {
const spy = sandbox.on(console, 'warn', () => {
// noop
});
@@ -366,11 +346,10 @@ export class LoggerSpec {
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[WARN]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
});
@test
'warn without output'() {
const spy = LoggerSpec.sandbox.on(console, 'warn', () => {
it('should warn without output', function () {
const spy = sandbox.on(console, 'warn', () => {
// noop
});
@@ -381,17 +360,16 @@ export class LoggerSpec {
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called();
}
});
@test
'log level exclusiveness'() {
const warnSpy = LoggerSpec.sandbox.on(console, 'warn', () => {
it('should log level exclusiveness', function () {
const warnSpy = sandbox.on(console, 'warn', () => {
// noop WARN
});
const infoSpy = LoggerSpec.sandbox.on(console, 'info', () => {
const infoSpy = sandbox.on(console, 'info', () => {
// noop INFO
});
const okSpy = LoggerSpec.sandbox.on(console, 'log', () => {
const okSpy = sandbox.on(console, 'log', () => {
// noop OK
});
@@ -413,20 +391,19 @@ export class LoggerSpec {
expect(okSpy).to.not.have.been.called();
delete process.env.STAPPS_LOG_LEVEL;
}
});
@test
'getExitLevel'() {
it('should getExitLevel', function () {
const savedProcess = process;
// @ts-ignore
// @ts-expect-error ignore
process = undefined;
(global as any).window = {
STAPPS_EXIT_LEVEL: 0,
};
const stub = LoggerSpec.sandbox.on(console, 'info', () => {
const stub = sandbox.on(console, 'info', () => {
// noop
});
@@ -437,20 +414,19 @@ export class LoggerSpec {
delete (global as any).window;
expect(stub).not.to.have.been.called();
}
});
@test
'getLogLevel'() {
it('should getLogLevel', function () {
const savedProcess = process;
// @ts-ignore
// @ts-expect-error ignore
process = undefined;
(global as any).window = {
STAPPS_LOG_LEVEL: 0,
};
const stub = LoggerSpec.sandbox.on(console, 'info', () => {
const stub = sandbox.on(console, 'info', () => {
// noop
});
@@ -461,20 +437,19 @@ export class LoggerSpec {
delete (global as any).window;
expect(stub).not.to.have.been.called();
}
});
@test
'output without transformations'() {
it('should output without transformations', function () {
Logger.setTransformations([]);
const stub = LoggerSpec.sandbox.on(console, 'log', () => {
const stub = sandbox.on(console, 'log', () => {
// noop
});
const applyTransformationsSpy = LoggerSpec.sandbox.on(new Logger(), 'applyTransformations');
const applyTransformationsSpy = sandbox.on(new Logger(), 'applyTransformations');
Logger.log('Foobar');
expect(stub).to.have.been.called.with('Foobar');
expect(applyTransformationsSpy).not.to.have.been.called;
}
}
});
});

View File

@@ -13,96 +13,42 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {suite, test} from '@testdeck/mocha';
import {SMTP} from '../src/smtp.js';
import {SMTP} from '../src/index.js';
@suite()
export class SMTPSpec {
/* tslint:disable:member-ordering */
@test
mailValidation1() {
expect(SMTP.isValidEmailAddress('stordeur@campus.tu-berlin.de')).to.be.true;
}
const validEmails = [
'stordeur@campus.tu-berlin.de',
'foo@bar.com',
'test@test.cz',
'info@beispiel.to',
'stördeur@campus.tu-berlin.de',
'stordeur@campus.tu-berlin.de+a',
];
@test
mailValidation2() {
expect(SMTP.isValidEmailAddress('foo@bar.com')).to.be.true;
}
const invalidEmails = [
'stordeurcampus.tu-berlin.de',
'@campus.tu-berlin.de',
'',
'@',
' stordeur@campus.tu-berlin.de',
'stord+eur@campus.tu-berlin.de ',
'stordeur@campus..tu-berlin.de',
'stordeur@campus',
];
@test
mailValidation3() {
expect(SMTP.isValidEmailAddress('test@test.cz')).to.be.true;
}
describe('isValidEmailAddress', function () {
describe('valid emails', function () {
for (const email of validEmails) {
it(`should detect "${email}" as valid`, function () {
expect(SMTP.isValidEmailAddress(email)).to.be.true;
});
}
});
@test
mailValidation4() {
expect(SMTP.isValidEmailAddress('info@beispiel.to')).to.be.true;
}
@test
mailValidation5() {
expect(SMTP.isValidEmailAddress('stördeur@campus.tu-berlin.de')).to.be.true;
}
@test
mailValidation6() {
expect(SMTP.isValidEmailAddress('stordeur@campus.tu-berlin.de+a')).to.be.true;
}
@test
mailValidation7() {
expect(SMTP.isValidEmailAddress('stordeurcampus.tu-berlin.de')).to.be.false;
}
@test
mailValidation8() {
expect(SMTP.isValidEmailAddress('@campus.tu-berlin.de')).to.be.false;
}
@test
mailValidation9() {
expect(SMTP.isValidEmailAddress('')).to.be.false;
}
@test
mailValidation10() {
expect(SMTP.isValidEmailAddress('@')).to.be.false;
}
@test
mailValidation11() {
expect(SMTP.isValidEmailAddress('@')).to.be.false;
}
@test
mailValidation12() {
expect(SMTP.isValidEmailAddress(' stordeur@campus.tu-berlin.de')).to.be.false;
}
@test
mailValidation13() {
expect(SMTP.isValidEmailAddress('stordeur@campus.tu-berlin.de ')).to.be.false;
}
@test
mailValidation14() {
expect(SMTP.isValidEmailAddress('stord+eur@campus.tu-berlin.de ')).to.be.false;
}
@test
mailValidation15() {
expect(SMTP.isValidEmailAddress('anselm..stordeur@campus.tu-berlin.de')).to.be.false;
}
@test
mailValidation16() {
expect(SMTP.isValidEmailAddress('stordeur@campus..tu-berlin.de')).to.be.false;
}
@test
mailValidation17() {
expect(SMTP.isValidEmailAddress('stordeur@campus')).to.be.false;
}
/* tslint:enable:member-ordering */
}
describe('invalid emails', function () {
for (const email of invalidEmails) {
it(`should detect "${email}" as invalid`, function () {
expect(SMTP.isValidEmailAddress(email)).to.be.false;
});
}
});
});

View File

@@ -13,15 +13,12 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {suite, test} from '@testdeck/mocha';
import {AddLogLevel} from '../../src/transformations/add-log-level.js';
import {AddLogLevel} from '../../src/index.js';
@suite()
export class AddLogLevelSpec {
@test
transform() {
describe('add log level', function () {
it('should transform', function () {
const transformation = new AddLogLevel();
expect(transformation.transform('ERROR', 'Foobar')).to.be.equal('[ERROR] Foobar');
}
}
});
});

View File

@@ -13,18 +13,18 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {suite, test} from '@testdeck/mocha';
import {Colorize} from '../../src/transformations/colorize.js';
import {Colorize} from '../../src/index.js';
import chalk from 'chalk';
@suite()
export class ColorizeSpec {
@test
transform() {
chalk.level = 2;
describe('colorize', function () {
it('should transform', function () {
const transformation = new Colorize();
expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(
'\u001b[1m\u001b[31mFoobar\u001b[39m\u001b[22m',
'\u001B[1m\u001B[31mFoobar\u001B[39m\u001B[22m',
);
expect(transformation.transform('LOG', 'Foobar')).to.be.equal('\u001b[37mFoobar\u001b[39m');
}
}
expect(transformation.transform('LOG', 'Foobar')).to.be.equal('\u001B[37mFoobar\u001B[39m');
});
});

Some files were not shown because too many files have changed in this diff Show More