mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
feat: biome lint (#4853)
This commit changes our linter/formatter to biome (https://biomejs.dev/) Causing our prehook to run almost instantly, and our "yarn lint" task to run in sub 100ms. Some trade-offs: * Biome isn't quite as well established as ESLint * Are we ready to install a different vscode plugin (the biome plugin) instead of the prettier plugin The configuration set for biome also has a set of recommended rules, this is turned on by default, in order to get to something that was mergeable I have turned off a couple the rules we seemed to violate the most, that we also explicitly told eslint to ignore.
This commit is contained in:
parent
fbc571dffc
commit
6673d131fe
@ -1,15 +0,0 @@
|
||||
node_modules
|
||||
docker
|
||||
bundle.js
|
||||
website/blog
|
||||
website/build
|
||||
website/core
|
||||
website/docs
|
||||
website/node_modules
|
||||
website/i18n/*.js
|
||||
website/pages
|
||||
website/translated_docs
|
||||
website
|
||||
setupJest.js
|
||||
frontend
|
||||
dist
|
59
.eslintrc
59
.eslintrc
@ -1,59 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": ["airbnb-typescript/base", "plugin:prettier/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2019,
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "prettier", "import", "no-only-tests", "regexp"],
|
||||
"root": true,
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"@typescript-eslint/naming-convention": 0,
|
||||
"@typescript-eslint/space-before-function-paren": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"class-methods-use-this": [0],
|
||||
"prettier/prettier": ["error"],
|
||||
"func-names": "off",
|
||||
"strict": [0, "global"],
|
||||
"no-underscore-dangle": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-param-reassign": "error",
|
||||
"no-return-await": "error",
|
||||
"max-nested-callbacks": "off",
|
||||
"no-only-tests/no-only-tests": "error",
|
||||
"new-cap": [
|
||||
"error",
|
||||
{
|
||||
"capIsNewExceptions": ["Router", "Mitm"]
|
||||
}
|
||||
],
|
||||
"regexp/no-super-linear-backtracking": "error",
|
||||
"regexp/strict": "warn",
|
||||
"regexp/no-useless-escape": "warn",
|
||||
"prefer-regex-literals": "warn"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
// enable the rule specifically for TypeScript files
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-module-boundary-types": ["error"],
|
||||
"@typescript-eslint/naming-convention": ["error"],
|
||||
"@typescript-eslint/space-before-function-paren": ["error"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/test/e2e/helpers/test-helper.ts"],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignorePatterns": ["**/docs/api/oas/", "examples/**", "scripts/**"]
|
||||
}
|
96
biome.json
Normal file
96
biome.json
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.2.2/schema.json",
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"performance": {
|
||||
"noDelete": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnsafeOptionalChaining": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noBannedTypes": "off",
|
||||
"noUselessConstructor": "off",
|
||||
"useOptionalChain": "warn",
|
||||
"noStaticOnlyClass": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off",
|
||||
"noInferrableTypes": "off",
|
||||
"noUnusedTemplateLiteral": "off",
|
||||
"useSingleVarDeclarator": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noExtraNonNullAssertion": "off",
|
||||
"noRedeclare": "off"
|
||||
}
|
||||
},
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"docker",
|
||||
"bundle.js",
|
||||
"website/blog",
|
||||
"website/build",
|
||||
"website/core",
|
||||
"website/docs",
|
||||
"website/i18n/*.js",
|
||||
"website/pages",
|
||||
"website/translated_docs",
|
||||
"website",
|
||||
"setupJest.js",
|
||||
"frontend",
|
||||
"dist",
|
||||
"src/migrations/*.js",
|
||||
"src/test/examples/*.json",
|
||||
"website/**/*.js",
|
||||
"coverage"
|
||||
]
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": false
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"docker",
|
||||
"bundle.js",
|
||||
"website/blog",
|
||||
"website/build",
|
||||
"website/core",
|
||||
"website/docs",
|
||||
"website/i18n/*.js",
|
||||
"website/pages",
|
||||
"website/translated_docs",
|
||||
"website",
|
||||
"setupJest.js",
|
||||
"frontend",
|
||||
"dist",
|
||||
"src/migrations/*.js",
|
||||
"src/migrations/*.json",
|
||||
"src/test/examples/*.json",
|
||||
"website/**/*.js",
|
||||
"coverage",
|
||||
".eslintrc",
|
||||
".eslintignore",
|
||||
"package.json"
|
||||
],
|
||||
"indentSize": 4
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"semicolons": "always",
|
||||
"quoteStyle": "single",
|
||||
"jsxQuoteStyle": "single",
|
||||
"indentSize": 4
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"indentSize": 2
|
||||
}
|
||||
}
|
||||
}
|
32
package.json
32
package.json
@ -44,7 +44,7 @@
|
||||
"prestart:dev": "yarn run clean",
|
||||
"start:dev": "TZ=UTC NODE_ENV=development tsc-watch --strictNullChecks false --onSuccess \"node dist/server-dev.js\"",
|
||||
"db-migrate": "db-migrate --migrations-dir ./src/migrations",
|
||||
"lint": "eslint ./src",
|
||||
"lint": "biome check src",
|
||||
"local:package": "del-cli --force build && mkdir build && cp -r dist docs CHANGELOG.md LICENSE README.md package.json build",
|
||||
"prebuild:watch": "yarn run clean",
|
||||
"build:watch": "tsc -w --strictNullChecks false",
|
||||
@ -168,6 +168,7 @@
|
||||
"@apidevtools/swagger-parser": "10.1.0",
|
||||
"@babel/core": "7.22.17",
|
||||
"@swc/core": "1.3.88",
|
||||
"@biomejs/biome": "1.2.2",
|
||||
"@swc/jest": "0.2.29",
|
||||
"@types/bcryptjs": "2.4.3",
|
||||
"@types/cors": "2.8.14",
|
||||
@ -190,20 +191,10 @@
|
||||
"@types/supertest": "2.0.12",
|
||||
"@types/type-is": "1.6.4",
|
||||
"@types/uuid": "9.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"copyfiles": "2.4.1",
|
||||
"coveralls": "3.1.1",
|
||||
"del-cli": "5.1.0",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.1.0",
|
||||
"eslint-config-prettier": "8.10.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-regexp": "^1.14.0",
|
||||
"faker": "5.5.3",
|
||||
"fast-check": "3.13.0",
|
||||
"fetch-mock": "9.11.0",
|
||||
@ -213,7 +204,6 @@
|
||||
"lint-staged": "13.2.3",
|
||||
"nock": "13.3.3",
|
||||
"openapi-enforcer": "1.22.3",
|
||||
"prettier": "2.8.1",
|
||||
"proxyquire": "2.1.3",
|
||||
"source-map-support": "0.5.21",
|
||||
"superagent": "8.1.2",
|
||||
@ -241,24 +231,10 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts}": [
|
||||
"eslint --fix"
|
||||
"biome check --apply"
|
||||
],
|
||||
"*.{json,yaml,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"proseWrap": "never",
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.{json,yaml,yml,md}",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
"biome format --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ function isPrerelease(version) {
|
||||
return arr && arr.length > 0;
|
||||
}
|
||||
|
||||
if(isPrerelease(version)){
|
||||
console.log('beta')
|
||||
}else if(semver.gt(version, latestUnleashVersion)) {
|
||||
if (isPrerelease(version)) {
|
||||
console.log('beta');
|
||||
} else if (semver.gt(version, latestUnleashVersion)) {
|
||||
console.log('latest');
|
||||
} else {
|
||||
console.log('previous');
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ test('Should call datadog webhook for archived toggle with project info', async
|
||||
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(`Should call datadog webhook for toggled environment`, async () => {
|
||||
test('Should call datadog webhook for toggled environment', async () => {
|
||||
const addon = new DatadogAddon({
|
||||
getLogger: noLogger,
|
||||
unleashUrl: 'http://some-url.com',
|
||||
@ -169,7 +169,7 @@ test(`Should call datadog webhook for toggled environment`, async () => {
|
||||
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(`Should include customHeaders in headers when calling service`, async () => {
|
||||
test('Should include customHeaders in headers when calling service', async () => {
|
||||
const addon = new DatadogAddon({
|
||||
getLogger: noLogger,
|
||||
unleashUrl: 'http://some-url.com',
|
||||
@ -205,7 +205,7 @@ test(`Should include customHeaders in headers when calling service`, async () =>
|
||||
expect(fetchRetryCalls[0].options.headers).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(`Should not include source_type_name when included in the config`, async () => {
|
||||
test('Should not include source_type_name when included in the config', async () => {
|
||||
const addon = new DatadogAddon({
|
||||
getLogger: noLogger,
|
||||
unleashUrl: 'http://some-url.com',
|
||||
|
@ -72,8 +72,7 @@ export default class DatadogAddon extends Addon {
|
||||
}
|
||||
|
||||
const { tags: eventTags } = event;
|
||||
const tags =
|
||||
eventTags && eventTags.map((tag) => `${tag.type}:${tag.value}`);
|
||||
const tags = eventTags?.map((tag) => `${tag.type}:${tag.value}`);
|
||||
const body: DDRequestBody = {
|
||||
text: text,
|
||||
title: 'Unleash notification update',
|
||||
|
@ -315,8 +315,7 @@ const testCases: [string, IEvent, string][] = [
|
||||
].map(
|
||||
([operator, display]) =>
|
||||
<[string, IEvent, string]>[
|
||||
'when default strategy updated with numeric constraint ' +
|
||||
operator,
|
||||
`when default strategy updated with numeric constraint ${operator}`,
|
||||
{
|
||||
id: 39,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
@ -504,7 +503,7 @@ const testCases: [string, IEvent, string][] = [
|
||||
];
|
||||
|
||||
testCases.forEach(([description, event, expected]) =>
|
||||
test('Should format specialised text for events ' + description, () => {
|
||||
test(`Should format specialised text for events ${description}`, () => {
|
||||
const formatter = new FeatureEventFormatterMd('unleashUrl');
|
||||
const actual = formatter.format(event);
|
||||
expect(actual).toBe(expected);
|
||||
|
@ -22,10 +22,9 @@ export interface FeatureEventFormatter {
|
||||
format: (event: IEvent) => string;
|
||||
featureLink: (event: IEvent) => string;
|
||||
}
|
||||
|
||||
export enum LinkStyle {
|
||||
SLACK,
|
||||
MD,
|
||||
SLACK = 0,
|
||||
MD = 1,
|
||||
}
|
||||
|
||||
export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
@ -254,10 +253,11 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
SEMVER_LT: 'is a SemVer less than',
|
||||
};
|
||||
const formatConstraint = (constraint: IConstraint) => {
|
||||
const val = constraint.hasOwnProperty('value')
|
||||
const val = Object.hasOwn(constraint, 'value')
|
||||
? constraint.value
|
||||
: `(${constraint.values.join(',')})`;
|
||||
const operator = constraintOperatorDescriptions.hasOwnProperty(
|
||||
const operator = Object.hasOwn(
|
||||
constraintOperatorDescriptions,
|
||||
constraint.operator,
|
||||
)
|
||||
? constraintOperatorDescriptions[constraint.operator]
|
||||
|
@ -41,7 +41,7 @@ export default async function getApp(
|
||||
|
||||
const baseUriPath = config.server.baseUriPath || '';
|
||||
const publicFolder = config.publicFolder || findPublicFolder();
|
||||
let indexHTML = await loadIndexHTML(config, publicFolder);
|
||||
const indexHTML = await loadIndexHTML(config, publicFolder);
|
||||
|
||||
app.set('trust proxy', true);
|
||||
app.disable('x-powered-by');
|
||||
|
@ -439,19 +439,19 @@ test('Environment variables for frontend CORS origins takes priority over option
|
||||
});
|
||||
|
||||
test('baseUriPath defaults to the empty string', async () => {
|
||||
let config = createConfig({});
|
||||
const config = createConfig({});
|
||||
expect(config.server.baseUriPath).toBe('');
|
||||
});
|
||||
test('BASE_URI_PATH defined in env is passed through', async () => {
|
||||
process.env.BASE_URI_PATH = '/demo';
|
||||
let config = createConfig({});
|
||||
const config = createConfig({});
|
||||
expect(config.server.baseUriPath).toBe('/demo');
|
||||
delete process.env.BASE_URI_PATH;
|
||||
});
|
||||
|
||||
test('environment variable takes precedence over configured variable', async () => {
|
||||
process.env.BASE_URI_PATH = '/demo';
|
||||
let config = createConfig({
|
||||
const config = createConfig({
|
||||
server: {
|
||||
baseUriPath: '/other',
|
||||
},
|
||||
@ -463,7 +463,7 @@ test('environment variable takes precedence over configured variable', async ()
|
||||
test.each(['demo', '/demo', '/demo/'])(
|
||||
'Trailing and leading slashes gets normalized for base path %s',
|
||||
async (path) => {
|
||||
let config = createConfig({
|
||||
const config = createConfig({
|
||||
server: {
|
||||
baseUriPath: path,
|
||||
},
|
||||
@ -473,7 +473,7 @@ test.each(['demo', '/demo', '/demo/'])(
|
||||
);
|
||||
|
||||
test('Config with enterpriseVersion set and pro environment should set isEnterprise to false', async () => {
|
||||
let config = createConfig({
|
||||
const config = createConfig({
|
||||
enterpriseVersion: '5.3.0',
|
||||
ui: { environment: 'pro' },
|
||||
});
|
||||
@ -481,7 +481,7 @@ test('Config with enterpriseVersion set and pro environment should set isEnterpr
|
||||
});
|
||||
|
||||
test('Config with enterpriseVersion set and not pro environment should set isEnterprise to true', async () => {
|
||||
let config = createConfig({
|
||||
const config = createConfig({
|
||||
enterpriseVersion: '5.3.0',
|
||||
ui: { environment: 'Enterprise' },
|
||||
});
|
||||
|
@ -78,7 +78,7 @@ const defaultClientCachingOptions: IClientCachingOption = {
|
||||
function loadClientCachingOptions(
|
||||
options: IUnleashOptions,
|
||||
): IClientCachingOption {
|
||||
let envs: Partial<IClientCachingOption> = {};
|
||||
const envs: Partial<IClientCachingOption> = {};
|
||||
if (process.env.CLIENT_FEATURE_CACHING_MAXAGE) {
|
||||
envs.maxAge = parseEnvVarNumber(
|
||||
process.env.CLIENT_FEATURE_CACHING_MAXAGE,
|
||||
|
@ -848,8 +848,7 @@ export class AccessStore implements IAccessStore {
|
||||
}
|
||||
|
||||
async getUserAccessOverview(): Promise<IUserAccessOverview[]> {
|
||||
const result = await this.db
|
||||
.raw(`SELECT u.id, u.created_at, u.name, u.email, u.seen_at, up.p_array as projects, gr.p_array as groups, gp.p_array as group_projects, r.name as root_role
|
||||
const result = await this.db.raw(`SELECT u.id, u.created_at, u.name, u.email, u.seen_at, up.p_array as projects, gr.p_array as groups, gp.p_array as group_projects, r.name as root_role
|
||||
FROM users u, LATERAL (
|
||||
SELECT ARRAY (
|
||||
SELECT ru.project
|
||||
|
@ -104,9 +104,7 @@ const remapUsageRow = (input) => {
|
||||
};
|
||||
};
|
||||
|
||||
export default class ClientApplicationsStore
|
||||
implements IClientApplicationsStore
|
||||
{
|
||||
export default class ClientApplicationsStore implements IClientApplicationsStore {
|
||||
private db: Db;
|
||||
|
||||
private logger: Logger;
|
||||
|
@ -158,7 +158,7 @@ export class ClientMetricsStoreV2 implements IClientMetricsStoreV2 {
|
||||
|
||||
// this function will collapse metrics before sending it to the database.
|
||||
async batchInsertMetrics(metrics: IClientMetricsEnv[]): Promise<void> {
|
||||
if (!metrics || metrics.length == 0) {
|
||||
if (!metrics || metrics.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = collapseHourlyMetrics(metrics).map(toRow);
|
||||
|
@ -47,7 +47,7 @@ test('Find unannounced events returns all events', async () => {
|
||||
await db.rawDatabase('events').insert(allEvents).returning(['id']);
|
||||
|
||||
const store = new EventStore(db.rawDatabase, getLogger);
|
||||
let events = await store.setUnannouncedToAnnounced();
|
||||
const events = await store.setUnannouncedToAnnounced();
|
||||
expect(events).toBeTruthy();
|
||||
expect(events.length).toBe(505);
|
||||
await db.destroy();
|
||||
|
@ -107,7 +107,7 @@ class EventStore implements IEventStore {
|
||||
}
|
||||
|
||||
async count(): Promise<number> {
|
||||
let count = await this.db(TABLE)
|
||||
const count = await this.db(TABLE)
|
||||
.count<Record<string, number>>()
|
||||
.first();
|
||||
if (!count) {
|
||||
@ -131,7 +131,7 @@ class EventStore implements IEventStore {
|
||||
if (eventSearch.feature) {
|
||||
query = query.andWhere({ feature_name: eventSearch.feature });
|
||||
}
|
||||
let count = await query.count().first();
|
||||
const count = await query.count().first();
|
||||
if (!count) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -389,7 +389,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
||||
environments: string[],
|
||||
variants: IVariant[],
|
||||
): Promise<void> {
|
||||
let v = variants || [];
|
||||
const v = variants || [];
|
||||
v.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const variantsString = JSON.stringify(v);
|
||||
const records = environments.map((env) => ({
|
||||
@ -407,7 +407,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
||||
async addFeatureEnvironment(
|
||||
featureEnvironment: IFeatureEnvironment,
|
||||
): Promise<void> {
|
||||
let v = featureEnvironment.variants || [];
|
||||
const v = featureEnvironment.variants || [];
|
||||
v.sort((a, b) => a.name.localeCompare(b.name));
|
||||
await this.db(T.featureEnvs)
|
||||
.insert({
|
||||
@ -424,11 +424,11 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
): Promise<void> {
|
||||
let sourceFeatureStrategies = await this.db('feature_strategies').where(
|
||||
{
|
||||
environment: sourceEnvironment,
|
||||
},
|
||||
);
|
||||
const sourceFeatureStrategies = await this.db(
|
||||
'feature_strategies',
|
||||
).where({
|
||||
environment: sourceEnvironment,
|
||||
});
|
||||
|
||||
const clonedStrategyRows = sourceFeatureStrategies.map(
|
||||
(featureStrategy) => {
|
||||
|
@ -493,10 +493,10 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
.whereIn(['tag_type', 'tag_value'], tag);
|
||||
query = query.whereIn('features.name', tagQuery);
|
||||
}
|
||||
if (namePrefix && namePrefix.trim()) {
|
||||
if (namePrefix?.trim()) {
|
||||
let namePrefixQuery = namePrefix;
|
||||
if (!namePrefix.endsWith('%')) {
|
||||
namePrefixQuery = namePrefixQuery + '%';
|
||||
namePrefixQuery = `${namePrefixQuery}%`;
|
||||
}
|
||||
query = query.whereILike('features.name', namePrefixQuery);
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ export default class FeatureToggleClientStore
|
||||
stopTimer();
|
||||
|
||||
const featureToggles = rows.reduce((acc, r) => {
|
||||
let feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
||||
const feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
||||
strategies: [],
|
||||
};
|
||||
if (this.isUnseenStrategyRow(feature, r) && !r.strategy_disabled) {
|
||||
|
@ -122,7 +122,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
||||
dateAccessor: string;
|
||||
}): Promise<number> {
|
||||
const { project, archived, dateAccessor } = queryModifiers;
|
||||
let query = this.db
|
||||
const query = this.db
|
||||
.count()
|
||||
.from(TABLE)
|
||||
.where({ project })
|
||||
|
@ -361,7 +361,7 @@ class ProjectStore implements IProjectStore {
|
||||
async getProjectLinksForEnvironments(
|
||||
environments: string[],
|
||||
): Promise<IEnvironmentProjectLink[]> {
|
||||
let rows = await this.db('project_environments')
|
||||
const rows = await this.db('project_environments')
|
||||
.select(['project_id', 'environment_name'])
|
||||
.whereIn('environment_name', environments);
|
||||
return rows.map(this.mapLinkRow);
|
||||
|
@ -17,11 +17,11 @@ class BadDataError extends UnleashError {
|
||||
message: string,
|
||||
errors?: [ValidationErrorDescription, ...ValidationErrorDescription[]],
|
||||
) {
|
||||
const topLevelMessage =
|
||||
'Request validation failed: your request body or params contain invalid data' +
|
||||
(errors
|
||||
const topLevelMessage = `Request validation failed: your request body or params contain invalid data${
|
||||
errors
|
||||
? '. Refer to the `details` list for more information.'
|
||||
: `: ${message}`);
|
||||
: `: ${message}`
|
||||
}`;
|
||||
super(topLevelMessage);
|
||||
|
||||
this.details = errors ?? [{ message: message, description: message }];
|
||||
|
@ -23,8 +23,6 @@ const getStatusCode = (errorName: string): number => {
|
||||
return 400;
|
||||
case 'InvalidTokenError':
|
||||
return 401;
|
||||
case 'NoAccessError':
|
||||
return 403;
|
||||
case 'UsedTokenError':
|
||||
return 403;
|
||||
case 'InvalidOperationError':
|
||||
|
@ -44,10 +44,9 @@ export class ImportTogglesStore implements IImportTogglesStore {
|
||||
environment: string,
|
||||
): Promise<boolean> {
|
||||
if (featureNames.length === 0) return true;
|
||||
const joinedFeatureNames = featureNames.map(() => '?').join(',');
|
||||
const result = await this.db.raw(
|
||||
'SELECT EXISTS (SELECT 1 FROM feature_strategies WHERE environment = ? and feature_name in (' +
|
||||
featureNames.map(() => '?').join(',') +
|
||||
')) AS present',
|
||||
`SELECT EXISTS (SELECT 1 FROM feature_strategies WHERE environment = ? and feature_name in (${joinedFeatureNames})) AS present`,
|
||||
[environment, ...featureNames],
|
||||
);
|
||||
const { present } = result.rows[0];
|
||||
|
@ -30,7 +30,7 @@ export function resolveContextValue(
|
||||
if (context[field]) {
|
||||
return context[field] as string;
|
||||
}
|
||||
if (context.properties && context.properties[field]) {
|
||||
if (context.properties?.[field]) {
|
||||
return context.properties[field] as string;
|
||||
}
|
||||
return undefined;
|
||||
|
@ -35,14 +35,12 @@ export const mapFeaturesForClient = (
|
||||
type: variant.payload.type as PayloadType,
|
||||
},
|
||||
})),
|
||||
constraints:
|
||||
strategy.constraints &&
|
||||
strategy.constraints.map((constraint) => ({
|
||||
inverted: false,
|
||||
values: [],
|
||||
...constraint,
|
||||
operator: constraint.operator as unknown as Operator,
|
||||
})),
|
||||
constraints: strategy.constraints?.map((constraint) => ({
|
||||
inverted: false,
|
||||
values: [],
|
||||
...constraint,
|
||||
operator: constraint.operator as unknown as Operator,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { IPrivateProjectChecker } from './privateProjectCheckerType';
|
||||
import { Promise } from 'ts-toolbelt/out/Any/Promise';
|
||||
import { ProjectAccess } from './privateProjectStore';
|
||||
|
||||
export class FakePrivateProjectChecker implements IPrivateProjectChecker {
|
||||
|
@ -42,7 +42,7 @@ class PrivateProjectStore implements IPrivateProjectStore {
|
||||
.count('*')
|
||||
.first();
|
||||
|
||||
if (!isViewer || isViewer.count == 0) {
|
||||
if (!isViewer || isViewer.count === 0) {
|
||||
return ALL_PROJECT_ACCESS;
|
||||
}
|
||||
|
||||
|
@ -351,7 +351,7 @@ export default class MetricsMonitor {
|
||||
}
|
||||
|
||||
configureDbMetrics(db: Knex, eventBus: EventEmitter): void {
|
||||
if (db && db.client) {
|
||||
if (db?.client) {
|
||||
const dbPoolMin = new client.Gauge({
|
||||
name: 'db_pool_min',
|
||||
help: 'Minimum DB pool size',
|
||||
|
@ -13,7 +13,7 @@ const authorizationMiddleware = (
|
||||
logger.debug('Enabling Authorization middleware');
|
||||
|
||||
return async (req: IAuthRequest, res: Response, next: NextFunction) => {
|
||||
if (req.session && req.session.user) {
|
||||
if (req.session?.user) {
|
||||
req.user = req.session.user;
|
||||
return next();
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { ApiTokenType } from '../types/models/api-token';
|
||||
|
||||
function demoAuthentication(
|
||||
app: Application,
|
||||
basePath: string = '', // eslint-disable-line
|
||||
basePath: string, // eslint-disable-line
|
||||
{ userService }: Pick<IUnleashServices, 'userService'>,
|
||||
{ authentication }: Pick<IUnleashConfig, 'authentication'>,
|
||||
): void {
|
||||
@ -30,7 +30,7 @@ function demoAuthentication(
|
||||
|
||||
app.use(`${basePath}/api/admin/`, (req, res, next) => {
|
||||
// @ts-expect-error
|
||||
if (req.session.user && req.session.user.email) {
|
||||
if (req.session.user?.email) {
|
||||
// @ts-expect-error
|
||||
req.user = req.session.user;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import { Application } from 'express';
|
||||
import NoAuthUser from '../types/no-auth-user';
|
||||
|
||||
// eslint-disable-next-line
|
||||
function noneAuthentication(basePath = '', app: Application): void {
|
||||
app.use(`${basePath}/api/admin/`, (req, res, next) => {
|
||||
function noneAuthentication(basePath: string, app: Application): void {
|
||||
app.use(`${basePath || ''}/api/admin/`, (req, res, next) => {
|
||||
// @ts-expect-error
|
||||
if (!req.user) {
|
||||
// @ts-expect-error
|
||||
|
@ -111,7 +111,7 @@ test('should call next if accountService throws exception', async () => {
|
||||
});
|
||||
|
||||
test('Should not log at error level if user not found', async () => {
|
||||
let fakeLogger = {
|
||||
const fakeLogger = {
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
warn: jest.fn(),
|
||||
@ -131,7 +131,7 @@ test('Should not log at error level if user not found', async () => {
|
||||
throw new NotFoundError('Could not find pat');
|
||||
}),
|
||||
};
|
||||
let mw = patMiddleware(conf, { accountService });
|
||||
const mw = patMiddleware(conf, { accountService });
|
||||
const cb = jest.fn();
|
||||
|
||||
const req = {
|
||||
|
@ -62,7 +62,7 @@ const rbacMiddleware = (
|
||||
|
||||
let projectId =
|
||||
findParam('projectId', req) || findParam('project', req);
|
||||
let environment =
|
||||
const environment =
|
||||
findParam('environment', req) ||
|
||||
findParam('environmentId', req);
|
||||
|
||||
@ -80,7 +80,7 @@ const rbacMiddleware = (
|
||||
projectId === undefined &&
|
||||
permissionsArray.some(
|
||||
(permission) =>
|
||||
permission == CREATE_FEATURE ||
|
||||
permission === CREATE_FEATURE ||
|
||||
permission.endsWith('FEATURE_STRATEGY'),
|
||||
)
|
||||
) {
|
||||
|
@ -18,8 +18,9 @@ test('all schema files should be added to the schemas object', () => {
|
||||
});
|
||||
|
||||
test('removeJsonSchemaProps', () => {
|
||||
expect(removeJsonSchemaProps({ a: 'b', $id: 'c', components: {} }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
removeJsonSchemaProps({ a: 'b', $id: 'c', components: {} }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"a": "b",
|
||||
}
|
||||
@ -41,7 +42,7 @@ describe('createOpenApiSchema', () => {
|
||||
createOpenApiSchema({
|
||||
unleashUrl: 'https://example.com/demo2',
|
||||
baseUriPath: '/demo2',
|
||||
}).servers![0].url,
|
||||
}).servers?.[0].url,
|
||||
).toEqual('https://example.com/demo2');
|
||||
});
|
||||
|
||||
@ -50,7 +51,7 @@ describe('createOpenApiSchema', () => {
|
||||
createOpenApiSchema({
|
||||
unleashUrl: 'https://example.com/demo2',
|
||||
baseUriPath: 'example',
|
||||
}).servers![0].url,
|
||||
}).servers?.[0].url,
|
||||
).toEqual('https://example.com/demo2');
|
||||
});
|
||||
|
||||
@ -59,13 +60,13 @@ describe('createOpenApiSchema', () => {
|
||||
createOpenApiSchema({
|
||||
unleashUrl: 'https://example.com/example/',
|
||||
baseUriPath: 'example',
|
||||
}).servers![0].url,
|
||||
}).servers?.[0].url,
|
||||
).toEqual('https://example.com');
|
||||
expect(
|
||||
createOpenApiSchema({
|
||||
unleashUrl: 'https://example.com/example/',
|
||||
baseUriPath: '/example',
|
||||
}).servers![0].url,
|
||||
}).servers?.[0].url,
|
||||
).toEqual('https://example.com/example');
|
||||
});
|
||||
});
|
||||
|
@ -58,7 +58,7 @@ export const resourceCreatedResponseSchema = (
|
||||
},
|
||||
},
|
||||
},
|
||||
description: `The resource was successfully created.`,
|
||||
description: 'The resource was successfully created.',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
|
@ -24,10 +24,7 @@ type Services = Pick<
|
||||
'featureToggleServiceV2' | 'segmentService' | 'configurationRevisionService'
|
||||
>;
|
||||
|
||||
export class ProxyRepository
|
||||
extends EventEmitter
|
||||
implements RepositoryInterface
|
||||
{
|
||||
export class ProxyRepository extends EventEmitter implements RepositoryInterface {
|
||||
private readonly config: Config;
|
||||
|
||||
private readonly logger: Logger;
|
||||
|
@ -51,46 +51,44 @@ interface TokenParam {
|
||||
interface TokenNameParam {
|
||||
name: string;
|
||||
}
|
||||
export const tokenTypeToCreatePermission: (
|
||||
tokenType: ApiTokenType,
|
||||
) => string = (tokenType) => {
|
||||
switch (tokenType) {
|
||||
case ApiTokenType.ADMIN:
|
||||
return ADMIN;
|
||||
case ApiTokenType.CLIENT:
|
||||
return CREATE_CLIENT_API_TOKEN;
|
||||
case ApiTokenType.FRONTEND:
|
||||
return CREATE_FRONTEND_API_TOKEN;
|
||||
}
|
||||
};
|
||||
export const tokenTypeToCreatePermission: (tokenType: ApiTokenType) => string =
|
||||
(tokenType) => {
|
||||
switch (tokenType) {
|
||||
case ApiTokenType.ADMIN:
|
||||
return ADMIN;
|
||||
case ApiTokenType.CLIENT:
|
||||
return CREATE_CLIENT_API_TOKEN;
|
||||
case ApiTokenType.FRONTEND:
|
||||
return CREATE_FRONTEND_API_TOKEN;
|
||||
}
|
||||
};
|
||||
|
||||
const permissionToTokenType: (
|
||||
permission: string,
|
||||
) => ApiTokenType | undefined = (permission) => {
|
||||
if (
|
||||
[
|
||||
CREATE_FRONTEND_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
DELETE_FRONTEND_API_TOKEN,
|
||||
UPDATE_FRONTEND_API_TOKEN,
|
||||
].includes(permission)
|
||||
) {
|
||||
return ApiTokenType.FRONTEND;
|
||||
} else if (
|
||||
[
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
].includes(permission)
|
||||
) {
|
||||
return ApiTokenType.CLIENT;
|
||||
} else if (ADMIN === permission) {
|
||||
return ApiTokenType.ADMIN;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const permissionToTokenType: (permission: string) => ApiTokenType | undefined =
|
||||
(permission) => {
|
||||
if (
|
||||
[
|
||||
CREATE_FRONTEND_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
DELETE_FRONTEND_API_TOKEN,
|
||||
UPDATE_FRONTEND_API_TOKEN,
|
||||
].includes(permission)
|
||||
) {
|
||||
return ApiTokenType.FRONTEND;
|
||||
} else if (
|
||||
[
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
].includes(permission)
|
||||
) {
|
||||
return ApiTokenType.CLIENT;
|
||||
} else if (ADMIN === permission) {
|
||||
return ApiTokenType.ADMIN;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const tokenTypeToUpdatePermission: (tokenType: ApiTokenType) => string = (
|
||||
tokenType,
|
||||
@ -386,7 +384,7 @@ export class ApiTokenController extends Controller {
|
||||
const permissionRequired = tokenTypeToDeletePermission(
|
||||
tokenToUpdate.type,
|
||||
);
|
||||
let hasPermission = await this.accessService.hasPermission(
|
||||
const hasPermission = await this.accessService.hasPermission(
|
||||
req.user,
|
||||
permissionRequired,
|
||||
);
|
||||
|
@ -120,7 +120,7 @@ class ConfigController extends Controller {
|
||||
|
||||
const disablePasswordAuth =
|
||||
simpleAuthSettings?.disabled ||
|
||||
this.config.authentication.type == IAuthType.NONE;
|
||||
this.config.authentication.type === IAuthType.NONE;
|
||||
|
||||
const expFlags = this.config.flagResolver.getAll({
|
||||
email: req.user.email,
|
||||
|
@ -661,8 +661,8 @@ export default class ProjectFeaturesController extends Controller {
|
||||
featureName,
|
||||
projectId,
|
||||
name,
|
||||
replaceGroupId,
|
||||
userName,
|
||||
replaceGroupId,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
|
@ -63,7 +63,7 @@ describe('Public Signup API', () => {
|
||||
destroy();
|
||||
});
|
||||
const expireAt = (addDays: number = 7): Date => {
|
||||
let now = new Date();
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + addDays);
|
||||
return now;
|
||||
};
|
||||
|
@ -425,7 +425,7 @@ export default class UserAdminController extends Controller {
|
||||
req: Request,
|
||||
res: Response<UsersGroupsBaseSchema>,
|
||||
): Promise<void> {
|
||||
let allUsers = await this.accountService.getAll();
|
||||
const allUsers = await this.accountService.getAll();
|
||||
let users = allUsers.map((u) => {
|
||||
return {
|
||||
id: u.id,
|
||||
@ -439,8 +439,8 @@ export default class UserAdminController extends Controller {
|
||||
users = this.anonymiseUsers(users);
|
||||
}
|
||||
|
||||
let allGroups = await this.groupService.getAll();
|
||||
let groups = allGroups.map((g) => {
|
||||
const allGroups = await this.groupService.getAll();
|
||||
const groups = allGroups.map((g) => {
|
||||
return {
|
||||
id: g.id,
|
||||
name: g.name,
|
||||
|
@ -39,8 +39,7 @@ interface IRouteOptionsNonGet extends IRouteOptionsBase {
|
||||
type IRouteOptions = IRouteOptionsNonGet | IRouteOptionsGet;
|
||||
|
||||
const checkPermission =
|
||||
(permission: Permission = []) =>
|
||||
async (req, res, next) => {
|
||||
(permission: Permission = []) => async (req, res, next) => {
|
||||
const permissions = (
|
||||
Array.isArray(permission) ? permission : [permission]
|
||||
).filter((p) => p !== NONE);
|
||||
|
@ -118,7 +118,7 @@ export default class EdgeController extends Controller {
|
||||
const { metrics, applications } = body;
|
||||
|
||||
try {
|
||||
let promises: Promise<void>[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const app of applications) {
|
||||
promises.push(
|
||||
this.clientInstanceService.registerClient(app, clientIp),
|
||||
|
@ -279,10 +279,10 @@ test('Should destroy sessions for user', async () => {
|
||||
},
|
||||
expired: addDays(new Date(), 2),
|
||||
});
|
||||
let activeSessionsBeforeLogout = await sessionStore.getSessionsForUser(1);
|
||||
const activeSessionsBeforeLogout = await sessionStore.getSessionsForUser(1);
|
||||
expect(activeSessionsBeforeLogout).toHaveLength(2);
|
||||
app.use('/logout', new LogoutController(config, { sessionService }).router);
|
||||
await supertest(app).post('/logout').expect(302);
|
||||
let activeSessions = await sessionStore.getSessionsForUser(1);
|
||||
const activeSessions = await sessionStore.getSessionsForUser(1);
|
||||
expect(activeSessions).toHaveLength(0);
|
||||
});
|
||||
|
@ -69,7 +69,7 @@ describe('Public Signup API', () => {
|
||||
destroy();
|
||||
});
|
||||
const expireAt = (addDays: number = 7): Date => {
|
||||
let now = new Date();
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + addDays);
|
||||
return now;
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ test('should reject token with projects and project', async () => {
|
||||
});
|
||||
|
||||
test('should not have default project set if projects is present', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
const token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'admin',
|
||||
projects: ['default'],
|
||||
@ -27,7 +27,7 @@ test('should not have default project set if projects is present', async () => {
|
||||
});
|
||||
|
||||
test('should have project set to default if projects is missing', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
const token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'admin',
|
||||
});
|
||||
@ -35,7 +35,7 @@ test('should have project set to default if projects is missing', async () => {
|
||||
});
|
||||
|
||||
test('should not have projects set if project is present', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
const token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'admin',
|
||||
project: 'default',
|
||||
@ -44,7 +44,7 @@ test('should not have projects set if project is present', async () => {
|
||||
});
|
||||
|
||||
test('should allow for embedded proxy (frontend) key', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
const token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'frontend',
|
||||
project: 'default',
|
||||
@ -53,7 +53,7 @@ test('should allow for embedded proxy (frontend) key', async () => {
|
||||
});
|
||||
|
||||
test('should set environment to default for frontend key', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
const token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'frontend',
|
||||
project: 'default',
|
||||
|
@ -113,5 +113,5 @@ test('should shutdown the server when calling stop()', async () => {
|
||||
createTestConfig({ server: { port: 0 } }),
|
||||
);
|
||||
await stop();
|
||||
expect(server!.address()).toBe(null);
|
||||
expect(server?.address()).toBe(null);
|
||||
});
|
||||
|
@ -500,7 +500,7 @@ export class AccessService {
|
||||
const userIdList = userRoleList.map((u) => u.userId);
|
||||
const users = await this.accountStore.getAllWithId(userIdList);
|
||||
return users.map((user) => {
|
||||
const role = userRoleList.find((r) => r.userId == user.id)!;
|
||||
const role = userRoleList.find((r) => r.userId === user.id)!;
|
||||
return {
|
||||
...user,
|
||||
addedAt: role.addedAt!,
|
||||
|
@ -113,7 +113,7 @@ export default class AddonService {
|
||||
(addon) =>
|
||||
!event.project ||
|
||||
!addon.projects ||
|
||||
addon.projects.length == 0 ||
|
||||
addon.projects.length === 0 ||
|
||||
addon.projects[0] === WILDCARD_OPTION ||
|
||||
addon.projects.includes(event.project),
|
||||
)
|
||||
@ -121,7 +121,7 @@ export default class AddonService {
|
||||
(addon) =>
|
||||
!event.environment ||
|
||||
!addon.environments ||
|
||||
addon.environments.length == 0 ||
|
||||
addon.environments.length === 0 ||
|
||||
addon.environments[0] === WILDCARD_OPTION ||
|
||||
addon.environments.includes(event.environment),
|
||||
)
|
||||
|
@ -133,8 +133,8 @@ test('Api token operations should all have events attached', async () => {
|
||||
config,
|
||||
eventService,
|
||||
);
|
||||
let saved = await apiTokenService.createApiTokenWithProjects(token);
|
||||
let newExpiry = addDays(new Date(), 30);
|
||||
const saved = await apiTokenService.createApiTokenWithProjects(token);
|
||||
const newExpiry = addDays(new Date(), 30);
|
||||
await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test');
|
||||
await apiTokenService.delete(saved.secret, 'test');
|
||||
const { events } = await eventService.getEvents();
|
||||
|
@ -81,7 +81,7 @@ export class ApiTokenService {
|
||||
try {
|
||||
this.activeTokens = await this.getAllActiveTokens();
|
||||
} finally {
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
// biome-ignore lint/correctness/noUnsafeFinally: We ignored this for eslint. Leaving this here for now, server-impl test fails without it
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -259,7 +259,7 @@ export class ApiTokenService {
|
||||
if (!errorDetails) {
|
||||
return 'invalid';
|
||||
}
|
||||
let invalidProject = projects.find((project) => {
|
||||
const invalidProject = projects.find((project) => {
|
||||
return errorDetails.includes(`=(${project})`);
|
||||
});
|
||||
return invalidProject || 'invalid';
|
||||
|
@ -43,7 +43,7 @@ export class EmailService {
|
||||
|
||||
constructor(email: IEmailOption, getLogger: LogProvider) {
|
||||
this.logger = getLogger('services/email-service.ts');
|
||||
if (email && email.host) {
|
||||
if (email?.host) {
|
||||
this.sender = email.sender;
|
||||
if (email.host === 'test') {
|
||||
this.mailer = createTransport({ jsonTransport: true });
|
||||
|
@ -152,12 +152,9 @@ export default class EnvironmentService {
|
||||
}
|
||||
|
||||
const environmentsNotAlreadyEnabled =
|
||||
existingEnvironmentsToEnable.filter((env) => env.enabled == false);
|
||||
existingEnvironmentsToEnable.filter((env) => !env.enabled);
|
||||
const environmentsToDisable = allEnvironments.filter((env) => {
|
||||
return (
|
||||
!environmentNamesToEnable.includes(env.name) &&
|
||||
env.enabled == true
|
||||
);
|
||||
return !environmentNamesToEnable.includes(env.name) && env.enabled;
|
||||
});
|
||||
|
||||
await this.environmentStore.disable(environmentsToDisable);
|
||||
@ -190,13 +187,13 @@ export default class EnvironmentService {
|
||||
...new Set(projectLinks.map((link) => link.projectId)),
|
||||
];
|
||||
|
||||
let linkTasks = uniqueProjects.map((project) => {
|
||||
const linkTasks = uniqueProjects.flatMap((project) => {
|
||||
return toEnable.map((enabledEnv) => {
|
||||
return this.addEnvironmentToProject(enabledEnv.name, project);
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(linkTasks.flat());
|
||||
await Promise.all(linkTasks);
|
||||
}
|
||||
|
||||
async forceRemoveEnvironmentFromProject(
|
||||
|
@ -27,8 +27,8 @@ export default class EventService {
|
||||
}
|
||||
|
||||
async getEvents(): Promise<IEventList> {
|
||||
let totalEvents = await this.eventStore.count();
|
||||
let events = await this.eventStore.getEvents();
|
||||
const totalEvents = await this.eventStore.count();
|
||||
const events = await this.eventStore.getEvents();
|
||||
return {
|
||||
events,
|
||||
totalEvents,
|
||||
@ -36,8 +36,8 @@ export default class EventService {
|
||||
}
|
||||
|
||||
async searchEvents(search: SearchEventsSchema): Promise<IEventList> {
|
||||
let totalEvents = await this.eventStore.filteredCount(search);
|
||||
let events = await this.eventStore.searchEvents(search);
|
||||
const totalEvents = await this.eventStore.filteredCount(search);
|
||||
const events = await this.eventStore.searchEvents(search);
|
||||
return {
|
||||
events,
|
||||
totalEvents,
|
||||
|
@ -348,8 +348,7 @@ class FeatureToggleService {
|
||||
}
|
||||
|
||||
if (
|
||||
contextDefinition &&
|
||||
contextDefinition.legalValues &&
|
||||
contextDefinition?.legalValues &&
|
||||
contextDefinition.legalValues.length > 0
|
||||
) {
|
||||
const valuesToValidate = oneOf(
|
||||
@ -1181,8 +1180,8 @@ class FeatureToggleService {
|
||||
featureName: string,
|
||||
projectId: string,
|
||||
newFeatureName: string,
|
||||
replaceGroupId: boolean = true, // eslint-disable-line
|
||||
userName: string,
|
||||
replaceGroupId: boolean = true,
|
||||
): Promise<FeatureToggle> {
|
||||
const changeRequestEnabled =
|
||||
await this.changeRequestAccessReadModel.isChangeRequestsEnabledForProject(
|
||||
@ -1227,7 +1226,7 @@ class FeatureToggleService {
|
||||
if (
|
||||
replaceGroupId &&
|
||||
s.parameters &&
|
||||
s.parameters.hasOwnProperty('groupId')
|
||||
Object.hasOwn(s.parameters, 'groupId')
|
||||
) {
|
||||
s.parameters.groupId = newFeatureName;
|
||||
}
|
||||
@ -1890,7 +1889,9 @@ class FeatureToggleService {
|
||||
env.name,
|
||||
newVariants,
|
||||
user,
|
||||
).then((resultingVariants) => (env.variants = resultingVariants)),
|
||||
).then((resultingVariants) => {
|
||||
env.variants = resultingVariants;
|
||||
}),
|
||||
);
|
||||
await Promise.all(promises);
|
||||
ft.variants = ft.environments[0].variants;
|
||||
@ -2077,11 +2078,11 @@ class FeatureToggleService {
|
||||
);
|
||||
}
|
||||
|
||||
let fixedVariants = variants.filter((x) => {
|
||||
const fixedVariants = variants.filter((x) => {
|
||||
return x.weightType === WeightType.FIX;
|
||||
});
|
||||
|
||||
let fixedWeights = fixedVariants.reduce((a, v) => a + v.weight, 0);
|
||||
const fixedWeights = fixedVariants.reduce((a, v) => a + v.weight, 0);
|
||||
|
||||
if (fixedWeights > 1000) {
|
||||
throw new BadDataError(
|
||||
@ -2089,7 +2090,7 @@ class FeatureToggleService {
|
||||
);
|
||||
}
|
||||
|
||||
let averageWeight = Math.floor(
|
||||
const averageWeight = Math.floor(
|
||||
(1000 - fixedWeights) / variableVariants.length,
|
||||
);
|
||||
let remainder = (1000 - fixedWeights) % variableVariants.length;
|
||||
|
@ -118,7 +118,7 @@ export class GroupService {
|
||||
const deletableUsers = existingUsers.filter(
|
||||
(existingUser) =>
|
||||
!group.users.some(
|
||||
(groupUser) => groupUser.user.id == existingUser.userId,
|
||||
(groupUser) => groupUser.user.id === existingUser.userId,
|
||||
),
|
||||
);
|
||||
|
||||
@ -188,7 +188,7 @@ export class GroupService {
|
||||
throw new BadDataError('Group name cannot be empty');
|
||||
}
|
||||
|
||||
if (!existingGroup || existingGroup.name != group.name) {
|
||||
if (!existingGroup || existingGroup.name !== group.name) {
|
||||
if (await this.groupStore.existsWithName(group.name)) {
|
||||
throw new NameExistsError('Group name already exists');
|
||||
}
|
||||
@ -205,18 +205,18 @@ export class GroupService {
|
||||
allUsers: IUser[],
|
||||
): IGroupModel {
|
||||
const groupUsers = allGroupUsers.filter(
|
||||
(user) => user.groupId == group.id,
|
||||
(user) => user.groupId === group.id,
|
||||
);
|
||||
const groupUsersId = groupUsers.map((user) => user.userId);
|
||||
const selectedUsers = allUsers.filter((user) =>
|
||||
groupUsersId.includes(user.id),
|
||||
);
|
||||
const finalUsers = selectedUsers.map((user) => {
|
||||
const roleUser = groupUsers.find((gu) => gu.userId == user.id);
|
||||
const roleUser = groupUsers.find((gu) => gu.userId === user.id);
|
||||
return {
|
||||
user: user,
|
||||
joinedAt: roleUser.joinedAt,
|
||||
createdBy: roleUser.createdBy,
|
||||
joinedAt: roleUser?.joinedAt,
|
||||
createdBy: roleUser?.createdBy,
|
||||
};
|
||||
});
|
||||
return { ...group, users: finalUsers };
|
||||
@ -228,7 +228,7 @@ export class GroupService {
|
||||
createdBy?: string,
|
||||
): Promise<void> {
|
||||
if (Array.isArray(externalGroups)) {
|
||||
let newGroups = await this.groupStore.getNewGroupsForExternalUser(
|
||||
const newGroups = await this.groupStore.getNewGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
@ -237,7 +237,7 @@ export class GroupService {
|
||||
newGroups.map((g) => g.id),
|
||||
createdBy,
|
||||
);
|
||||
let oldGroups = await this.groupStore.getOldGroupsForExternalUser(
|
||||
const oldGroups = await this.groupStore.getOldGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
|
@ -58,7 +58,7 @@ export class OpenApiService {
|
||||
|
||||
useErrorHandler(app: Express): void {
|
||||
app.use((err, req, res, next) => {
|
||||
if (err && err.status && err.validationErrors) {
|
||||
if (err?.status && err.validationErrors) {
|
||||
const apiError = fromOpenApiValidationErrors(
|
||||
req.body,
|
||||
err.validationErrors,
|
||||
|
@ -756,7 +756,9 @@ export default class ProjectService {
|
||||
projectId,
|
||||
);
|
||||
const groups = await this.groupService.getProjectGroups(projectId);
|
||||
const roleGroups = groups.filter((g) => g.roleId == currentRole.id);
|
||||
const roleGroups = groups.filter(
|
||||
(g) => g.roleId === currentRole.id,
|
||||
);
|
||||
if (users.length + roleGroups.length < 2) {
|
||||
throw new ProjectWithoutOwnerError();
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
async deleteClientForProxyToken(secret: string): Promise<void> {
|
||||
let clientPromise = this.clients.get(secret);
|
||||
const clientPromise = this.clients.get(secret);
|
||||
if (clientPromise) {
|
||||
const client = await clientPromise;
|
||||
client.destroy();
|
||||
|
@ -153,7 +153,7 @@ export default class StateService {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
moveVariantsToFeatureEnvironments(data: any) {
|
||||
data.featureEnvironments?.forEach((featureEnvironment) => {
|
||||
let feature = data.features?.find(
|
||||
const feature = data.features?.find(
|
||||
(f) => f.name === featureEnvironment.featureName,
|
||||
);
|
||||
if (feature) {
|
||||
@ -634,7 +634,7 @@ export default class StateService {
|
||||
async importTagTypes(
|
||||
tagTypes: ITagType[],
|
||||
keepExisting: boolean,
|
||||
oldTagTypes: ITagType[] = [], // eslint-disable-line
|
||||
oldTagTypes: ITagType[],
|
||||
userName: string,
|
||||
): Promise<void> {
|
||||
const tagTypesToInsert = tagTypes.filter((tagType) =>
|
||||
|
@ -17,19 +17,16 @@ export const parseFile: (file: string, data: string) => any = (
|
||||
export const filterExisting: (
|
||||
keepExisting: boolean,
|
||||
existingArray: any[],
|
||||
) => (item: any) => boolean =
|
||||
(keepExisting, existingArray = []) =>
|
||||
(item) => {
|
||||
if (keepExisting) {
|
||||
const found = existingArray.find((t) => t.name === item.name);
|
||||
return !found;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
) => (item: any) => boolean = (keepExisting, existingArray = []) => (item) => {
|
||||
if (keepExisting) {
|
||||
const found = existingArray.find((t) => t.name === item.name);
|
||||
return !found;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const filterEqual: (existingArray: any[]) => (item: any) => boolean =
|
||||
(existingArray = []) =>
|
||||
(item) => {
|
||||
(existingArray = []) => (item) => {
|
||||
const toggle = existingArray.find((t) => t.name === item.name);
|
||||
if (toggle) {
|
||||
return JSON.stringify(toggle) !== JSON.stringify(item);
|
||||
|
@ -68,7 +68,7 @@ export default class TagTypeService {
|
||||
|
||||
async validate(tagType: Partial<ITagType> | undefined): Promise<void> {
|
||||
await tagTypeSchema.validateAsync(tagType);
|
||||
if (tagType && tagType.name) {
|
||||
if (tagType?.name) {
|
||||
await this.validateUnique(tagType.name);
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ class UserService {
|
||||
this.emailService = services.emailService;
|
||||
this.sessionService = services.sessionService;
|
||||
this.settingService = services.settingService;
|
||||
if (authentication && authentication.createAdminUser) {
|
||||
if (authentication?.createAdminUser) {
|
||||
process.nextTick(() => this.initAdminUser());
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ export const findDuplicates = <T>(arr: T[]): T[] => {
|
||||
const seen: Set<T> = new Set();
|
||||
const duplicates: Set<T> = new Set();
|
||||
|
||||
for (let item of arr) {
|
||||
for (const item of arr) {
|
||||
if (seen.has(item)) {
|
||||
duplicates.add(item);
|
||||
} else {
|
||||
|
@ -11,9 +11,15 @@ export const urlFriendlyString = (): Arbitrary<string> =>
|
||||
fc
|
||||
.array(
|
||||
fc.oneof(
|
||||
fc.integer({ min: 0x30, max: 0x39 }).map(String.fromCharCode), // numbers
|
||||
fc.integer({ min: 0x41, max: 0x5a }).map(String.fromCharCode), // UPPERCASE LETTERS
|
||||
fc.integer({ min: 0x61, max: 0x7a }).map(String.fromCharCode), // lowercase letters
|
||||
fc
|
||||
.integer({ min: 0x30, max: 0x39 })
|
||||
.map(String.fromCharCode), // numbers
|
||||
fc
|
||||
.integer({ min: 0x41, max: 0x5a })
|
||||
.map(String.fromCharCode), // UPPERCASE LETTERS
|
||||
fc
|
||||
.integer({ min: 0x61, max: 0x7a })
|
||||
.map(String.fromCharCode), // lowercase letters
|
||||
fc.constantFrom('-', '_', '~', '.'), // rest
|
||||
fc.lorem({ maxCount: 1 }), // random words for more 'realistic' names
|
||||
),
|
||||
|
@ -24,7 +24,7 @@ const createFeature = async (featureName: string) => {
|
||||
|
||||
const loginRegularUser = () =>
|
||||
app.request
|
||||
.post(`/auth/demo/login`)
|
||||
.post('/auth/demo/login')
|
||||
.send({
|
||||
email: `${regularUserName}@getunleash.io`,
|
||||
})
|
||||
@ -74,7 +74,7 @@ const getProject = async (projectName = 'default') => {
|
||||
|
||||
const getProjects = async () => {
|
||||
return app.request
|
||||
.get(`/api/admin/projects`)
|
||||
.get('/api/admin/projects')
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
};
|
||||
@ -121,7 +121,7 @@ test('should be favorited in project endpoint', async () => {
|
||||
await favoriteProject();
|
||||
|
||||
const { body } = await app.request
|
||||
.get(`/api/admin/projects/default`)
|
||||
.get('/api/admin/projects/default')
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
@ -157,7 +157,7 @@ test('should be favorited in admin endpoint', async () => {
|
||||
await favoriteFeature(featureName);
|
||||
|
||||
const { body } = await app.request
|
||||
.get(`/api/admin/features`)
|
||||
.get('/api/admin/features')
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
|
@ -44,7 +44,7 @@ describe('updating lifetimes', () => {
|
||||
'it updates to the lifetime correctly: `%s`',
|
||||
async (lifetimeDays) => {
|
||||
const { body } = await app.request
|
||||
.put(`/api/admin/feature-types/release/lifetime`)
|
||||
.put('/api/admin/feature-types/release/lifetime')
|
||||
.send({ lifetimeDays })
|
||||
.expect(200);
|
||||
|
||||
@ -53,7 +53,7 @@ describe('updating lifetimes', () => {
|
||||
);
|
||||
test("if the feature type doesn't exist, you get a 404", async () => {
|
||||
await app.request
|
||||
.put(`/api/admin/feature-types/bogus-feature-type/lifetime`)
|
||||
.put('/api/admin/feature-types/bogus-feature-type/lifetime')
|
||||
.send({ lifetimeDays: 45 })
|
||||
.expect(404);
|
||||
});
|
||||
@ -72,7 +72,7 @@ describe('updating lifetimes', () => {
|
||||
test('the :id parameter is not case sensitive', async () => {
|
||||
const lifetimeDays = 45;
|
||||
const { body } = await app.request
|
||||
.put(`/api/admin/feature-types/kIlL-SwItCh/lifetime`)
|
||||
.put('/api/admin/feature-types/kIlL-SwItCh/lifetime')
|
||||
.send({ lifetimeDays })
|
||||
.expect(200);
|
||||
|
||||
|
@ -318,8 +318,8 @@ test('Update update_at when setHealth runs', async () => {
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
let now = new Date().getTime();
|
||||
let updatedAt = new Date(res.body.updatedAt).getTime();
|
||||
const now = new Date().getTime();
|
||||
const updatedAt = new Date(res.body.updatedAt).getTime();
|
||||
expect(now - updatedAt).toBeLessThan(5000);
|
||||
});
|
||||
});
|
||||
|
@ -88,7 +88,7 @@ test('Trying to do operations on a non-existing feature yields 404', async () =>
|
||||
weight: 700,
|
||||
weightType: WeightType.VARIABLE,
|
||||
});
|
||||
let patch = jsonpatch.generate(observer);
|
||||
const patch = jsonpatch.generate(observer);
|
||||
await app.request
|
||||
.patch('/api/admin/projects/default/features/${featureName}/variants')
|
||||
.send(patch)
|
||||
@ -744,7 +744,7 @@ test('Patching with a fixed variant and variable variants splits remaining weigh
|
||||
.get(`/api/admin/projects/default/features/${featureName}/variants`)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
let body = res.body;
|
||||
const body = res.body;
|
||||
expect(body.variants).toHaveLength(7);
|
||||
expect(
|
||||
body.variants.reduce((total, v) => total + v.weight, 0),
|
||||
@ -817,7 +817,7 @@ test('Multiple fixed variants gets added together to decide how much weight vari
|
||||
.get(`/api/admin/projects/default/features/${featureName}/variants`)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
let body = res.body;
|
||||
const body = res.body;
|
||||
expect(body.variants).toHaveLength(3);
|
||||
expect(
|
||||
body.variants.find((v) => v.name === 'variant3').weight,
|
||||
@ -921,7 +921,7 @@ test('If sum of fixed variant weight equals 1000 variable variants gets weight 0
|
||||
.get(`/api/admin/projects/default/features/${featureName}/variants`)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
let body = res.body;
|
||||
const body = res.body;
|
||||
expect(body.variants).toHaveLength(4);
|
||||
expect(
|
||||
body.variants.find((v) => v.name === 'variant3').weight,
|
||||
|
@ -25,7 +25,7 @@ afterAll(async () => {
|
||||
});
|
||||
|
||||
const expireAt = (addDays: number = 7): Date => {
|
||||
let now = new Date();
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + addDays);
|
||||
return now;
|
||||
};
|
||||
|
@ -414,10 +414,10 @@ test(`should not show environment on feature toggle, when environment is disable
|
||||
.expect(200);
|
||||
|
||||
const result = body.environments;
|
||||
let dev = result.find((e) => e.name === 'development');
|
||||
const dev = result.find((e) => e.name === 'development');
|
||||
expect(dev).toBeTruthy();
|
||||
expect(dev.enabled).toBe(true);
|
||||
let prod = result.find((e) => e.name === 'production');
|
||||
const prod = result.find((e) => e.name === 'production');
|
||||
expect(prod).toBeTruthy();
|
||||
expect(prod.enabled).toBe(false);
|
||||
});
|
||||
|
@ -380,7 +380,7 @@ test('generates USER_UPDATED event', async () => {
|
||||
});
|
||||
|
||||
test('Anonymises name, username and email fields if anonymiseEventLog flag is set', async () => {
|
||||
let anonymisedApp = await setupAppWithCustomConfig(
|
||||
const anonymisedApp = await setupAppWithCustomConfig(
|
||||
stores,
|
||||
{ experimental: { flags: { anonymiseEventLog: true } } },
|
||||
db,
|
||||
@ -393,10 +393,10 @@ test('Anonymises name, username and email fields if anonymiseEventLog flag is se
|
||||
rootRole: editorRole.id,
|
||||
})
|
||||
.set('Content-Type', 'application/json');
|
||||
let response = await anonymisedApp.request.get(
|
||||
const response = await anonymisedApp.request.get(
|
||||
'/api/admin/user-admin/access',
|
||||
);
|
||||
let body = response.body;
|
||||
const body = response.body;
|
||||
expect(body.users[0].email).toEqual('aeb83743e@unleash.run');
|
||||
expect(body.users[0].name).toEqual('3a8b17647@unleash.run');
|
||||
expect(body.users[0].username).toEqual(''); // Not set, so anonymise should return the empty string.
|
||||
|
@ -9,7 +9,7 @@ let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
let patStore: IPatStore;
|
||||
|
||||
let tomorrow = new Date();
|
||||
const tomorrow = new Date();
|
||||
let firstSecret;
|
||||
let firstId;
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
@ -149,7 +149,7 @@ test('should get only current user PATs', async () => {
|
||||
test('should fail creation of PAT with passed expiry', async () => {
|
||||
const { request } = app;
|
||||
|
||||
let yesterday = new Date();
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
await request
|
||||
.post('/api/admin/user/tokens')
|
||||
|
@ -46,13 +46,15 @@ test('multiple slashes after base path is also rejected with 404', async () => {
|
||||
await appWithBaseUrl.request.get('/demo/api/client/features').expect(401);
|
||||
});
|
||||
|
||||
test(`Access with API token is granted`, async () => {
|
||||
let token = await app.services.apiTokenService.createApiTokenWithProjects({
|
||||
environment: 'default',
|
||||
projects: ['default'],
|
||||
tokenName: 'test',
|
||||
type: ApiTokenType.CLIENT,
|
||||
});
|
||||
test('Access with API token is granted', async () => {
|
||||
const token = await app.services.apiTokenService.createApiTokenWithProjects(
|
||||
{
|
||||
environment: 'default',
|
||||
projects: ['default'],
|
||||
tokenName: 'test',
|
||||
type: ApiTokenType.CLIENT,
|
||||
},
|
||||
);
|
||||
await app.request
|
||||
.get('/api/client/features')
|
||||
.set('Authorization', token.secret)
|
||||
|
@ -77,7 +77,7 @@ test('Can log in', async () => {
|
||||
});
|
||||
|
||||
test('Gets rate limited after 10 tries', async () => {
|
||||
for (let statusCode of [...Array(10).fill(200), 429]) {
|
||||
for (const statusCode of [...Array(10).fill(200), 429]) {
|
||||
await app.request
|
||||
.post('/auth/simple/login')
|
||||
.send({
|
||||
|
@ -380,8 +380,7 @@ test('should send all segments that are in use by feature', async () => {
|
||||
|
||||
const globalSegmentIds = globalSegments.map((segment) => segment.id);
|
||||
const allSegmentIds = clientFeatures.features
|
||||
.map((feat) => feat.strategies.map((strategy) => strategy.segments))
|
||||
.flat()
|
||||
.flatMap((feat) => feat.strategies.map((strategy) => strategy.segments))
|
||||
.flat()
|
||||
.filter((x) => !!x);
|
||||
const toggleSegmentIds = [...new Set(allSegmentIds)];
|
||||
|
@ -149,8 +149,7 @@ test('all tags are listed in the root "tags" list', async () => {
|
||||
// store other invalid tags that already exist on this
|
||||
// operation
|
||||
const preExistingTags =
|
||||
(invalidTags[path] ?? {})[operation]?.invalidTags ?? [];
|
||||
|
||||
invalidTags[path]?.[operation]?.invalidTags ?? [];
|
||||
// add information about the invalid tag to the invalid tags
|
||||
// dict.
|
||||
invalidTags = {
|
||||
|
@ -9,7 +9,7 @@ let db: ITestDb;
|
||||
let appErrorLogs: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit(`proxy_concurrency`, getLogger);
|
||||
db = await dbInit('proxy_concurrency', getLogger);
|
||||
const baseLogger = getLogger();
|
||||
const appLogger = {
|
||||
...baseLogger,
|
||||
|
@ -34,7 +34,9 @@ async function resetDatabase(knex) {
|
||||
knex.table('tag_types').del(),
|
||||
knex.table('addons').del(),
|
||||
knex.table('users').del(),
|
||||
knex.table('reset_tokens').del(),
|
||||
knex
|
||||
.table('reset_tokens')
|
||||
.del(),
|
||||
// knex.table('settings').del(),
|
||||
]);
|
||||
}
|
||||
@ -80,7 +82,7 @@ export interface ITestDb {
|
||||
}
|
||||
|
||||
export default async function init(
|
||||
databaseSchema: string = 'test',
|
||||
databaseSchema = 'test',
|
||||
getLogger: LogProvider = noLoggerProvider,
|
||||
configOverride: Partial<IUnleashOptions> = {},
|
||||
): Promise<ITestDb> {
|
||||
|
@ -55,7 +55,7 @@ const createUser = async (role?: number) => {
|
||||
return user;
|
||||
};
|
||||
|
||||
let groupIndex = 0;
|
||||
const groupIndex = 0;
|
||||
const createGroup = async ({
|
||||
users,
|
||||
role,
|
||||
@ -1837,7 +1837,7 @@ test('access overview should have admin access and default project for admin use
|
||||
const accessOverView: IUserAccessOverview[] =
|
||||
await accessService.getUserAccessOverview();
|
||||
const userAccess = accessOverView.find(
|
||||
(overviewRow) => overviewRow.userId == user.id,
|
||||
(overviewRow) => overviewRow.userId === user.id,
|
||||
)!;
|
||||
|
||||
expect(userAccess.userId).toBe(user.id);
|
||||
@ -1890,7 +1890,7 @@ test('access overview should have group access for groups that they are in', asy
|
||||
const accessOverView: IUserAccessOverview[] =
|
||||
await accessService.getUserAccessOverview();
|
||||
const userAccess = accessOverView.find(
|
||||
(overviewRow) => overviewRow.userId == user.id,
|
||||
(overviewRow) => overviewRow.userId === user.id,
|
||||
)!;
|
||||
|
||||
expect(userAccess.userId).toBe(user.id);
|
||||
|
@ -159,11 +159,11 @@ test('Setting an override disables all other envs', async () => {
|
||||
|
||||
const environments = await service.getAll();
|
||||
const targetedEnvironment = environments.find(
|
||||
(env) => env.name == enabledEnvName,
|
||||
(env) => env.name === enabledEnvName,
|
||||
);
|
||||
|
||||
const allOtherEnvironments = environments
|
||||
.filter((x) => x.name != enabledEnvName)
|
||||
.filter((x) => x.name !== enabledEnvName)
|
||||
.map((env) => env.enabled);
|
||||
|
||||
expect(targetedEnvironment?.enabled).toBe(true);
|
||||
@ -184,7 +184,7 @@ test('Passing an empty override does nothing', async () => {
|
||||
|
||||
const environments = await service.getAll();
|
||||
const targetedEnvironment = environments.find(
|
||||
(env) => env.name == enabledEnvName,
|
||||
(env) => env.name === enabledEnvName,
|
||||
);
|
||||
|
||||
expect(targetedEnvironment?.enabled).toBe(true);
|
||||
@ -255,11 +255,11 @@ test('Override works correctly when enabling default and disabling prod and dev'
|
||||
|
||||
const environments = await service.getAll();
|
||||
const targetedEnvironment = environments.find(
|
||||
(env) => env.name == defaultEnvironment,
|
||||
(env) => env.name === defaultEnvironment,
|
||||
);
|
||||
|
||||
const allOtherEnvironments = environments
|
||||
.filter((x) => x.name != defaultEnvironment)
|
||||
.filter((x) => x.name !== defaultEnvironment)
|
||||
.map((env) => env.enabled);
|
||||
const envNames = environments.map((x) => x.name);
|
||||
|
||||
|
@ -351,8 +351,8 @@ test('cloning a feature toggle copies variant environments correctly', async ()
|
||||
newToggleName,
|
||||
'default',
|
||||
clonedToggleName,
|
||||
true,
|
||||
'test-user',
|
||||
true,
|
||||
);
|
||||
|
||||
const clonedToggle =
|
||||
@ -379,8 +379,8 @@ test('cloning a feature toggle not allowed for change requests enabled', async (
|
||||
'newToggleName',
|
||||
'default',
|
||||
'clonedToggleName',
|
||||
true,
|
||||
'test-user',
|
||||
true,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new ForbiddenError(
|
||||
@ -407,7 +407,7 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
|
||||
const featureName = 'ToggleWithSegments';
|
||||
const clonedFeatureName = 'AWholeNewFeatureToggle';
|
||||
|
||||
let segment = await segmentService.create(
|
||||
const segment = await segmentService.create(
|
||||
{
|
||||
name: 'SomeSegment',
|
||||
constraints: mockConstraints(),
|
||||
@ -442,11 +442,13 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
|
||||
featureName,
|
||||
'default',
|
||||
clonedFeatureName,
|
||||
true,
|
||||
'test-user',
|
||||
true,
|
||||
);
|
||||
|
||||
let feature = await service.getFeature({ featureName: clonedFeatureName });
|
||||
const feature = await service.getFeature({
|
||||
featureName: clonedFeatureName,
|
||||
});
|
||||
expect(
|
||||
feature.environments.find((x) => x.name === 'default')?.strategies[0]
|
||||
.segments,
|
||||
|
@ -940,7 +940,7 @@ test('should change a users role in the project', async () => {
|
||||
|
||||
await projectService.addUser(project.id, member.id, projectUser.id, 'test');
|
||||
const { users } = await projectService.getAccessToProject(project.id);
|
||||
let memberUser = users.filter((u) => u.roleId === member.id);
|
||||
const memberUser = users.filter((u) => u.roleId === member.id);
|
||||
|
||||
expect(memberUser).toHaveLength(1);
|
||||
expect(memberUser[0].id).toBe(projectUser.id);
|
||||
@ -958,7 +958,7 @@ test('should change a users role in the project', async () => {
|
||||
'test',
|
||||
);
|
||||
|
||||
let { users: updatedUsers } = await projectService.getAccessToProject(
|
||||
const { users: updatedUsers } = await projectService.getAccessToProject(
|
||||
project.id,
|
||||
);
|
||||
const customUser = updatedUsers.filter((u) => u.roleId === customRole.id);
|
||||
|
@ -141,7 +141,7 @@ test('Should import variants from old format and convert to new format (per envi
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
let featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
expect(featureEnvironments).toHaveLength(6); // There are 3 environments enabled and 2 features
|
||||
expect(
|
||||
featureEnvironments
|
||||
@ -155,13 +155,13 @@ test('Should import variants in new format (per environment)', async () => {
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
let exportedJson = await stateService.export({});
|
||||
const exportedJson = await stateService.export({});
|
||||
await stateService.import({
|
||||
data: exportedJson,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
let featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
expect(featureEnvironments).toHaveLength(6); // 3 environments, 2 features === 6 rows
|
||||
});
|
||||
|
||||
|
@ -120,7 +120,7 @@ test('should not be able to login with deleted user', async () => {
|
||||
userService.loginUser('deleted_user', 'unleash4all'),
|
||||
).rejects.toThrow(
|
||||
new PasswordMismatch(
|
||||
`The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.`,
|
||||
'The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.',
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -139,7 +139,7 @@ test('should not be able to login without password_hash on user', async () => {
|
||||
userService.loginUser('deleted_user', 'anything-should-fail'),
|
||||
).rejects.toThrow(
|
||||
new PasswordMismatch(
|
||||
`The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.`,
|
||||
'The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.',
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -273,7 +273,7 @@ test('should throw if rootRole is wrong via SSO', async () => {
|
||||
name: 'some',
|
||||
autoCreate: true,
|
||||
}),
|
||||
).rejects.toThrow(new BadDataError(`Could not find rootRole=Member`));
|
||||
).rejects.toThrow(new BadDataError('Could not find rootRole=Member'));
|
||||
});
|
||||
|
||||
test('should update user name when signing in via SSO', async () => {
|
||||
@ -330,5 +330,5 @@ test('should throw if autoCreate is false via SSO', async () => {
|
||||
name: 'some',
|
||||
autoCreate: false,
|
||||
}),
|
||||
).rejects.toThrow(new NotFoundError(`No user found`));
|
||||
).rejects.toThrow(new NotFoundError('No user found'));
|
||||
});
|
||||
|
@ -192,7 +192,7 @@ test('Should get all events of type', async () => {
|
||||
await Promise.all(
|
||||
[0, 1, 2, 3, 4, 5].map(async (id) => {
|
||||
const event =
|
||||
id % 2 == 0
|
||||
id % 2 === 0
|
||||
? new FeatureCreatedEvent({
|
||||
project: data.project,
|
||||
featureName: data.name,
|
||||
|
@ -24,8 +24,8 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
test('Setting enabled to same as existing value returns 0', async () => {
|
||||
let envName = 'enabled-to-true';
|
||||
let featureName = 'enabled-to-true-feature';
|
||||
const envName = 'enabled-to-true';
|
||||
const featureName = 'enabled-to-true-feature';
|
||||
await environmentStore.create({
|
||||
name: envName,
|
||||
enabled: true,
|
||||
@ -47,8 +47,8 @@ test('Setting enabled to same as existing value returns 0', async () => {
|
||||
});
|
||||
|
||||
test('Setting enabled to not existing value returns 1', async () => {
|
||||
let envName = 'enabled-toggle';
|
||||
let featureName = 'enabled-toggle-feature';
|
||||
const envName = 'enabled-toggle';
|
||||
const featureName = 'enabled-toggle-feature';
|
||||
await environmentStore.create({
|
||||
name: envName,
|
||||
enabled: true,
|
||||
|
@ -112,6 +112,6 @@ test('should throw not found error if feature does not exist', async () => {
|
||||
test('Returns empty tag list for existing feature with no tags', async () => {
|
||||
const name = 'feature.with.no.tags';
|
||||
await featureToggleStore.create('default', { name });
|
||||
let tags = await featureTagStore.getAllTagsForFeature(name);
|
||||
const tags = await featureTagStore.getAllTagsForFeature(name);
|
||||
expect(tags).toHaveLength(0);
|
||||
});
|
||||
|
4
src/test/fixtures/fake-environment-store.ts
vendored
4
src/test/fixtures/fake-environment-store.ts
vendored
@ -11,7 +11,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
|
||||
environments: IEnvironment[] = [];
|
||||
|
||||
disable(environments: IEnvironment[]): Promise<void> {
|
||||
for (let env of this.environments) {
|
||||
for (const env of this.environments) {
|
||||
if (environments.map((e) => e.name).includes(env.name))
|
||||
env.enabled = false;
|
||||
}
|
||||
@ -19,7 +19,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
|
||||
}
|
||||
|
||||
enable(environments: IEnvironment[]): Promise<void> {
|
||||
for (let env of this.environments) {
|
||||
for (const env of this.environments) {
|
||||
if (environments.map((e) => e.name).includes(env.name))
|
||||
env.enabled = true;
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ import { IFavoriteFeaturesStore } from '../../lib/types';
|
||||
import { IFavoriteFeatureKey } from '../../lib/types/stores/favorite-features';
|
||||
import { IFavoriteFeature } from '../../lib/types/favorites';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
export default class FakeFavoriteFeaturesStore
|
||||
implements IFavoriteFeaturesStore
|
||||
{
|
||||
export default class FakeFavoriteFeaturesStore implements IFavoriteFeaturesStore {
|
||||
addFavoriteFeature(
|
||||
favorite: IFavoriteFeatureKey,
|
||||
): Promise<IFavoriteFeature> {
|
||||
|
@ -2,9 +2,7 @@ import { IFavoriteProjectsStore } from '../../lib/types';
|
||||
import { IFavoriteProjectKey } from '../../lib/types/stores/favorite-projects';
|
||||
import { IFavoriteProject } from '../../lib/types/favorites';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
export default class FakeFavoriteProjectsStore
|
||||
implements IFavoriteProjectsStore
|
||||
{
|
||||
export default class FakeFavoriteProjectsStore implements IFavoriteProjectsStore {
|
||||
addFavoriteProject(
|
||||
favorite: IFavoriteProjectKey,
|
||||
): Promise<IFavoriteProject> {
|
||||
|
@ -41,7 +41,9 @@ export default class FakeFeatureEnvironmentStore
|
||||
fe.featureName === featureName &&
|
||||
environments.includes(fe.environment),
|
||||
)
|
||||
.map((fe) => (fe.variants = variants));
|
||||
.forEach((fe) => {
|
||||
fe.variants = variants;
|
||||
});
|
||||
}
|
||||
|
||||
async delete(key: FeatureEnvironmentKey): Promise<void> {
|
||||
|
@ -198,8 +198,8 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
|
||||
}
|
||||
|
||||
async getAllVariants(): Promise<IFeatureEnvironment[]> {
|
||||
let features = await this.getAll();
|
||||
let variants = features.flatMap((feature) => ({
|
||||
const features = await this.getAll();
|
||||
const variants = features.flatMap((feature) => ({
|
||||
featureName: feature.name,
|
||||
environment: 'development',
|
||||
variants: feature.variants,
|
||||
@ -271,7 +271,9 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
|
||||
}
|
||||
|
||||
dropAllVariants(): Promise<void> {
|
||||
this.features.forEach((feature) => (feature.variants = []));
|
||||
this.features.forEach((feature) => {
|
||||
feature.variants = [];
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
2
src/test/fixtures/fake-role-store.ts
vendored
2
src/test/fixtures/fake-role-store.ts
vendored
@ -60,7 +60,7 @@ export default class FakeRoleStore implements IRoleStore {
|
||||
}
|
||||
|
||||
async getRoleByName(name: string): Promise<IRole> {
|
||||
return this.roles.find((r) => (r.name = name)) as IRole;
|
||||
return this.roles.find((r) => r.name === name) as IRole;
|
||||
}
|
||||
|
||||
getRolesForProject(projectId: string): Promise<IRole[]> {
|
||||
|
Loading…
Reference in New Issue
Block a user