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