1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00
Files
browser/libs/common/src/platform/services/app-id.service.spec.ts
Matt Gibson 3154d21925 [PM-8285] Resolve app id race (#9501)
* Do not update appId if it is not null

* Prefer linear transformations to side-effect-based changes

This leaves us open to repeat emits due to updates, but distinct until changed stops those.

Tracker improvements are due to passed in observables with replay causing immediate emits when `expectingEmission`s. This converts to a cold observable that only emits when the tracked observable does _after_ subscribing.

* Prefer while

* PR review
2024-06-04 18:42:04 -04:00

120 lines
3.7 KiB
TypeScript

import { FakeGlobalState, FakeGlobalStateProvider, ObservableTracker } from "../../../spec";
import { Utils } from "../misc/utils";
import { ANONYMOUS_APP_ID_KEY, APP_ID_KEY, AppIdService } from "./app-id.service";
describe("AppIdService", () => {
let globalStateProvider: FakeGlobalStateProvider;
let appIdState: FakeGlobalState<string>;
let anonymousAppIdState: FakeGlobalState<string>;
let sut: AppIdService;
beforeEach(() => {
globalStateProvider = new FakeGlobalStateProvider();
appIdState = globalStateProvider.getFake(APP_ID_KEY);
anonymousAppIdState = globalStateProvider.getFake(ANONYMOUS_APP_ID_KEY);
sut = new AppIdService(globalStateProvider);
});
afterEach(() => {
jest.resetAllMocks();
});
describe("getAppId", () => {
it("returns the existing appId when it exists", async () => {
appIdState.stateSubject.next("existingAppId");
const appId = await sut.getAppId();
expect(appId).toBe("existingAppId");
});
it("creates a new appId only once", async () => {
appIdState.stateSubject.next(null);
const appIds: string[] = [];
const promises = [async () => appIds.push(await sut.getAppId())];
promises.push(async () => appIds.push(await sut.getAppId()));
await Promise.all(promises);
expect(appIds[0]).toBe(appIds[1]);
});
it.each([null, undefined])("returns a new appId when %s", async (value) => {
appIdState.stateSubject.next(value);
const appId = await sut.getAppId();
expect(appId).toMatch(Utils.guidRegex);
});
it.each([null, undefined])("stores the new guid when %s", async (value) => {
appIdState.stateSubject.next(value);
const appId = await sut.getAppId();
expect(appIdState.nextMock).toHaveBeenCalledWith(appId);
});
it("emits only once when creating a new appId", async () => {
appIdState.stateSubject.next(null);
const tracker = new ObservableTracker(sut.appId$);
const appId = await sut.getAppId();
expect(tracker.emissions).toEqual([appId]);
await expect(tracker.pauseUntilReceived(2, 50)).rejects.toThrow("Timeout exceeded");
});
});
describe("getAnonymousAppId", () => {
it("returns the existing appId when it exists", async () => {
anonymousAppIdState.stateSubject.next("existingAppId");
const appId = await sut.getAnonymousAppId();
expect(appId).toBe("existingAppId");
});
it("creates a new anonymousAppId only once", async () => {
anonymousAppIdState.stateSubject.next(null);
const appIds: string[] = [];
const promises = [async () => appIds.push(await sut.getAnonymousAppId())];
promises.push(async () => appIds.push(await sut.getAnonymousAppId()));
await Promise.all(promises);
expect(appIds[0]).toBe(appIds[1]);
});
it.each([null, undefined])("returns a new appId when it does not exist", async (value) => {
anonymousAppIdState.stateSubject.next(value);
const appId = await sut.getAnonymousAppId();
expect(appId).toMatch(Utils.guidRegex);
});
it.each([null, undefined])(
"stores the new guid when it an existing one is not found",
async (value) => {
anonymousAppIdState.stateSubject.next(value);
const appId = await sut.getAnonymousAppId();
expect(anonymousAppIdState.nextMock).toHaveBeenCalledWith(appId);
},
);
it("emits only once when creating a new anonymousAppId", async () => {
anonymousAppIdState.stateSubject.next(null);
const tracker = new ObservableTracker(sut.anonymousAppId$);
const appId = await sut.getAnonymousAppId();
expect(tracker.emissions).toEqual([appId]);
await expect(tracker.pauseUntilReceived(2, 50)).rejects.toThrow("Timeout exceeded");
});
});
});