mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 15:23:33 +00:00
Migrate provider service to state provider (#8173)
* Migrate existing provider data to StateProvider Migrate existing provider data to StateProvider * Rework the ProviderService to call StateProvider * Unit test the ProviderService * Update DI to reflect ProviderService's new args * Add ProviderService to logout chains across products * Remove provider related stateService methods * Update libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.spec.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Cover up a copy/paste job * Compare equality over entire array in a test --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { UserId } from "../../types/guid";
|
||||
import { ProviderData } from "../models/data/provider.data";
|
||||
import { Provider } from "../models/domain/provider";
|
||||
|
||||
export abstract class ProviderService {
|
||||
get: (id: string) => Promise<Provider>;
|
||||
getAll: () => Promise<Provider[]>;
|
||||
save: (providers: { [id: string]: ProviderData }) => Promise<any>;
|
||||
save: (providers: { [id: string]: ProviderData }, userId?: UserId) => Promise<any>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,56 @@
|
||||
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
|
||||
import { FakeActiveUserState } from "../../../spec/fake-state";
|
||||
import { Utils } from "../../platform/misc/utils";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { ProviderUserStatusType, ProviderUserType } from "../enums";
|
||||
import { ProviderData } from "../models/data/provider.data";
|
||||
import { Provider } from "../models/domain/provider";
|
||||
|
||||
import { PROVIDERS } from "./provider.service";
|
||||
import { PROVIDERS, ProviderService } from "./provider.service";
|
||||
|
||||
/**
|
||||
* It is easier to read arrays than records in code, but we store a record
|
||||
* in state. This helper methods lets us build provider arrays in tests
|
||||
* and easily map them to records before storing them in state.
|
||||
*/
|
||||
function arrayToRecord(input: ProviderData[]): Record<string, ProviderData> {
|
||||
if (input == null) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.fromEntries(input?.map((i) => [i.id, i]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a simple mock `ProviderData[]` array that can be used in tests
|
||||
* to populate state.
|
||||
* @param count The number of organizations to populate the list with. The
|
||||
* function returns undefined if this is less than 1. The default value is 1.
|
||||
* @param suffix A string to append to data fields on each provider.
|
||||
* This defaults to the index of the organization in the list.
|
||||
* @returns a `ProviderData[]` array that can be used to populate
|
||||
* stateProvider.
|
||||
*/
|
||||
function buildMockProviders(count = 1, suffix?: string): ProviderData[] {
|
||||
if (count < 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildMockProvider(id: string, name: string): ProviderData {
|
||||
const data = new ProviderData({} as any);
|
||||
data.id = id;
|
||||
data.name = name;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const mockProviders = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const s = suffix ? suffix + i.toString() : i.toString();
|
||||
mockProviders.push(buildMockProvider("provider" + s, "provider" + s));
|
||||
}
|
||||
|
||||
return mockProviders;
|
||||
}
|
||||
|
||||
describe("PROVIDERS key definition", () => {
|
||||
const sut = PROVIDERS;
|
||||
@@ -21,3 +70,75 @@ describe("PROVIDERS key definition", () => {
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ProviderService", () => {
|
||||
let providerService: ProviderService;
|
||||
|
||||
const fakeUserId = Utils.newGuid() as UserId;
|
||||
let fakeAccountService: FakeAccountService;
|
||||
let fakeStateProvider: FakeStateProvider;
|
||||
let fakeActiveUserState: FakeActiveUserState<Record<string, ProviderData>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
fakeAccountService = mockAccountServiceWith(fakeUserId);
|
||||
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
||||
fakeActiveUserState = fakeStateProvider.activeUser.getFake(PROVIDERS);
|
||||
providerService = new ProviderService(fakeStateProvider);
|
||||
});
|
||||
|
||||
describe("getAll()", () => {
|
||||
it("Returns an array of all providers stored in state", async () => {
|
||||
const mockData: ProviderData[] = buildMockProviders(5);
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const providers = await providerService.getAll();
|
||||
expect(providers).toHaveLength(5);
|
||||
expect(providers).toEqual(mockData.map((x) => new Provider(x)));
|
||||
});
|
||||
|
||||
it("Returns an empty array if no providers are found in state", async () => {
|
||||
const mockData: ProviderData[] = undefined;
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await providerService.getAll();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("get()", () => {
|
||||
it("Returns a single provider from state that matches the specified id", async () => {
|
||||
const mockData = buildMockProviders(5);
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await providerService.get(mockData[3].id);
|
||||
expect(result).toEqual(new Provider(mockData[3]));
|
||||
});
|
||||
|
||||
it("Returns undefined if the specified provider id is not found", async () => {
|
||||
const result = await providerService.get("this-provider-does-not-exist");
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("save()", () => {
|
||||
it("replaces the entire provider list in state for the active user", async () => {
|
||||
const originalData = buildMockProviders(10);
|
||||
fakeActiveUserState.nextState(arrayToRecord(originalData));
|
||||
|
||||
const newData = buildMockProviders(10, "newData");
|
||||
await providerService.save(arrayToRecord(newData));
|
||||
|
||||
const result = await providerService.getAll();
|
||||
|
||||
expect(result).toEqual(newData);
|
||||
expect(result).not.toEqual(originalData);
|
||||
});
|
||||
|
||||
// This is more or less a test for logouts
|
||||
it("can replace state with null", async () => {
|
||||
const originalData = buildMockProviders(2);
|
||||
fakeActiveUserState.nextState(arrayToRecord(originalData));
|
||||
await providerService.save(null);
|
||||
const result = await providerService.getAll();
|
||||
expect(result).toEqual([]);
|
||||
expect(result).not.toEqual(originalData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { StateService } from "../../platform/abstractions/state.service";
|
||||
import { KeyDefinition, PROVIDERS_DISK } from "../../platform/state";
|
||||
import { Observable, map, firstValueFrom } from "rxjs";
|
||||
|
||||
import { KeyDefinition, PROVIDERS_DISK, StateProvider } from "../../platform/state";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service";
|
||||
import { ProviderData } from "../models/data/provider.data";
|
||||
import { Provider } from "../models/domain/provider";
|
||||
@@ -8,32 +10,34 @@ export const PROVIDERS = KeyDefinition.record<ProviderData>(PROVIDERS_DISK, "pro
|
||||
deserializer: (obj: ProviderData) => obj,
|
||||
});
|
||||
|
||||
function mapToSingleProvider(providerId: string) {
|
||||
return map<Provider[], Provider>((providers) => providers?.find((p) => p.id === providerId));
|
||||
}
|
||||
|
||||
export class ProviderService implements ProviderServiceAbstraction {
|
||||
constructor(private stateService: StateService) {}
|
||||
constructor(private stateProvider: StateProvider) {}
|
||||
|
||||
private providers$(userId?: UserId): Observable<Provider[] | undefined> {
|
||||
return this.stateProvider
|
||||
.getUserState$(PROVIDERS, userId)
|
||||
.pipe(this.mapProviderRecordToArray());
|
||||
}
|
||||
|
||||
private mapProviderRecordToArray() {
|
||||
return map<Record<string, ProviderData>, Provider[]>((providers) =>
|
||||
Object.values(providers ?? {})?.map((o) => new Provider(o)),
|
||||
);
|
||||
}
|
||||
|
||||
async get(id: string): Promise<Provider> {
|
||||
const providers = await this.stateService.getProviders();
|
||||
// eslint-disable-next-line
|
||||
if (providers == null || !providers.hasOwnProperty(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Provider(providers[id]);
|
||||
return await firstValueFrom(this.providers$().pipe(mapToSingleProvider(id)));
|
||||
}
|
||||
|
||||
async getAll(): Promise<Provider[]> {
|
||||
const providers = await this.stateService.getProviders();
|
||||
const response: Provider[] = [];
|
||||
for (const id in providers) {
|
||||
// eslint-disable-next-line
|
||||
if (providers.hasOwnProperty(id)) {
|
||||
response.push(new Provider(providers[id]));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
return await firstValueFrom(this.providers$());
|
||||
}
|
||||
|
||||
async save(providers: { [id: string]: ProviderData }) {
|
||||
await this.stateService.setProviders(providers);
|
||||
async save(providers: { [id: string]: ProviderData }, userId?: UserId) {
|
||||
await this.stateProvider.setUserState(PROVIDERS, providers, userId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user