From 56d9dfb2be70862b5d435dff89225cd3701bf8fb Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 20 Mar 2020 17:13:29 -0400 Subject: [PATCH] Add method to create a template from JSON string --- src/remote-config/remote-config.ts | 31 ++++++- test/unit/remote-config/remote-config.spec.ts | 80 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 853d6d757e..75bde011a4 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -96,6 +96,33 @@ export class RemoteConfig implements FirebaseServiceInterface { return new RemoteConfigTemplateImpl(templateResponse); }); } + + /** + * Creates and returns a new Remote Config template from a JSON string. + * + * @param {string} json The JSON string to populate a Remote Config template. + * + * @return {RemoteConfigTemplate} A new template instance. + */ + public createTemplateFromJSON(json: string): RemoteConfigTemplate { + if (!validator.isNonEmptyString(json)) { + throw new FirebaseRemoteConfigError( + 'invalid-argument', + 'JSON string must be a valid non-empty string'); + } + + let template: RemoteConfigTemplate; + try { + template = JSON.parse(json); + } catch (e) { + throw new FirebaseRemoteConfigError( + 'invalid-argument', + `Failed to parse the JSON string: ${json}. ` + e + ); + } + + return new RemoteConfigTemplateImpl(template); + } } /** @@ -121,7 +148,7 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate { if (!validator.isNonNullObject(config.parameters)) { throw new FirebaseRemoteConfigError( 'invalid-argument', - `Remote Config parameters must be a non-null object`); + 'Remote Config parameters must be a non-null object'); } this.parameters = config.parameters; } else { @@ -132,7 +159,7 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate { if (!validator.isArray(config.conditions)) { throw new FirebaseRemoteConfigError( 'invalid-argument', - `Remote Config conditions must be an array`); + 'Remote Config conditions must be an array'); } this.conditions = config.conditions; } else { diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index 3464db9b3d..aaa7505fac 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -419,4 +419,84 @@ describe('RemoteConfig', () => { }); }); }); + + describe('createTemplateFromJSON', () => { + const INVALID_STRINGS: any[] = [null, undefined, '', 1, true, {}, []]; + const INVALID_JSON_STRINGS: any[] = ['abc', 'foo', 'a:a', '1:1']; + const INVALID_PARAMETERS: any[] = [null, '', 'abc', 1, true, []]; + const INVALID_CONDITIONS: any[] = [null, '', 'abc', 1, true, {}]; + + INVALID_STRINGS.forEach((invalidJson) => { + it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { + expect(() => remoteConfig.createTemplateFromJSON(invalidJson)) + .to.throw('JSON string must be a valid non-empty string'); + }); + }); + + INVALID_JSON_STRINGS.forEach((invalidJson) => { + it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { + expect(() => remoteConfig.createTemplateFromJSON(invalidJson)) + .to.throw(/^Failed to parse the JSON string: ([\D\w]*)\. SyntaxError: Unexpected token ([\D\w]*) in JSON at position ([0-9]*)$/); + }); + }); + + const invalidEtags = [...INVALID_STRINGS]; + let sourceTemplate = deepCopy(REMOTE_CONFIG_RESPONSE); + invalidEtags.forEach((invalidEtag) => { + sourceTemplate.etag = invalidEtag; + const jsonString = JSON.stringify(sourceTemplate); + it(`should throw if the ETag is ${JSON.stringify(invalidEtag)}`, () => { + expect(() => remoteConfig.createTemplateFromJSON(jsonString)) + .to.throw(`Invalid Remote Config template response: ${jsonString}`); + }); + }); + + sourceTemplate = deepCopy(REMOTE_CONFIG_RESPONSE); + INVALID_PARAMETERS.forEach((invalidParameter) => { + sourceTemplate.parameters = invalidParameter; + const jsonString = JSON.stringify(sourceTemplate); + it(`should throw if the parameters is ${JSON.stringify(invalidParameter)}`, () => { + expect(() => remoteConfig.createTemplateFromJSON(jsonString)) + .to.throw('Remote Config parameters must be a non-null object'); + }); + }); + + sourceTemplate = deepCopy(REMOTE_CONFIG_RESPONSE); + INVALID_CONDITIONS.forEach((invalidConditions) => { + sourceTemplate.conditions = invalidConditions; + const jsonString = JSON.stringify(sourceTemplate); + it(`should throw if the conditions is ${JSON.stringify(invalidConditions)}`, () => { + expect(() => remoteConfig.createTemplateFromJSON(jsonString)) + .to.throw('Remote Config conditions must be an array'); + }); + }); + + it('should succeed when a valid json string is provided', () => { + const jsonString = JSON.stringify(REMOTE_CONFIG_RESPONSE); + const newTemplate = remoteConfig.createTemplateFromJSON(jsonString); + expect(newTemplate.conditions.length).to.equal(1); + expect(newTemplate.conditions[0].name).to.equal('ios'); + expect(newTemplate.conditions[0].expression).to.equal('device.os == \'ios\''); + expect(newTemplate.conditions[0].tagColor).to.equal('BLUE'); + // verify that the etag is unchanged + expect(newTemplate.etag).to.equal('etag-123456789012-5'); + // verify that the etag is read-only + expect(() => { + (newTemplate as any).etag = "new-etag"; + }).to.throw('Cannot set property etag of # which has only a getter'); + + const key = 'holiday_promo_enabled'; + const p1 = newTemplate.parameters[key]; + expect(p1.defaultValue).deep.equals({ value: 'true' }); + expect(p1.conditionalValues).deep.equals({ ios: { useInAppDefault: true } }); + expect(p1.description).equals('this is a promo'); + + const c = newTemplate.conditions.find((c) => c.name === 'ios'); + expect(c).to.be.not.undefined; + const cond = c as RemoteConfigCondition; + expect(cond.name).to.equal('ios'); + expect(cond.expression).to.equal('device.os == \'ios\''); + expect(cond.tagColor).to.equal('BLUE'); + }); + }); });