Skip to content

Commit 5d43520

Browse files
authored
Realtime RC test cases (#9210)
Added unit test cases.
1 parent 3eee5f8 commit 5d43520

File tree

2 files changed

+1017
-1
lines changed

2 files changed

+1017
-1
lines changed

packages/remote-config/test/api.test.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
import { expect } from 'chai';
1919
import {
20+
ConfigUpdateObserver,
2021
ensureInitialized,
2122
fetchAndActivate,
2223
FetchResponse,
2324
getRemoteConfig,
24-
getString
25+
getString,
26+
onConfigUpdate
2527
} from '../src';
2628
import '../test/setup';
2729
import {
@@ -34,6 +36,8 @@ import * as sinon from 'sinon';
3436
import { Component, ComponentType } from '@firebase/component';
3537
import { FirebaseInstallations } from '@firebase/installations-types';
3638
import { openDatabase, APP_NAMESPACE_STORE } from '../src/storage/storage';
39+
import { ERROR_FACTORY, ErrorCode } from '../src/errors';
40+
import { RemoteConfig as RemoteConfigImpl } from '../src/remote_config';
3741

3842
const fakeFirebaseConfig = {
3943
apiKey: 'api-key',
@@ -45,6 +49,12 @@ const fakeFirebaseConfig = {
4549
appId: '1:111:web:a1234'
4650
};
4751

52+
const mockObserver = {
53+
next: sinon.stub(),
54+
error: sinon.stub(),
55+
complete: sinon.stub()
56+
};
57+
4858
async function clearDatabase(): Promise<void> {
4959
const db = await openDatabase();
5060
db.transaction([APP_NAMESPACE_STORE], 'readwrite')
@@ -151,4 +161,99 @@ describe('Remote Config API', () => {
151161
await ensureInitialized(rc);
152162
expect(getString(rc, 'foobar')).to.equal('hello world');
153163
});
164+
165+
describe('onConfigUpdate', () => {
166+
let capturedObserver: ConfigUpdateObserver | undefined;
167+
let rc: RemoteConfigImpl;
168+
let addObserverStub: sinon.SinonStub;
169+
let removeObserverStub: sinon.SinonStub;
170+
171+
beforeEach(() => {
172+
rc = getRemoteConfig(app) as RemoteConfigImpl;
173+
174+
addObserverStub = sinon
175+
.stub(rc._realtimeHandler, 'addObserver')
176+
.resolves();
177+
removeObserverStub = sinon
178+
.stub(rc._realtimeHandler, 'removeObserver')
179+
.resolves();
180+
181+
addObserverStub.callsFake(async (observer: ConfigUpdateObserver) => {
182+
capturedObserver = observer;
183+
});
184+
});
185+
186+
afterEach(() => {
187+
capturedObserver = undefined;
188+
addObserverStub.restore();
189+
removeObserverStub.restore();
190+
});
191+
192+
it('should call addObserver on the internal realtimeHandler', async () => {
193+
await onConfigUpdate(rc, mockObserver);
194+
expect(addObserverStub).to.have.been.calledOnce;
195+
expect(addObserverStub).to.have.been.calledWith(mockObserver);
196+
});
197+
198+
it('should return an unsubscribe function', async () => {
199+
const unsubscribe = await onConfigUpdate(rc, mockObserver);
200+
expect(unsubscribe).to.be.a('function');
201+
});
202+
203+
it('returned unsubscribe function should call removeObserver', async () => {
204+
const unsubscribe = await onConfigUpdate(rc, mockObserver);
205+
206+
unsubscribe();
207+
expect(removeObserverStub).to.have.been.calledOnce;
208+
expect(removeObserverStub).to.have.been.calledWith(mockObserver);
209+
});
210+
211+
it('observer.next should be called when realtimeHandler propagates an update', async () => {
212+
await onConfigUpdate(rc, mockObserver);
213+
214+
if (capturedObserver && capturedObserver.next) {
215+
const mockConfigUpdate = { getUpdatedKeys: () => new Set(['new_key']) };
216+
capturedObserver.next(mockConfigUpdate);
217+
} else {
218+
expect.fail('Observer was not captured or next method is missing.');
219+
}
220+
221+
expect(mockObserver.next).to.have.been.calledOnce;
222+
expect(mockObserver.next).to.have.been.calledWithMatch({
223+
getUpdatedKeys: sinon.match.func
224+
});
225+
expect(
226+
mockObserver.next.getCall(0).args[0].getUpdatedKeys()
227+
).to.deep.equal(new Set(['new_key']));
228+
});
229+
230+
it('observer.error should be called when realtimeHandler propagates an error', async () => {
231+
await onConfigUpdate(rc, mockObserver);
232+
233+
if (capturedObserver && capturedObserver.error) {
234+
const expectedOriginalErrorMessage = 'Realtime stream error';
235+
const mockError = ERROR_FACTORY.create(
236+
ErrorCode.CONFIG_UPDATE_STREAM_ERROR,
237+
{
238+
originalErrorMessage: expectedOriginalErrorMessage
239+
}
240+
);
241+
capturedObserver.error(mockError);
242+
} else {
243+
expect.fail('Observer was not captured or error method is missing.');
244+
}
245+
246+
expect(mockObserver.error).to.have.been.calledOnce;
247+
const receivedError = mockObserver.error.getCall(0).args[0];
248+
249+
expect(receivedError.message).to.equal(
250+
'Remote Config: The stream was not able to connect to the backend: Realtime stream error. (remoteconfig/stream-error).'
251+
);
252+
expect(receivedError).to.have.nested.property(
253+
'customData.originalErrorMessage',
254+
'Realtime stream error'
255+
);
256+
expect((receivedError as any).code).to.equal('remoteconfig/stream-error');
257+
});
258+
});
154259
});

0 commit comments

Comments
 (0)