1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00
Files
browser/libs/common/src/platform/services/config/config.service.spec.ts
Oscar Hinton e767295c86 [PM-5979] Refactor EnvironmentService (#8040)
Refactor environment service to emit a single observable. This required significant changes to how the environment service behaves and tackles much of the tech debt planned for it.
2024-03-21 09:09:44 -07:00

212 lines
6.6 KiB
TypeScript

import { MockProxy, mock } from "jest-mock-extended";
import { ReplaySubject, skip, take } from "rxjs";
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
import { AuthService } from "../../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { UserId } from "../../../types/guid";
import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction";
import { ServerConfig } from "../../abstractions/config/server-config";
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
import { LogService } from "../../abstractions/log.service";
import { StateService } from "../../abstractions/state.service";
import { ServerConfigData } from "../../models/data/server-config.data";
import {
EnvironmentServerConfigResponse,
ServerConfigResponse,
ThirdPartyServerConfigResponse,
} from "../../models/response/server-config.response";
import { StateProvider } from "../../state";
import { ConfigService } from "./config.service";
describe("ConfigService", () => {
let stateService: MockProxy<StateService>;
let configApiService: MockProxy<ConfigApiServiceAbstraction>;
let authService: MockProxy<AuthService>;
let environmentService: MockProxy<EnvironmentService>;
let logService: MockProxy<LogService>;
let replaySubject: ReplaySubject<Environment>;
let stateProvider: StateProvider;
let serverResponseCount: number; // increments to track distinct responses received from server
// Observables will start emitting as soon as this is created, so only create it
// after everything is mocked
const configServiceFactory = () => {
const configService = new ConfigService(
stateService,
configApiService,
authService,
environmentService,
logService,
stateProvider,
);
configService.init();
return configService;
};
beforeEach(() => {
stateService = mock();
configApiService = mock();
authService = mock();
environmentService = mock();
logService = mock();
replaySubject = new ReplaySubject<Environment>(1);
const accountService = mockAccountServiceWith("0" as UserId);
stateProvider = new FakeStateProvider(accountService);
environmentService.environment$ = replaySubject.asObservable();
serverResponseCount = 1;
configApiService.get.mockImplementation(() =>
Promise.resolve(serverConfigResponseFactory("server" + serverResponseCount++)),
);
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it("Uses storage as fallback", (done) => {
const storedConfigData = serverConfigDataFactory("storedConfig");
stateService.getServerConfig.mockResolvedValueOnce(storedConfigData);
configApiService.get.mockRejectedValueOnce(new Error("Unable to fetch"));
const configService = configServiceFactory();
configService.serverConfig$.pipe(take(1)).subscribe((config) => {
expect(config).toEqual(new ServerConfig(storedConfigData));
expect(stateService.getServerConfig).toHaveBeenCalledTimes(1);
expect(stateService.setServerConfig).not.toHaveBeenCalled();
done();
});
configService.triggerServerConfigFetch();
});
it("Stream does not error out if fetch fails", (done) => {
const storedConfigData = serverConfigDataFactory("storedConfig");
stateService.getServerConfig.mockResolvedValueOnce(storedConfigData);
const configService = configServiceFactory();
configService.serverConfig$.pipe(skip(1), take(1)).subscribe((config) => {
try {
expect(config.gitHash).toEqual("server1");
done();
} catch (e) {
done(e);
}
});
configApiService.get.mockRejectedValueOnce(new Error("Unable to fetch"));
configService.triggerServerConfigFetch();
configApiService.get.mockResolvedValueOnce(serverConfigResponseFactory("server1"));
configService.triggerServerConfigFetch();
});
describe("Fetches config from server", () => {
beforeEach(() => {
stateService.getServerConfig.mockResolvedValueOnce(null);
});
it.each<number | jest.DoneCallback>([1, 2, 3])(
"after %p hour/s",
(hours: number, done: jest.DoneCallback) => {
const configService = configServiceFactory();
// skip previous hours (if any)
configService.serverConfig$.pipe(skip(hours - 1), take(1)).subscribe((config) => {
try {
expect(config.gitHash).toEqual("server" + hours);
expect(configApiService.get).toHaveBeenCalledTimes(hours);
done();
} catch (e) {
done(e);
}
});
const oneHourInMs = 1000 * 3600;
jest.advanceTimersByTime(oneHourInMs * hours + 1);
},
);
it("when environment URLs change", (done) => {
const configService = configServiceFactory();
configService.serverConfig$.pipe(take(1)).subscribe((config) => {
try {
expect(config.gitHash).toEqual("server1");
done();
} catch (e) {
done(e);
}
});
replaySubject.next(null);
});
it("when triggerServerConfigFetch() is called", (done) => {
const configService = configServiceFactory();
configService.serverConfig$.pipe(take(1)).subscribe((config) => {
try {
expect(config.gitHash).toEqual("server1");
done();
} catch (e) {
done(e);
}
});
configService.triggerServerConfigFetch();
});
});
it("Saves server config to storage when the user is logged in", (done) => {
stateService.getServerConfig.mockResolvedValueOnce(null);
authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked);
const configService = configServiceFactory();
configService.serverConfig$.pipe(take(1)).subscribe(() => {
try {
expect(stateService.setServerConfig).toHaveBeenCalledWith(
expect.objectContaining({ gitHash: "server1" }),
);
done();
} catch (e) {
done(e);
}
});
configService.triggerServerConfigFetch();
});
});
function serverConfigDataFactory(gitHash: string) {
return new ServerConfigData(serverConfigResponseFactory(gitHash));
}
function serverConfigResponseFactory(gitHash: string) {
return new ServerConfigResponse({
version: "myConfigVersion",
gitHash: gitHash,
server: new ThirdPartyServerConfigResponse({
name: "myThirdPartyServer",
url: "www.example.com",
}),
environment: new EnvironmentServerConfigResponse({
vault: "vault.example.com",
}),
featureStates: {
feat1: "off",
feat2: "on",
feat3: "off",
},
});
}