Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
[SSRC] Add client internals and tests
  • Loading branch information
trekforever authored and erikeldridge committed Feb 13, 2024
commit ab077c4a8ba8e2a2b9fad7e4d6ad5f12ee8103e0
48 changes: 45 additions & 3 deletions src/remote-config/remote-config-api-client-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ import { PrefixedFirebaseError } from '../utils/error';
import * as utils from '../utils/index';
import * as validator from '../utils/validator';
import { deepCopy } from '../utils/deep-copy';
import { ListVersionsOptions, ListVersionsResult, RemoteConfigTemplate } from './remote-config-api';
import {
ListVersionsOptions,
ListVersionsResult,
RemoteConfigTemplate,
RemoteConfigServerTemplateData
} from './remote-config-api';

// Remote Config backend constants
const FIREBASE_REMOTE_CONFIG_V1_API = 'https://firebaseremoteconfig.googleapis.com/v1';
// Honors env param to enable URL override independent of binary change.
const FIREBASE_REMOTE_CONFIG_URL_BASE = process.env.FIREBASE_REMOTE_CONFIG_URL_BASE || 'https://firebaseremoteconfig.googleapis.com';
const FIREBASE_REMOTE_CONFIG_HEADERS = {
'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}`,
// There is a known issue in which the ETag is not properly returned in cases where the request
Expand Down Expand Up @@ -166,6 +172,24 @@ export class RemoteConfigApiClient {
});
}

public getServerTemplate(): Promise<RemoteConfigServerTemplateData> {
return this.getUrl()
.then((url) => {
const request: HttpRequestConfig = {
method: 'GET',
url: `${url}/templates/server`,
headers: FIREBASE_REMOTE_CONFIG_HEADERS
};
return this.httpClient.send(request);
})
.then((resp) => {
return this.toRemoteConfigServerTemplate(resp);
})
.catch((err) => {
throw this.toFirebaseError(err);
});
}

private sendPutRequest(template: RemoteConfigTemplate, etag: string, validateOnly?: boolean): Promise<HttpResponse> {
let path = 'remoteConfig';
if (validateOnly) {
Expand All @@ -191,7 +215,7 @@ export class RemoteConfigApiClient {
private getUrl(): Promise<string> {
return this.getProjectIdPrefix()
.then((projectIdPrefix) => {
return `${FIREBASE_REMOTE_CONFIG_V1_API}/${projectIdPrefix}`;
return `${FIREBASE_REMOTE_CONFIG_URL_BASE}/v1/${projectIdPrefix}`;
});
}

Expand Down Expand Up @@ -255,6 +279,24 @@ export class RemoteConfigApiClient {
};
}

/**
* Creates a RemoteConfigServerTemplate from the API response.
* If provided, customEtag is used instead of the etag returned in the API response.
*
* @param {HttpResponse} resp API response object.
* @param {string} customEtag A custom etag to replace the etag fom the API response (Optional).
*/
private toRemoteConfigServerTemplate(resp: HttpResponse, customEtag?: string): RemoteConfigServerTemplateData {
const etag = (typeof customEtag === 'undefined') ? resp.headers['etag'] : customEtag;
this.validateEtag(etag);
return {
conditions: resp.data.conditions,
parameters: resp.data.parameters,
etag,
version: resp.data.version,
};
}

/**
* Checks if the given RemoteConfigTemplate object is valid.
* The object must have valid parameters, parameter groups, conditions, and an etag.
Expand Down
36 changes: 34 additions & 2 deletions test/unit/remote-config/remote-config-api-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { getSdkVersion } from '../../../src/utils/index';
import {
RemoteConfigTemplate, Version, ListVersionsResult,
} from '../../../src/remote-config/index';
import { RemoteConfigServerTemplateData } from '../../../src/remote-config/remote-config-api';

const expect = chai.expect;

Expand Down Expand Up @@ -661,6 +662,36 @@ describe('RemoteConfigApiClient', () => {
});
});

describe('getServerTemplate', () => {
it('should reject when project id is not available', () => {
return clientWithoutProjectId.getServerTemplate()
.should.eventually.be.rejectedWith(noProjectId);
});

// // tests for api response validations
runEtagHeaderTests(() => apiClient.getServerTemplate());
runErrorResponseTests(() => apiClient.getServerTemplate());

it('should resolve with the latest template on success', () => {
const stub = sinon
.stub(HttpClient.prototype, 'send')
.resolves(utils.responseFrom(TEST_RESPONSE, 200, { etag: 'etag-123456789012-1' }));
stubs.push(stub);
return apiClient.getServerTemplate()
.then((resp) => {
expect(resp.conditions).to.deep.equal(TEST_RESPONSE.conditions);
expect(resp.parameters).to.deep.equal(TEST_RESPONSE.parameters);
expect(resp.etag).to.equal('etag-123456789012-1');
expect(resp.version).to.deep.equal(TEST_RESPONSE.version);
expect(stub).to.have.been.calledOnce.and.calledWith({
method: 'GET',
url: 'https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/templates/server',
headers: EXPECTED_HEADERS,
});
});
});
});

function runTemplateVersionNumberTests(rcOperation: (v: string | number) => any): void {
['', null, NaN, true, [], {}].forEach((invalidVersion) => {
it(`should reject if the versionNumber is: ${invalidVersion}`, () => {
Expand All @@ -677,7 +708,7 @@ describe('RemoteConfigApiClient', () => {
});
}

function runEtagHeaderTests(rcOperation: () => Promise<RemoteConfigTemplate>): void {
function runEtagHeaderTests(rcOperation: () => Promise<RemoteConfigTemplate | RemoteConfigServerTemplateData>): void {
it('should reject when the etag is not present in the response', () => {
const stub = sinon
.stub(HttpClient.prototype, 'send')
Expand All @@ -690,7 +721,8 @@ describe('RemoteConfigApiClient', () => {
});
}

function runErrorResponseTests(rcOperation: () => Promise<RemoteConfigTemplate | ListVersionsResult>): void {
function runErrorResponseTests(
rcOperation: () => Promise<RemoteConfigTemplate | RemoteConfigServerTemplateData | ListVersionsResult>): void {
it('should reject when a full platform error response is received', () => {
const stub = sinon
.stub(HttpClient.prototype, 'send')
Expand Down