Skip to content

Commit c68610a

Browse files
committed
Parameterize load on RC server template to enable passing in a template or templateJSON string
1 parent c7d4c6c commit c68610a

File tree

4 files changed

+157
-50
lines changed

4 files changed

+157
-50
lines changed

etc/firebase-admin.remote-config.api.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,10 @@ export type ServerConfig = {
175175

176176
// @public
177177
export interface ServerTemplate {
178-
cache: ServerTemplateData;
179178
evaluate(context?: EvaluationContext): ServerConfig;
180-
load(): Promise<void>;
179+
load(template?: ServerTemplateData | string): Promise<void>;
180+
// (undocumented)
181+
toJSON(): ServerTemplateData;
181182
}
182183

183184
// @public

src/remote-config/remote-config-api.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,12 +385,6 @@ export interface InitServerTemplateOptions extends GetServerTemplateOptions {
385385
* Represents a stateful abstraction for a Remote Config server template.
386386
*/
387387
export interface ServerTemplate {
388-
389-
/**
390-
* Cached {@link ServerTemplateData}.
391-
*/
392-
cache: ServerTemplateData;
393-
394388
/**
395389
* Evaluates the current template to produce a {@link ServerConfig}.
396390
*/
@@ -400,7 +394,12 @@ export interface ServerTemplate {
400394
* Fetches and caches the current active version of the
401395
* project's {@link ServerTemplate}.
402396
*/
403-
load(): Promise<void>;
397+
load(template?: ServerTemplateData | string): Promise<void>;
398+
399+
/**
400+
* @returns JSON representation of {@link ServerTemplateData}
401+
*/
402+
toJSON(): ServerTemplateData;
404403
}
405404

406405
/**

src/remote-config/remote-config.ts

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,28 @@ export class RemoteConfig {
196196
* Synchronously instantiates {@link ServerTemplate}.
197197
*/
198198
public initServerTemplate(options?: InitServerTemplateOptions): ServerTemplate {
199-
const template = new ServerTemplateImpl(
199+
let template = new ServerTemplateImpl(
200200
this.client, new ConditionEvaluator(), options?.defaultConfig);
201+
201202
if (options?.template) {
202-
// Check and instantiates the template via a json string
203-
if (isString(options?.template)) {
203+
if (isString(options.template)) {
204+
// Check and instantiates the template via a json string
204205
try {
205-
template.cache = new ServerTemplateDataImpl(JSON.parse(options?.template));
206+
template = new ServerTemplateImpl(
207+
this.client, new ConditionEvaluator(), options?.defaultConfig,
208+
new ServerTemplateDataImpl(JSON.parse(options?.template)));
206209
} catch (e) {
207210
throw new FirebaseRemoteConfigError(
208211
'invalid-argument',
209-
`Failed to parse the JSON string: ${options?.template}. ` + e
212+
`Failed to parse the JSON string: ${template}. ` + e
210213
);
211214
}
212215
} else {
213-
template.cache = options?.template;
216+
template = new ServerTemplateImpl(
217+
this.client, new ConditionEvaluator(), options?.defaultConfig, new ServerTemplateDataImpl(options?.template));
214218
}
215219
}
220+
216221
return template;
217222
}
218223
}
@@ -305,22 +310,42 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate {
305310
* Remote Config dataplane template data implementation.
306311
*/
307312
class ServerTemplateImpl implements ServerTemplate {
308-
public cache: ServerTemplateData;
313+
private cache: ServerTemplateData;
309314

310315
constructor(
311316
private readonly apiClient: RemoteConfigApiClient,
312317
private readonly conditionEvaluator: ConditionEvaluator,
313-
private readonly defaultConfig: ServerConfig = {}
314-
) { }
318+
private readonly defaultConfig: ServerConfig = {},
319+
template?: ServerTemplateData
320+
) {
321+
if (template) { this.cache = template; }
322+
}
315323

316324
/**
317325
* Fetches and caches the current active version of the project's {@link ServerTemplate}.
318326
*/
319-
public load(): Promise<void> {
320-
return this.apiClient.getServerTemplate()
321-
.then((template) => {
322-
this.cache = new ServerTemplateDataImpl(template);
323-
});
327+
public load(template?: ServerTemplateData | string): Promise<void> {
328+
if (template) {
329+
// Check and instantiates the template via a json string
330+
if (isString(template)) {
331+
try {
332+
this.cache = new ServerTemplateDataImpl(JSON.parse(template));
333+
} catch (e) {
334+
throw new FirebaseRemoteConfigError(
335+
'invalid-argument',
336+
`Failed to parse the JSON string: ${template}. ` + e
337+
);
338+
}
339+
} else {
340+
this.cache = template;
341+
}
342+
return Promise.resolve();
343+
} else {
344+
return this.apiClient.getServerTemplate()
345+
.then((template) => {
346+
this.cache = new ServerTemplateDataImpl(template);
347+
});
348+
}
324349
}
325350

326351
/**
@@ -400,6 +425,13 @@ class ServerTemplateImpl implements ServerTemplate {
400425
return new Proxy(mergedConfig, proxyHandler);
401426
}
402427

428+
/**
429+
* @returns JSON representation of the server template
430+
*/
431+
public toJSON(): ServerTemplateData {
432+
return this.cache;
433+
}
434+
403435
/**
404436
* Private helper method that coerces a parameter value string to the {@link ParameterValueType}.
405437
*/

test/unit/remote-config/remote-config.spec.ts

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -574,11 +574,12 @@ describe('RemoteConfig', () => {
574574

575575
return remoteConfig.getServerTemplate()
576576
.then((template) => {
577-
expect(template.cache.conditions.length).to.equal(1);
578-
expect(template.cache.conditions[0].name).to.equal('ios');
579-
expect(template.cache.etag).to.equal('etag-123456789012-5');
577+
const parsedTemplate = template.toJSON();
578+
expect(parsedTemplate.conditions.length).to.equal(1);
579+
expect(parsedTemplate.conditions[0].name).to.equal('ios');
580+
expect(parsedTemplate.etag).to.equal('etag-123456789012-5');
580581

581-
const version = template.cache.version!;
582+
const version = parsedTemplate.version!;
582583
expect(version.versionNumber).to.equal('86');
583584
expect(version.updateOrigin).to.equal('ADMIN_SDK_NODE');
584585
expect(version.updateType).to.equal('INCREMENTAL_UPDATE');
@@ -589,23 +590,22 @@ describe('RemoteConfig', () => {
589590
expect(version.updateTime).to.equal('Mon, 15 Jun 2020 16:45:03 GMT');
590591

591592
const key = 'holiday_promo_enabled';
592-
const p1 = template.cache.parameters[key];
593+
const p1 = parsedTemplate.parameters[key];
593594
expect(p1.defaultValue).deep.equals({ value: 'true' });
594595
expect(p1.conditionalValues).deep.equals({ ios: { useInAppDefault: true } });
595596
expect(p1.description).equals('this is a promo');
596597
expect(p1.valueType).equals('BOOLEAN');
597598

598-
const c = template.cache.conditions.find((c) => c.name === 'ios');
599+
const c = parsedTemplate.conditions.find((c) => c.name === 'ios');
599600
expect(c).to.be.not.undefined;
600601
const cond = c as NamedCondition;
601602
expect(cond.name).to.equal('ios');
602603

603-
const parsed = JSON.parse(JSON.stringify(template.cache));
604604
const expectedTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE);
605605
const expectedVersion = deepCopy(VERSION_INFO);
606606
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
607607
expectedTemplate.version = expectedVersion;
608-
expect(parsed).deep.equals(expectedTemplate);
608+
expect(parsedTemplate).deep.equals(expectedTemplate);
609609
});
610610
});
611611

@@ -649,7 +649,10 @@ describe('RemoteConfig', () => {
649649
}
650650
};
651651
const initializedTemplate = remoteConfig.initServerTemplate({ template });
652-
const parsed = JSON.parse(JSON.stringify(initializedTemplate.cache));
652+
const parsed = initializedTemplate.toJSON();
653+
const expectedVersion = deepCopy(VERSION_INFO);
654+
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
655+
template.version = expectedVersion as Version;
653656
expect(parsed).deep.equals(deepCopy(template));
654657
});
655658

@@ -666,7 +669,7 @@ describe('RemoteConfig', () => {
666669
};
667670
const templateJson = JSON.stringify(template);
668671
const initializedTemplate = remoteConfig.initServerTemplate({ template: templateJson });
669-
const parsed = JSON.parse(JSON.stringify(initializedTemplate.cache));
672+
const parsed = initializedTemplate.toJSON();
670673
const expectedVersion = deepCopy(VERSION_INFO);
671674
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
672675
template.version = expectedVersion as Version;
@@ -751,6 +754,78 @@ describe('RemoteConfig', () => {
751754

752755
describe('load', () => {
753756
const operationName = 'getServerTemplate';
757+
const INVALID_PARAMETERS: any[] = [null, '', 'abc', 1, true, []];
758+
const INVALID_CONDITIONS: any[] = [null, '', 'abc', 1, true, {}];
759+
let sourceTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE);
760+
761+
it('should set template when passed', () => {
762+
const template = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE) as ServerTemplateData;
763+
template.parameters = {
764+
dog_type: {
765+
defaultValue: {
766+
value: 'shiba'
767+
},
768+
description: 'Type of dog breed',
769+
valueType: 'STRING'
770+
}
771+
};
772+
const initializedTemplate = remoteConfig.initServerTemplate();
773+
initializedTemplate.load(template);
774+
const parsed = initializedTemplate.toJSON();
775+
const expectedVersion = deepCopy(VERSION_INFO);
776+
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
777+
template.version = expectedVersion as Version;
778+
expect(parsed).deep.equals(deepCopy(template));
779+
});
780+
781+
it('should set and instantiates template when json string is passed', () => {
782+
const template = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE) as ServerTemplateData;
783+
template.parameters = {
784+
dog_type: {
785+
defaultValue: {
786+
value: 'shiba'
787+
},
788+
description: 'Type of dog breed',
789+
valueType: 'STRING'
790+
}
791+
};
792+
const templateJson = JSON.stringify(template);
793+
const initializedTemplate = remoteConfig.initServerTemplate();
794+
initializedTemplate.load(templateJson);
795+
const parsed = initializedTemplate.toJSON();
796+
const expectedVersion = deepCopy(VERSION_INFO);
797+
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
798+
template.version = expectedVersion as Version;
799+
expect(parsed).deep.equals(deepCopy(template));
800+
});
801+
802+
it('should throw if template is an invalid JSON', () => {
803+
const jsonString = '{invalidJson: null}';
804+
const initializedTemplate = remoteConfig.initServerTemplate();
805+
expect(() => initializedTemplate.load(jsonString))
806+
.to.throw(/Failed to parse the JSON string: ([\D\w]*)\./);
807+
});
808+
809+
INVALID_PARAMETERS.forEach((invalidParameter) => {
810+
sourceTemplate.parameters = invalidParameter;
811+
it(`should throw if the parameters is ${JSON.stringify(invalidParameter)}`, () => {
812+
const jsonString = JSON.stringify(sourceTemplate);
813+
const initializedTemplate = remoteConfig.initServerTemplate();
814+
expect(() => initializedTemplate.load(jsonString))
815+
.to.throw(/Failed to parse the JSON string: ([\D\w]*)\./);
816+
});
817+
});
818+
819+
sourceTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE);
820+
INVALID_CONDITIONS.forEach((invalidConditions) => {
821+
sourceTemplate.conditions = invalidConditions;
822+
it(`should throw if the conditions is ${JSON.stringify(invalidConditions)}`, () => {
823+
const jsonString = JSON.stringify(sourceTemplate);
824+
const initializedTemplate = remoteConfig.initServerTemplate();
825+
expect(() => initializedTemplate.load(jsonString))
826+
.to.throw(/Failed to parse the JSON string: ([\D\w]*)\./);
827+
});
828+
});
754829

755830
it('should propagate API errors', () => {
756831
const stub = sinon
@@ -816,7 +891,7 @@ describe('RemoteConfig', () => {
816891
return remoteConfig.getServerTemplate()
817892
.then((template) => {
818893
// If parameters are not present in the response, we set it to an empty object.
819-
expect(template.cache.parameters).deep.equals({});
894+
expect(template.toJSON().parameters).deep.equals({});
820895
});
821896
});
822897

@@ -830,7 +905,7 @@ describe('RemoteConfig', () => {
830905
return remoteConfig.getServerTemplate()
831906
.then((template) => {
832907
// If conditions are not present in the response, we set it to an empty array.
833-
expect(template.cache.conditions).deep.equals([]);
908+
expect(template.toJSON().conditions).deep.equals([]);
834909
});
835910
});
836911

@@ -842,11 +917,11 @@ describe('RemoteConfig', () => {
842917

843918
return remoteConfig.getServerTemplate()
844919
.then((template) => {
845-
expect(template.cache.conditions.length).to.equal(1);
846-
expect(template.cache.conditions[0].name).to.equal('ios');
847-
expect(template.cache.etag).to.equal('etag-123456789012-5');
920+
expect(template.toJSON().conditions.length).to.equal(1);
921+
expect(template.toJSON().conditions[0].name).to.equal('ios');
922+
expect(template.toJSON().etag).to.equal('etag-123456789012-5');
848923

849-
const version = template.cache.version!;
924+
const version = template.toJSON().version!;
850925
expect(version.versionNumber).to.equal('86');
851926
expect(version.updateOrigin).to.equal('ADMIN_SDK_NODE');
852927
expect(version.updateType).to.equal('INCREMENTAL_UPDATE');
@@ -857,13 +932,13 @@ describe('RemoteConfig', () => {
857932
expect(version.updateTime).to.equal('Mon, 15 Jun 2020 16:45:03 GMT');
858933

859934
const key = 'holiday_promo_enabled';
860-
const p1 = template.cache.parameters[key];
935+
const p1 = template.toJSON().parameters[key];
861936
expect(p1.defaultValue).deep.equals({ value: 'true' });
862937
expect(p1.conditionalValues).deep.equals({ ios: { useInAppDefault: true } });
863938
expect(p1.description).equals('this is a promo');
864939
expect(p1.valueType).equals('BOOLEAN');
865940

866-
const c = template.cache.conditions.find((c) => c.name === 'ios');
941+
const c = template.toJSON().conditions.find((c) => c.name === 'ios');
867942
expect(c).to.be.not.undefined;
868943
const cond = c as NamedCondition;
869944
expect(cond.name).to.equal('ios');
@@ -883,7 +958,7 @@ describe('RemoteConfig', () => {
883958
}
884959
});
885960

886-
const parsed = JSON.parse(JSON.stringify(template.cache));
961+
const parsed = template.toJSON();
887962
const expectedTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE);
888963
const expectedVersion = deepCopy(VERSION_INFO);
889964
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
@@ -904,9 +979,9 @@ describe('RemoteConfig', () => {
904979

905980
return remoteConfig.getServerTemplate()
906981
.then((template) => {
907-
expect(template.cache.etag).to.equal('etag-123456789012-5');
982+
expect(template.toJSON().etag).to.equal('etag-123456789012-5');
908983

909-
const version = template.cache.version!;
984+
const version = template.toJSON().version!;
910985
expect(version.versionNumber).to.equal('86');
911986
expect(version.updateOrigin).to.equal('ADMIN_SDK_NODE');
912987
expect(version.updateType).to.equal('INCREMENTAL_UPDATE');
@@ -930,9 +1005,9 @@ describe('RemoteConfig', () => {
9301005

9311006
return remoteConfig.getServerTemplate()
9321007
.then((template) => {
933-
expect(template.cache.etag).to.equal('etag-123456789012-5');
1008+
expect(template.toJSON().etag).to.equal('etag-123456789012-5');
9341009

935-
const version = template.cache.version!;
1010+
const version = template.toJSON().version!;
9361011
expect(version.versionNumber).to.equal('86');
9371012
expect(version.updateOrigin).to.equal('ADMIN_SDK_NODE');
9381013
expect(version.updateType).to.equal('INCREMENTAL_UPDATE');
@@ -956,9 +1031,9 @@ describe('RemoteConfig', () => {
9561031

9571032
return remoteConfig.getServerTemplate()
9581033
.then((template) => {
959-
expect(template.cache.etag).to.equal('etag-123456789012-5');
1034+
expect(template.toJSON().etag).to.equal('etag-123456789012-5');
9601035

961-
const version = template.cache.version!;
1036+
const version = template.toJSON().version!;
9621037
expect(version.versionNumber).to.equal('86');
9631038
expect(version.updateOrigin).to.equal('ADMIN_SDK_NODE');
9641039
expect(version.updateType).to.equal('INCREMENTAL_UPDATE');
@@ -1173,7 +1248,7 @@ describe('RemoteConfig', () => {
11731248
},
11741249
}
11751250

1176-
template.cache = response as ServerTemplateData;
1251+
template.load(response as ServerTemplateData);
11771252

11781253
let config = template.evaluate();
11791254

@@ -1188,7 +1263,7 @@ describe('RemoteConfig', () => {
11881263
},
11891264
}
11901265

1191-
template.cache = response as ServerTemplateData;
1266+
template.load(response as ServerTemplateData);
11921267

11931268
config = template.evaluate();
11941269

0 commit comments

Comments
 (0)