Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDKS-7670] flag set input validation improvements #164

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 2 additions & 45 deletions client/__tests__/treatmentsByFlagSets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const request = require('supertest');
const app = require('../../app');
const { expectError, expectErrorContaining, expectOkMultipleResults, getLongKey } = require('../../utils/testWrapper');
const { NULL_FLAG_SETS, EMPTY_FLAG_SETS } = require('../../utils/constants');
const { expectedGreenResults, expectedPurpleResults, expectedPinkResults } = require('../../utils/mocks');

jest.mock('node-fetch', () => {
return jest.fn().mockImplementation((url) => {
Expand Down Expand Up @@ -215,7 +216,7 @@ describe('get-treatments-by-sets', () => {

test('should be 400 if attributes is an invalid json (POST)', async (done) => {
const response = await request(app)
.post('/client/get-treatments-by-sets?key=test&set-name=my-experiment')
.post('/client/get-treatments-by-sets?key=test&flag-sets=my-experiment')
.set('Content-Type', 'application/json')
// eslint-disable-next-line no-useless-escape
.send('\|\\\"/regex/i') // Syntax error parsing the JSON.
Expand All @@ -225,50 +226,6 @@ describe('get-treatments-by-sets', () => {
done();
});

const expectedGreenResults = {
'test_green': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_green_config': {
treatment: 'on',
config: undefined,
},
};
const expectedPurpleResults = {
'test_purple': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_purple_config': {
treatment: 'on',
config: undefined,
},
};
const expectedPinkResults = {
'test_purple': {
treatment: 'on',
},
'test_green': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_purple_config': {
treatment: 'on',
config: undefined,
},
'test_green_config': {
treatment: 'on',
config: undefined,
},
};

test('should be 200 if is valid attributes (GET)', async (done) => {
const response = await request(app)
.get('/client/get-treatments-by-sets?key=key_green&flag-sets=set_green&attributes={"test":"test"}')
Expand Down
61 changes: 9 additions & 52 deletions client/__tests__/treatmentsWithConfigByFlagSets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const request = require('supertest');
const app = require('../../app');
const { expectError, expectErrorContaining, expectOkMultipleResults, getLongKey } = require('../../utils/testWrapper');
const { NULL_FLAG_SETS, EMPTY_FLAG_SETS } = require('../../utils/constants');
const { expectedGreenResultsWithConfig, expectedPurpleResultsWithConfig, expectedPinkResultsWithConfig } = require('../../utils/mocks');

jest.mock('node-fetch', () => {
return jest.fn().mockImplementation((url) => {
Expand Down Expand Up @@ -204,63 +205,19 @@ describe('get-treatments-with-config-by-sets', () => {
done();
});

const expectedGreenResults = {
'test_green': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_green_config': {
treatment: 'on',
config: '{"color":"green"}',
},
};
const expectedPurpleResults = {
'test_purple': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_purple_config': {
treatment: 'on',
config: '{"color":"purple"}',
},
};
const expectedPinkResults = {
'test_purple': {
treatment: 'on',
},
'test_green': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_purple_config': {
treatment: 'on',
config: '{"color":"purple"}',
},
'test_green_config': {
treatment: 'on',
config: '{"color":"green"}',
},
};

test('should be 200 if is valid attributes (GET)', async (done) => {
const response = await request(app)
.get('/client/get-treatments-with-config-by-sets?key=key_green&flag-sets=set_green&attributes={"test":"test"}')
.set('Authorization', 'key_green');
expectOkMultipleResults(response, 200, expectedGreenResults, 3);
expectOkMultipleResults(response, 200, expectedGreenResultsWithConfig, 3);
done();
});

test('should be 200 when attributes is null (GET)', async (done) => {
const response = await request(app)
.get('/client/get-treatments-with-config-by-sets?key=key_green&flag-sets=set_green')
.set('Authorization', 'key_green');
expectOkMultipleResults(response, 200, expectedGreenResults, 3);
expectOkMultipleResults(response, 200, expectedGreenResultsWithConfig, 3);
done();
});

Expand All @@ -269,7 +226,7 @@ describe('get-treatments-with-config-by-sets', () => {
.post('/client/get-treatments-with-config-by-sets?key=key_green&flag-sets=set_green')
.send({attributes: { test:'test' }})
.set('Authorization', 'key_green');
expectOkMultipleResults(response, 200, expectedGreenResults, 3);
expectOkMultipleResults(response, 200, expectedGreenResultsWithConfig, 3);
done();
});

Expand All @@ -278,7 +235,7 @@ describe('get-treatments-with-config-by-sets', () => {
.get('/client/get-treatments-with-config-by-sets?key=key_green&flag-sets=set_green&attributes={"test":"test"}')
.send(JSON.stringify({attributes: { test:'test' }}))
.set('Authorization', 'key_green');
expectOkMultipleResults(response, 200, expectedGreenResults, 3);
expectOkMultipleResults(response, 200, expectedGreenResultsWithConfig, 3);
done();
});

Expand All @@ -289,31 +246,31 @@ describe('get-treatments-with-config-by-sets', () => {
attributes: null,
})
.set('Authorization', 'key_green');
expectOkMultipleResults(response, 200, expectedGreenResults, 3);
expectOkMultipleResults(response, 200, expectedGreenResultsWithConfig, 3);
done();
});

test('should be 200 with multiple evaluation but evualuate configured flag sets', async (done) => {
const response = await request(app)
.get('/client/get-treatments-with-config-by-sets?key=key_green&flag-sets=set_green,set_purple,nonexistant-experiment')
.set('Authorization', 'key_green');
expectOkMultipleResults(response, 200, expectedGreenResults, 3);
expectOkMultipleResults(response, 200, expectedGreenResultsWithConfig, 3);
done();
});

test('should be 200 with multiple evaluation but evualuate configured flag sets', async (done) => {
const response = await request(app)
.get('/client/get-treatments-with-config-by-sets?key=key_purple&flag-sets=set_green,set_purple,nonexistant-experiment')
.set('Authorization', 'key_purple');
expectOkMultipleResults(response, 200, expectedPurpleResults, 3);
expectOkMultipleResults(response, 200, expectedPurpleResultsWithConfig, 3);
done();
});

test('should be 200 with multiple evaluation but evualuate configured flag sets', async (done) => {
const response = await request(app)
.get('/client/get-treatments-with-config-by-sets?key=key_purple&flag-sets=set_green,set_purple,nonexistant-experiment')
.set('Authorization', 'key_pink');
expectOkMultipleResults(response, 200, expectedPinkResults, 5);
expectOkMultipleResults(response, 200, expectedPinkResultsWithConfig, 5);
done();
});
});
4 changes: 2 additions & 2 deletions client/client.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const express = require('express');
const router = express.Router();
const keyValidator = require('../utils/inputValidation/key');
const splitValidator = require('../utils/inputValidation/split');
const flagSetValidator = require('../utils/inputValidation/flagSet');
const flagSetsValidator = require('../utils/inputValidation/flagSets');
const splitsValidator = require('../utils/inputValidation/splits');
const attributesValidator = require('../utils/inputValidation/attributes');
const trafficTypeValidator = require('../utils/inputValidation/trafficType');
Expand Down Expand Up @@ -86,7 +86,7 @@ const treatmentsValidation = (req, res, next) => {
const flagSetsValidation = (req, res, next) => {
const matchingKeyValidation = keyValidator(req.query.key, 'key');
const bucketingKeyValidation = req.query['bucketing-key'] !== undefined ? keyValidator(req.query['bucketing-key'], 'bucketing-key') : null;
const flagSetNameValidation = flagSetValidator(req.query['flag-sets']);
const flagSetNameValidation = flagSetsValidator(req.query['flag-sets']);
const attributesValidation = attributesValidator(req.query.attributes);

const error = parseValidators([matchingKeyValidation, bucketingKeyValidation, flagSetNameValidation, attributesValidation]);
Expand Down
3 changes: 3 additions & 0 deletions utils/constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/;

const EMPTY_FLAG_SETS = 'you passed an empty flag-sets, flag-sets must be a non-empty array.';
const NULL_FLAG_SETS = 'you passed a null or undefined flag-sets, flag-sets must be a non-empty array.';

module.exports = {
TRIMMABLE_SPACES_REGEX,
EMPTY_FLAG_SETS,
NULL_FLAG_SETS,
};
9 changes: 4 additions & 5 deletions utils/inputValidation/flagSet.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
const { NULL_FLAG_SETS, EMPTY_FLAG_SETS } = require('../constants');
const errorWrapper = require('./wrapper/error');
const okWrapper = require('./wrapper/ok');
const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/;
const { NULL_FLAG_SET, EMPTY_FLAG_SET, TRIMMABLE_SPACES_REGEX } = require('../constants');

const validateFlagSet = (maybeFlagSet) => {
// eslint-disable-next-line
if (maybeFlagSet == undefined) return errorWrapper(NULL_FLAG_SETS);
if (maybeFlagSet == undefined) return errorWrapper(NULL_FLAG_SET);

if (TRIMMABLE_SPACES_REGEX.test(maybeFlagSet)) {
console.log(`flag-sets "${maybeFlagSet}" has extra whitespace, trimming.`);
maybeFlagSet = maybeFlagSet.trim();
}
if (maybeFlagSet.length === 0) return errorWrapper(EMPTY_FLAG_SETS);
if (maybeFlagSet.length === 0) return errorWrapper(EMPTY_FLAG_SET);

return okWrapper(maybeFlagSet.split(','));
return okWrapper(maybeFlagSet);
};

module.exports = validateFlagSet;
28 changes: 28 additions & 0 deletions utils/inputValidation/flagSets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const errorWrapper = require('./wrapper/error');
const okWrapper = require('./wrapper/ok');
const lang = require('../lang');
const validateFlagSet = require('./flagSet');
const { EMPTY_FLAG_SETS, NULL_FLAG_SETS } = require('../constants');

const validateFlagSets = (maybeFlagSets) => {
// eslint-disable-next-line eqeqeq
if (maybeFlagSets == undefined) return errorWrapper(NULL_FLAG_SETS);

maybeFlagSets = maybeFlagSets.split(',');

if (maybeFlagSets.length > 0) {
let validatedArray = [];
mmelograno marked this conversation as resolved.
Show resolved Hide resolved
// Remove invalid values
maybeFlagSets.forEach(maybeFlagSet => {
const flagSetValidation = validateFlagSet(maybeFlagSet);
if (flagSetValidation.valid) validatedArray.push(flagSetValidation.value);
});

// Strip off duplicated values if we have valid flag sets then return
if (validatedArray.length) return okWrapper(lang.uniq(validatedArray));
}

return errorWrapper(EMPTY_FLAG_SETS);
};

module.exports = validateFlagSets;
2 changes: 1 addition & 1 deletion utils/inputValidation/split.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const errorWrapper = require('./wrapper/error');
const okWrapper = require('./wrapper/ok');
const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/;
const { TRIMMABLE_SPACES_REGEX } = require('../constants');

const validateSplit = (maybeSplit) => {
// eslint-disable-next-line eqeqeq
Expand Down
68 changes: 67 additions & 1 deletion utils/mocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,70 @@ const integrations = [{
type: 'GOOGLE_ANALYTICS_TO_SPLIT',
}];

module.exports = { core, scheduler, urls, startup, storage, sync, integrations, apiKeyMocksMap };
const expectedGreenResults = {
'test_green': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_green_config': {
treatment: 'on',
},
};
const expectedPurpleResults = {
'test_purple': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_purple_config': {
treatment: 'on',
},
};
const expectedPinkResults = {
...expectedGreenResults,
...expectedPurpleResults,
};

const expectedGreenResultsWithConfig = {
'test_green': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_green_config': {
treatment: 'on',
config: '{"color":"green"}',
},
};

const expectedPurpleResultsWithConfig = {
'test_purple': {
treatment: 'on',
},
'test_color': {
treatment: 'on',
},
'test_purple_config': {
treatment: 'on',
config: '{"color":"purple"}',
},
};

const expectedPinkResultsWithConfig = {
...expectedGreenResultsWithConfig,
...expectedPurpleResultsWithConfig,
};

module.exports = {
core, scheduler, urls, startup, storage, sync, integrations, apiKeyMocksMap,
expectedGreenResults,
expectedPurpleResults,
expectedPinkResults,
expectedGreenResultsWithConfig,
expectedPurpleResultsWithConfig,
expectedPinkResultsWithConfig,
};