Skip to content

Commit b8dc2ad

Browse files
[FSSDK-11034] project config test completion
1 parent ceb610e commit b8dc2ad

File tree

3 files changed

+150
-998
lines changed

3 files changed

+150
-998
lines changed

lib/project_config/project_config.spec.ts

Lines changed: 149 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { describe, it, expect, beforeEach, afterEach, vi, assert } from 'vitest';
16+
import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock } from 'vitest';
1717
import { forEach, cloneDeep } from 'lodash';
1818
import { sprintf } from '../utils/fns';
1919
import fns from '../utils/fns';
@@ -33,6 +33,7 @@ import {
3333
import exp from 'constants';
3434
import { VariableType } from '../shared_types';
3535
import { OptimizelyError } from '../error/optimizly_error';
36+
import { J } from 'vitest/dist/chunks/environment.0M5R1SX_.js';
3637

3738
const createLogger = (...args: any) => ({
3839
debug: () => {},
@@ -162,17 +163,17 @@ describe('createProjectConfig - feature management', () => {
162163
expect(configObj.rolloutIdMap).toEqual(testDatafile.datafileWithFeaturesExpectedData.rolloutIdMap);
163164
});
164165

165-
it('creates a variationVariableUsageMap from rollouts and experiments with features in the datafile', () => {
166+
it('should create a variationVariableUsageMap from rollouts and experiments with features in the datafile', () => {
166167
expect(configObj.variationVariableUsageMap).toEqual(
167168
testDatafile.datafileWithFeaturesExpectedData.variationVariableUsageMap
168169
);
169170
});
170171

171-
it('creates a featureKeyMap from features in the datafile', () => {
172+
it('should create a featureKeyMap from features in the datafile', () => {
172173
expect(configObj.featureKeyMap).toEqual(testDatafile.datafileWithFeaturesExpectedData.featureKeyMap);
173174
});
174175

175-
it('adds variations from rollout experiements to the variationKeyMap', () => {
176+
it('should add variations from rollout experiements to the variationKeyMap', () => {
176177
expect(configObj.variationIdMap['594032']).toEqual({
177178
variables: [
178179
{ value: 'true', id: '4919852825313280' },
@@ -232,7 +233,7 @@ describe('createProjectConfig - flag variations', () => {
232233
configObj = projectConfig.createProjectConfig(testDatafile.getTestDecideProjectConfig());
233234
});
234235

235-
it('it should populate flagVariationsMap correctly', function() {
236+
it('should populate flagVariationsMap correctly', function() {
236237
const allVariationsForFlag = configObj.flagVariationsMap;
237238
const feature1Variations = allVariationsForFlag.feature_1;
238239
const feature2Variations = allVariationsForFlag.feature_2;
@@ -595,7 +596,7 @@ describe('getVariableValueForVariation', () => {
595596
vi.restoreAllMocks();
596597
});
597598

598-
it('returns a value for a valid variation and variable', () => {
599+
it('should return a value for a valid variation and variable', () => {
599600
const variation = configObj.variationIdMap['594096'];
600601
let variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons;
601602
let result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger);
@@ -617,7 +618,7 @@ describe('getVariableValueForVariation', () => {
617618
expect(result).toBe('20.25');
618619
});
619620

620-
it('returns null for a null variation', () => {
621+
it('should return null for a null variation', () => {
621622
const variation = null;
622623
const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons;
623624
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -627,7 +628,7 @@ describe('getVariableValueForVariation', () => {
627628
expect(result).toBe(null);
628629
});
629630

630-
it('returns null for a null variable', () => {
631+
it('should return null for a null variable', () => {
631632
const variation = configObj.variationIdMap['594096'];
632633
const variable = null;
633634
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -637,7 +638,7 @@ describe('getVariableValueForVariation', () => {
637638
expect(result).toBe(null);
638639
});
639640

640-
it('returns null for a null variation and null variable', () => {
641+
it('should return null for a null variation and null variable', () => {
641642
const variation = null;
642643
const variable = null;
643644
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -647,7 +648,7 @@ describe('getVariableValueForVariation', () => {
647648
expect(result).toBe(null);
648649
});
649650

650-
it('returns null for a variation whose id is not in the datafile', () => {
651+
it('should return null for a variation whose id is not in the datafile', () => {
651652
const variation = {
652653
key: 'some_variation',
653654
id: '999999999999',
@@ -661,7 +662,7 @@ describe('getVariableValueForVariation', () => {
661662
expect(result).toBe(null);
662663
});
663664

664-
it('returns null if the variation does not have a value for this variable', () => {
665+
it('should return null if the variation does not have a value for this variable', () => {
665666
const variation = configObj.variationIdMap['595008']; // This variation has no variable values associated with it
666667
const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons;
667668
const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger);
@@ -687,7 +688,7 @@ describe('getTypeCastValue', () => {
687688
vi.restoreAllMocks();
688689
});
689690

690-
it('can cast a boolean', () => {
691+
it('should cast a boolean', () => {
691692
let result = projectConfig.getTypeCastValue(
692693
'true',
693694
FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType,
@@ -705,7 +706,7 @@ describe('getTypeCastValue', () => {
705706
expect(result).toBe(false);
706707
});
707708

708-
it('can cast an integer', () => {
709+
it('should cast an integer', () => {
709710
let result = projectConfig.getTypeCastValue(
710711
'50',
711712
FEATURE_VARIABLE_TYPES.INTEGER as VariableType,
@@ -731,7 +732,7 @@ describe('getTypeCastValue', () => {
731732
expect(result).toBe(0);
732733
});
733734

734-
it('can cast a double', () => {
735+
it('should cast a double', () => {
735736
let result = projectConfig.getTypeCastValue(
736737
'89.99',
737738
FEATURE_VARIABLE_TYPES.DOUBLE as VariableType,
@@ -765,7 +766,7 @@ describe('getTypeCastValue', () => {
765766
expect(result).toBe(10);
766767
});
767768

768-
it('can return a string unmodified', () => {
769+
it('should return a string unmodified', () => {
769770
const result = projectConfig.getTypeCastValue(
770771
'message',
771772
FEATURE_VARIABLE_TYPES.STRING as VariableType,
@@ -775,7 +776,7 @@ describe('getTypeCastValue', () => {
775776
expect(result).toBe('message');
776777
});
777778

778-
it('returns null and logs an error for an invalid boolean', () => {
779+
it('should return null and logs an error for an invalid boolean', () => {
779780
const result = projectConfig.getTypeCastValue(
780781
'notabool',
781782
FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType,
@@ -786,7 +787,7 @@ describe('getTypeCastValue', () => {
786787
expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notabool', 'boolean');
787788
});
788789

789-
it('returns null and logs an error for an invalid integer', () => {
790+
it('should return null and logs an error for an invalid integer', () => {
790791
const result = projectConfig.getTypeCastValue(
791792
'notanint',
792793
FEATURE_VARIABLE_TYPES.INTEGER as VariableType,
@@ -797,7 +798,7 @@ describe('getTypeCastValue', () => {
797798
expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notanint', 'integer');
798799
});
799800

800-
it('returns null and logs an error for an invalid double', () => {
801+
it('should return null and logs an error for an invalid double', () => {
801802
const result = projectConfig.getTypeCastValue(
802803
'notadouble',
803804
FEATURE_VARIABLE_TYPES.DOUBLE as VariableType,
@@ -878,21 +879,21 @@ describe('getExperimentAudienceConditions', () => {
878879
});
879880

880881
describe('isFeatureExperiment', () => {
881-
it('returns true for a feature test', () => {
882+
it('should return true for a feature test', () => {
882883
const config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures());
883884
const result = projectConfig.isFeatureExperiment(config, '594098'); // id of 'testing_my_feature'
884885

885886
expect(result).toBe(true);
886887
});
887888

888-
it('returns false for an A/B test', () => {
889+
it('should return false for an A/B test', () => {
889890
const config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig());
890891
const result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment'
891892

892893
expect(result).toBe(false);
893894
});
894895

895-
it('returns true for a feature test in a mutex group', () => {
896+
it('should return true for a feature test in a mutex group', () => {
896897
const config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig());
897898
let result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1'
898899

@@ -905,7 +906,7 @@ describe('isFeatureExperiment', () => {
905906
});
906907

907908
describe('getAudienceSegments', () => {
908-
it('returns all qualified segments from an audience', () => {
909+
it('should return all qualified segments from an audience', () => {
909910
const dummyQualifiedAudienceJson = {
910911
id: '13389142234',
911912
conditions: [
@@ -984,7 +985,7 @@ describe('integrations: with segments', () => {
984985
});
985986
});
986987

987-
describe('withoutSegments', () => {
988+
describe('integrations: without segments', () => {
988989
let config: ProjectConfig;
989990
beforeEach(() => {
990991
config = projectConfig.createProjectConfig(testDatafile.getOdpIntegratedConfigWithoutSegments());
@@ -1006,3 +1007,128 @@ describe('withoutSegments', () => {
10061007
expect(config.odpIntegrationConfig.odpConfig.segmentsToCheck).toEqual([]);
10071008
});
10081009
});
1010+
1011+
describe('without valid integration key', () => {
1012+
it('should throw an error when parsing the project config due to integrations not containing a key', () => {
1013+
const odpIntegratedConfigWithoutKey = testDatafile.getOdpIntegratedConfigWithoutKey();
1014+
1015+
expect(() => projectConfig.createProjectConfig(odpIntegratedConfigWithoutKey)).toThrowError(OptimizelyError);
1016+
});
1017+
});
1018+
1019+
describe('without integrations', () => {
1020+
let config: ProjectConfig;
1021+
1022+
beforeEach(() => {
1023+
const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments();
1024+
const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] };
1025+
config = projectConfig.createProjectConfig(noIntegrationsConfigWithSegments);
1026+
});
1027+
1028+
it('should convert integrations from the datafile into the project config', () => {
1029+
expect(config.integrations.length).toBe(0);
1030+
});
1031+
1032+
it('should populate odpIntegrationConfig', () => {
1033+
expect(config.odpIntegrationConfig.integrated).toBe(false);
1034+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1035+
// @ts-ignore
1036+
expect(config.odpIntegrationConfig.odpConfig).toBeUndefined();
1037+
});
1038+
});
1039+
1040+
describe('tryCreatingProjectConfig', () => {
1041+
let mockJsonSchemaValidator: Mock;
1042+
beforeEach(() => {
1043+
mockJsonSchemaValidator = vi.fn().mockReturnValue(true);
1044+
vi.spyOn(configValidator, 'validateDatafile').mockReturnValue(true);
1045+
vi.spyOn(logger, 'error');
1046+
});
1047+
1048+
afterEach(() => {
1049+
vi.restoreAllMocks();
1050+
});
1051+
1052+
it('returns a project config object created by createProjectConfig when all validation is applied and there are no errors', () => {
1053+
const configDatafile = {
1054+
foo: 'bar',
1055+
experiments: [{ key: 'a' }, { key: 'b' }],
1056+
};
1057+
1058+
vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(configDatafile);
1059+
1060+
const configObj = {
1061+
foo: 'bar',
1062+
experimentKeyMap: {
1063+
a: { key: 'a', variationKeyMap: {} },
1064+
b: { key: 'b', variationKeyMap: {} },
1065+
},
1066+
};
1067+
1068+
// stubJsonSchemaValidator.returns(true);
1069+
mockJsonSchemaValidator.mockReturnValueOnce(true);
1070+
1071+
const result = projectConfig.tryCreatingProjectConfig({
1072+
datafile: configDatafile,
1073+
jsonSchemaValidator: mockJsonSchemaValidator,
1074+
logger: logger,
1075+
});
1076+
1077+
expect(result).toMatchObject(configObj);
1078+
});
1079+
1080+
it('throws an error when validateDatafile throws', function() {
1081+
vi.spyOn(configValidator, 'validateDatafile').mockImplementationOnce(() => {
1082+
throw new Error();
1083+
});
1084+
mockJsonSchemaValidator.mockReturnValueOnce(true);
1085+
1086+
expect(() =>
1087+
projectConfig.tryCreatingProjectConfig({
1088+
datafile: { foo: 'bar' },
1089+
jsonSchemaValidator: mockJsonSchemaValidator,
1090+
logger: logger,
1091+
})
1092+
).toThrowError();
1093+
});
1094+
1095+
it('throws an error when jsonSchemaValidator.validate throws', function() {
1096+
vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(true);
1097+
mockJsonSchemaValidator.mockImplementationOnce(() => {
1098+
throw new Error();
1099+
})
1100+
1101+
expect(() =>
1102+
projectConfig.tryCreatingProjectConfig({
1103+
datafile: { foo: 'bar' },
1104+
jsonSchemaValidator: mockJsonSchemaValidator,
1105+
logger: logger,
1106+
})
1107+
).toThrowError();
1108+
});
1109+
1110+
it('skips json validation when jsonSchemaValidator is not provided', function() {
1111+
const configDatafile = {
1112+
foo: 'bar',
1113+
experiments: [{ key: 'a' }, { key: 'b' }],
1114+
};
1115+
1116+
vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(configDatafile);
1117+
1118+
const configObj = {
1119+
foo: 'bar',
1120+
experimentKeyMap: {
1121+
a: { key: 'a', variationKeyMap: {} },
1122+
b: { key: 'b', variationKeyMap: {} },
1123+
},
1124+
};
1125+
1126+
const result = projectConfig.tryCreatingProjectConfig({
1127+
datafile: configDatafile,
1128+
logger: logger,
1129+
});
1130+
1131+
expect(result).toMatchObject(configObj);
1132+
expect(logger.error).not.toHaveBeenCalled();
1133+
});
1134+
});

0 commit comments

Comments
 (0)