mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
fix: handle objects in top-level context in playground (#6773)
Don't include invalid context properties in the contexts that we evaluate. This PR removes any non-`properties` fields that have a non-string value. This prevents the front end from crashing when trying to render an object. Expect follow-up PRs to include more warnings/diagnostics we can show to the end user to inform them of what fields have been removed and why.
This commit is contained in:
parent
770155d46f
commit
ac6c05def1
35
src/lib/features/playground/clean-context.test.ts
Normal file
35
src/lib/features/playground/clean-context.test.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { cleanContext } from './clean-context';
|
||||||
|
|
||||||
|
test('strips invalid context properties from the context', async () => {
|
||||||
|
const invalidJsonTypes = {
|
||||||
|
object: {},
|
||||||
|
array: [],
|
||||||
|
true: true,
|
||||||
|
false: false,
|
||||||
|
number: 123,
|
||||||
|
null: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const validValues = {
|
||||||
|
appName: 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputContext = {
|
||||||
|
...invalidJsonTypes,
|
||||||
|
...validValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanedContext = cleanContext(inputContext);
|
||||||
|
|
||||||
|
expect(cleanedContext).toStrictEqual(validValues);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("doesn't add non-existing properties", async () => {
|
||||||
|
const input = {
|
||||||
|
appName: 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = cleanContext(input);
|
||||||
|
|
||||||
|
expect(output).toStrictEqual(input);
|
||||||
|
});
|
16
src/lib/features/playground/clean-context.ts
Normal file
16
src/lib/features/playground/clean-context.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { SdkContextSchema } from '../../openapi';
|
||||||
|
|
||||||
|
export const cleanContext = (context: SdkContextSchema): SdkContextSchema => {
|
||||||
|
const { appName, ...otherContextFields } = context;
|
||||||
|
|
||||||
|
const cleanedContextFields = Object.fromEntries(
|
||||||
|
Object.entries(otherContextFields).filter(
|
||||||
|
([key, value]) => key === 'properties' || typeof value === 'string',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cleanedContextFields,
|
||||||
|
appName,
|
||||||
|
};
|
||||||
|
};
|
78
src/lib/features/playground/playground-api.e2e.test.ts
Normal file
78
src/lib/features/playground/playground-api.e2e.test.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
|
||||||
|
import {
|
||||||
|
type IUnleashTest,
|
||||||
|
setupApp,
|
||||||
|
} from '../../../test/e2e/helpers/test-helper';
|
||||||
|
import type { IUnleashStores } from '../../types';
|
||||||
|
import getLogger from '../../../test/fixtures/no-logger';
|
||||||
|
|
||||||
|
let stores: IUnleashStores;
|
||||||
|
let db: ITestDb;
|
||||||
|
let app: IUnleashTest;
|
||||||
|
|
||||||
|
const flag = {
|
||||||
|
name: 'test-flag',
|
||||||
|
enabled: true,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
createdByUserId: 9999,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
db = await dbInit('playground_api', getLogger);
|
||||||
|
stores = db.stores;
|
||||||
|
|
||||||
|
await stores.featureToggleStore.create('default', flag);
|
||||||
|
|
||||||
|
app = await setupApp(stores);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await db.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strips invalid context properties from input before using it', async () => {
|
||||||
|
const validData = {
|
||||||
|
appName: 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputContext = {
|
||||||
|
invalid: {},
|
||||||
|
...validData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { body } = await app.request
|
||||||
|
.post('/api/admin/playground/advanced')
|
||||||
|
.send({
|
||||||
|
context: inputContext,
|
||||||
|
environments: ['production'],
|
||||||
|
projects: '*',
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const evaluatedContext =
|
||||||
|
body.features[0].environments.production[0].context;
|
||||||
|
|
||||||
|
expect(evaluatedContext).toStrictEqual(validData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns the input context exactly as it came in, even if invalid values have been removed for the evaluation', async () => {
|
||||||
|
const invalidData = {
|
||||||
|
invalid: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputContext = {
|
||||||
|
...invalidData,
|
||||||
|
appName: 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { body } = await app.request
|
||||||
|
.post('/api/admin/playground/advanced')
|
||||||
|
.send({
|
||||||
|
context: inputContext,
|
||||||
|
environments: ['production'],
|
||||||
|
projects: '*',
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body.input.context).toMatchObject(inputContext);
|
||||||
|
});
|
@ -28,6 +28,7 @@ import type { AdvancedPlaygroundEnvironmentFeatureSchema } from '../../openapi/s
|
|||||||
import { validateQueryComplexity } from './validateQueryComplexity';
|
import { validateQueryComplexity } from './validateQueryComplexity';
|
||||||
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
||||||
import { getDefaultVariant } from './feature-evaluator/variant';
|
import { getDefaultVariant } from './feature-evaluator/variant';
|
||||||
|
import { cleanContext } from './clean-context';
|
||||||
|
|
||||||
type EvaluationInput = {
|
type EvaluationInput = {
|
||||||
features: FeatureConfigurationClient[];
|
features: FeatureConfigurationClient[];
|
||||||
@ -124,7 +125,8 @@ export class PlaygroundService {
|
|||||||
this.resolveFeatures(filteredProjects, env),
|
this.resolveFeatures(filteredProjects, env),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const contexts = generateObjectCombinations(context);
|
|
||||||
|
const contexts = generateObjectCombinations(cleanContext(context));
|
||||||
|
|
||||||
validateQueryComplexity(
|
validateQueryComplexity(
|
||||||
environments.length,
|
environments.length,
|
||||||
|
Loading…
Reference in New Issue
Block a user