1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 05:13:29 +00:00

[PM-24280] Remove account service from state (#15828)

* Introduce ActiveUserAccessor

* Use ActiveUserAccessor over AccountService

* Updates tests and testing utils to support ActiveUserAccessor

* Update all injection points

* Fix types test

* Use ternary instead
This commit is contained in:
Justin Baur
2025-07-31 09:09:14 -04:00
committed by GitHub
parent 9c8188875a
commit 4f9b2b618f
14 changed files with 118 additions and 104 deletions

View File

@@ -43,6 +43,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/default-active-user.accessor";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
@@ -591,7 +592,7 @@ export default class MainBackground {
this.singleUserStateProvider, this.singleUserStateProvider,
); );
this.activeUserStateProvider = new DefaultActiveUserStateProvider( this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService, new DefaultActiveUserAccessor(this.accountService),
this.singleUserStateProvider, this.singleUserStateProvider,
); );
this.derivedStateProvider = new InlineDerivedStateProvider(); this.derivedStateProvider = new InlineDerivedStateProvider();

View File

@@ -44,6 +44,7 @@ import {
} from "@bitwarden/common/auth/services/account.service"; } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/default-active-user.accessor";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation"; import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation";
import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service";
@@ -377,7 +378,7 @@ export class ServiceContainer {
); );
this.activeUserStateProvider = new DefaultActiveUserStateProvider( this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService, new DefaultActiveUserAccessor(this.accountService),
this.singleUserStateProvider, this.singleUserStateProvider,
); );

View File

@@ -9,6 +9,7 @@ import { Subject, firstValueFrom } from "rxjs";
import { SsoUrlService } from "@bitwarden/auth/common"; import { SsoUrlService } from "@bitwarden/auth/common";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/default-active-user.accessor";
import { ClientType } from "@bitwarden/common/enums"; import { ClientType } from "@bitwarden/common/enums";
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -170,7 +171,7 @@ export class Main {
); );
const activeUserStateProvider = new DefaultActiveUserStateProvider( const activeUserStateProvider = new DefaultActiveUserStateProvider(
accountService, new DefaultActiveUserAccessor(accountService),
singleUserStateProvider, singleUserStateProvider,
); );

View File

@@ -110,6 +110,7 @@ import { AccountServiceImplementation } from "@bitwarden/common/auth/services/ac
import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service"; import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/default-active-user.accessor";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation"; import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation";
@@ -232,6 +233,7 @@ import { StorageServiceProvider } from "@bitwarden/common/platform/services/stor
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { ValidationService } from "@bitwarden/common/platform/services/validation.service"; import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
import { import {
ActiveUserAccessor,
ActiveUserStateProvider, ActiveUserStateProvider,
DerivedStateProvider, DerivedStateProvider,
GlobalStateProvider, GlobalStateProvider,
@@ -1271,10 +1273,15 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultGlobalStateProvider, useClass: DefaultGlobalStateProvider,
deps: [StorageServiceProvider, LogService], deps: [StorageServiceProvider, LogService],
}), }),
safeProvider({
provide: ActiveUserAccessor,
useClass: DefaultActiveUserAccessor,
deps: [AccountServiceAbstraction],
}),
safeProvider({ safeProvider({
provide: ActiveUserStateProvider, provide: ActiveUserStateProvider,
useClass: DefaultActiveUserStateProvider, useClass: DefaultActiveUserStateProvider,
deps: [AccountServiceAbstraction, SingleUserStateProvider], deps: [ActiveUserAccessor, SingleUserStateProvider],
}), }),
safeProvider({ safeProvider({
provide: SingleUserStateProvider, provide: SingleUserStateProvider,

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { Observable, map, of, switchMap, take } from "rxjs"; import { BehaviorSubject, map, Observable, of, switchMap, take } from "rxjs";
import { import {
GlobalState, GlobalState,
@@ -16,11 +16,11 @@ import {
DeriveDefinition, DeriveDefinition,
DerivedStateProvider, DerivedStateProvider,
UserKeyDefinition, UserKeyDefinition,
ActiveUserAccessor,
} from "../src/platform/state"; } from "../src/platform/state";
import { UserId } from "../src/types/guid"; import { UserId } from "../src/types/guid";
import { DerivedStateDependencies } from "../src/types/state"; import { DerivedStateDependencies } from "../src/types/state";
import { FakeAccountService } from "./fake-account-service";
import { import {
FakeActiveUserState, FakeActiveUserState,
FakeDerivedState, FakeDerivedState,
@@ -28,6 +28,35 @@ import {
FakeSingleUserState, FakeSingleUserState,
} from "./fake-state"; } from "./fake-state";
export interface MinimalAccountService {
activeUserId: UserId | null;
activeAccount$: Observable<{ id: UserId } | null>;
}
export class FakeActiveUserAccessor implements MinimalAccountService, ActiveUserAccessor {
private _subject: BehaviorSubject<UserId | null>;
constructor(startingUser: UserId | null) {
this._subject = new BehaviorSubject(startingUser);
this.activeAccount$ = this._subject
.asObservable()
.pipe(map((id) => (id != null ? { id } : null)));
this.activeUserId$ = this._subject.asObservable();
}
get activeUserId(): UserId {
return this._subject.value;
}
activeUserId$: Observable<UserId>;
activeAccount$: Observable<{ id: UserId }>;
switch(user: UserId | null) {
this._subject.next(user);
}
}
export class FakeGlobalStateProvider implements GlobalStateProvider { export class FakeGlobalStateProvider implements GlobalStateProvider {
mock = mock<GlobalStateProvider>(); mock = mock<GlobalStateProvider>();
establishedMocks: Map<string, FakeGlobalState<unknown>> = new Map(); establishedMocks: Map<string, FakeGlobalState<unknown>> = new Map();
@@ -138,18 +167,18 @@ export class FakeSingleUserStateProvider implements SingleUserStateProvider {
} }
export class FakeActiveUserStateProvider implements ActiveUserStateProvider { export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
activeUserId$: Observable<UserId>; activeUserId$: Observable<UserId | null>;
states: Map<string, FakeActiveUserState<unknown>> = new Map(); states: Map<string, FakeActiveUserState<unknown>> = new Map();
constructor( constructor(
public accountService: FakeAccountService, public accountServiceAccessor: MinimalAccountService,
readonly updateSyncCallback?: ( readonly updateSyncCallback?: (
key: UserKeyDefinition<unknown>, key: UserKeyDefinition<unknown>,
userId: UserId, userId: UserId,
newValue: unknown, newValue: unknown,
) => Promise<void>, ) => Promise<void>,
) { ) {
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id)); this.activeUserId$ = accountServiceAccessor.activeAccount$.pipe(map((a) => a?.id));
} }
get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T> { get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
@@ -182,9 +211,13 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
} }
private buildFakeState<T>(userKeyDefinition: UserKeyDefinition<T>, initialValue?: T) { private buildFakeState<T>(userKeyDefinition: UserKeyDefinition<T>, initialValue?: T) {
const state = new FakeActiveUserState<T>(this.accountService, initialValue, async (...args) => { const state = new FakeActiveUserState<T>(
await this.updateSyncCallback?.(userKeyDefinition, ...args); this.accountServiceAccessor,
}); initialValue,
async (...args) => {
await this.updateSyncCallback?.(userKeyDefinition, ...args);
},
);
state.keyDefinition = userKeyDefinition; state.keyDefinition = userKeyDefinition;
return state; return state;
} }
@@ -256,14 +289,14 @@ export class FakeStateProvider implements StateProvider {
return this.derived.get(parentState$, deriveDefinition, dependencies); return this.derived.get(parentState$, deriveDefinition, dependencies);
} }
constructor(public accountService: FakeAccountService) {} constructor(private activeAccountAccessor: MinimalAccountService) {}
private distributeSingleUserUpdate( private distributeSingleUserUpdate(
key: UserKeyDefinition<unknown>, key: UserKeyDefinition<unknown>,
userId: UserId, userId: UserId,
newState: unknown, newState: unknown,
) { ) {
if (this.activeUser.accountService.activeUserId === userId) { if (this.activeUser.accountServiceAccessor.activeUserId === userId) {
const state = this.activeUser.getFake(key, { allowInit: false }); const state = this.activeUser.getFake(key, { allowInit: false });
state?.nextState(newState, { syncValue: false }); state?.nextState(newState, { syncValue: false });
} }
@@ -284,7 +317,7 @@ export class FakeStateProvider implements StateProvider {
this.distributeSingleUserUpdate.bind(this), this.distributeSingleUserUpdate.bind(this),
); );
activeUser: FakeActiveUserStateProvider = new FakeActiveUserStateProvider( activeUser: FakeActiveUserStateProvider = new FakeActiveUserStateProvider(
this.accountService, this.activeAccountAccessor,
this.distributeActiveUserUpdate.bind(this), this.distributeActiveUserUpdate.bind(this),
); );
derived: FakeDerivedStateProvider = new FakeDerivedStateProvider(); derived: FakeDerivedStateProvider = new FakeDerivedStateProvider();

View File

@@ -18,7 +18,7 @@ import { CombinedState, activeMarker } from "../src/platform/state/user-state";
import { UserId } from "../src/types/guid"; import { UserId } from "../src/types/guid";
import { DerivedStateDependencies } from "../src/types/state"; import { DerivedStateDependencies } from "../src/types/state";
import { FakeAccountService } from "./fake-account-service"; import { MinimalAccountService } from "./fake-state-provider";
const DEFAULT_TEST_OPTIONS: StateUpdateOptions<any, any> = { const DEFAULT_TEST_OPTIONS: StateUpdateOptions<any, any> = {
shouldUpdate: () => true, shouldUpdate: () => true,
@@ -177,7 +177,7 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
combinedState$: Observable<CombinedState<T>>; combinedState$: Observable<CombinedState<T>>;
constructor( constructor(
private accountService: FakeAccountService, private activeAccountAccessor: MinimalAccountService,
initialValue?: T, initialValue?: T,
updateSyncCallback?: (userId: UserId, newValue: T) => Promise<void>, updateSyncCallback?: (userId: UserId, newValue: T) => Promise<void>,
) { ) {
@@ -194,14 +194,10 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
this.state$ = this.combinedState$.pipe(map(([_userId, state]) => state)); this.state$ = this.combinedState$.pipe(map(([_userId, state]) => state));
} }
get userId() {
return this.accountService.activeUserId;
}
nextState(state: T | null, { syncValue }: { syncValue: boolean } = { syncValue: true }) { nextState(state: T | null, { syncValue }: { syncValue: boolean } = { syncValue: true }) {
this.stateSubject.next({ this.stateSubject.next({
syncValue, syncValue,
combinedState: [this.userId, state], combinedState: [this.activeAccountAccessor.activeUserId, state],
}); });
} }
@@ -216,12 +212,12 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
? await firstValueFrom(options.combineLatestWith.pipe(timeout(options.msTimeout))) ? await firstValueFrom(options.combineLatestWith.pipe(timeout(options.msTimeout)))
: null; : null;
if (!options.shouldUpdate(current, combinedDependencies)) { if (!options.shouldUpdate(current, combinedDependencies)) {
return [this.userId, current]; return [this.activeAccountAccessor.activeUserId, current];
} }
const newState = configureState(current, combinedDependencies); const newState = configureState(current, combinedDependencies);
this.nextState(newState); this.nextState(newState);
this.nextMock([this.userId, newState]); this.nextMock([this.activeAccountAccessor.activeUserId, newState]);
return [this.userId, newState]; return [this.activeAccountAccessor.activeUserId, newState];
} }
/** Tracks update values resolved by `FakeState.update` */ /** Tracks update values resolved by `FakeState.update` */

View File

@@ -0,0 +1,19 @@
import { map, Observable } from "rxjs";
import { UserId } from "@bitwarden/user-core";
import { ActiveUserAccessor } from "../../platform/state";
import { AccountService } from "../abstractions/account.service";
/**
* Implementation for Platform so they can avoid a direct dependency on AccountService. Not for general consumption.
*/
export class DefaultActiveUserAccessor implements ActiveUserAccessor {
constructor(private readonly accountService: AccountService) {
this.activeUserId$ = this.accountService.activeAccount$.pipe(
map((a) => (a != null ? a.id : null)),
);
}
activeUserId$: Observable<UserId | null>;
}

View File

@@ -0,0 +1,11 @@
import { Observable } from "rxjs";
import { UserId } from "@bitwarden/user-core";
export abstract class ActiveUserAccessor {
/**
* Returns a stream of the current active user for the application. The stream either emits the user id for that account
* or returns null if there is no current active user.
*/
abstract activeUserId$: Observable<UserId | null>;
}

View File

@@ -1,37 +0,0 @@
import { mock } from "jest-mock-extended";
import { mockAccountServiceWith, trackEmissions } from "../../../../spec";
import { UserId } from "../../../types/guid";
import { SingleUserStateProvider } from "../user-state.provider";
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
describe("DefaultActiveUserStateProvider", () => {
const singleUserStateProvider = mock<SingleUserStateProvider>();
const userId = "userId" as UserId;
const accountInfo = {
id: userId,
name: "name",
email: "email",
emailVerified: false,
};
const accountService = mockAccountServiceWith(userId, accountInfo);
let sut: DefaultActiveUserStateProvider;
beforeEach(() => {
sut = new DefaultActiveUserStateProvider(accountService, singleUserStateProvider);
});
afterEach(() => {
jest.resetAllMocks();
});
it("should track the active User id from account service", () => {
const emissions = trackEmissions(sut.activeUserId$);
accountService.activeAccountSubject.next(undefined);
accountService.activeAccountSubject.next(accountInfo);
expect(emissions).toEqual([userId, undefined, userId]);
});
});

View File

@@ -1,9 +1,9 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { Observable, distinctUntilChanged, map } from "rxjs"; import { Observable, distinctUntilChanged } from "rxjs";
import { AccountService } from "../../../auth/abstractions/account.service";
import { UserId } from "../../../types/guid"; import { UserId } from "../../../types/guid";
import { ActiveUserAccessor } from "../active-user.accessor";
import { UserKeyDefinition } from "../user-key-definition"; import { UserKeyDefinition } from "../user-key-definition";
import { ActiveUserState } from "../user-state"; import { ActiveUserState } from "../user-state";
import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider"; import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider";
@@ -14,11 +14,10 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
activeUserId$: Observable<UserId | undefined>; activeUserId$: Observable<UserId | undefined>;
constructor( constructor(
private readonly accountService: AccountService, private readonly activeAccountAccessor: ActiveUserAccessor,
private readonly singleUserStateProvider: SingleUserStateProvider, private readonly singleUserStateProvider: SingleUserStateProvider,
) { ) {
this.activeUserId$ = this.accountService.activeAccount$.pipe( this.activeUserId$ = this.activeAccountAccessor.activeUserId$.pipe(
map((account) => account?.id),
// To avoid going to storage when we don't need to, only get updates when there is a true change. // To avoid going to storage when we don't need to, only get updates when there is a true change.
distinctUntilChanged((a, b) => (a == null || b == null ? a == b : a === b)), // Treat null and undefined as equal distinctUntilChanged((a, b) => (a == null || b == null ? a == b : a === b)), // Treat null and undefined as equal
); );

View File

@@ -3,14 +3,13 @@
* @jest-environment ../shared/test.environment.ts * @jest-environment ../shared/test.environment.ts
*/ */
import { any, mock } from "jest-mock-extended"; import { any, mock } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, map, of, timeout } from "rxjs"; import { BehaviorSubject, firstValueFrom, of, timeout } from "rxjs";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { StorageServiceProvider } from "@bitwarden/storage-core"; import { StorageServiceProvider } from "@bitwarden/storage-core";
import { awaitAsync, trackEmissions } from "../../../../spec"; import { awaitAsync, trackEmissions } from "../../../../spec";
import { FakeStorageService } from "../../../../spec/fake-storage.service"; import { FakeStorageService } from "../../../../spec/fake-storage.service";
import { Account } from "../../../auth/abstractions/account.service";
import { UserId } from "../../../types/guid"; import { UserId } from "../../../types/guid";
import { LogService } from "../../abstractions/log.service"; import { LogService } from "../../abstractions/log.service";
import { StateDefinition } from "../state-definition"; import { StateDefinition } from "../state-definition";
@@ -48,7 +47,7 @@ describe("DefaultActiveUserState", () => {
const storageServiceProvider = mock<StorageServiceProvider>(); const storageServiceProvider = mock<StorageServiceProvider>();
const stateEventRegistrarService = mock<StateEventRegistrarService>(); const stateEventRegistrarService = mock<StateEventRegistrarService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
let activeAccountSubject: BehaviorSubject<Account | null>; let activeAccountSubject: BehaviorSubject<UserId | null>;
let singleUserStateProvider: DefaultSingleUserStateProvider; let singleUserStateProvider: DefaultSingleUserStateProvider;
@@ -64,11 +63,11 @@ describe("DefaultActiveUserState", () => {
logService, logService,
); );
activeAccountSubject = new BehaviorSubject<Account | null>(null); activeAccountSubject = new BehaviorSubject<UserId | null>(null);
userState = new DefaultActiveUserState( userState = new DefaultActiveUserState(
testKeyDefinition, testKeyDefinition,
activeAccountSubject.asObservable().pipe(map((a) => a?.id)), activeAccountSubject.asObservable(),
singleUserStateProvider, singleUserStateProvider,
); );
}); });
@@ -83,12 +82,7 @@ describe("DefaultActiveUserState", () => {
const changeActiveUser = async (id: string) => { const changeActiveUser = async (id: string) => {
const userId = makeUserId(id); const userId = makeUserId(id);
activeAccountSubject.next({ activeAccountSubject.next(userId);
id: userId,
email: `test${id}@example.com`,
emailVerified: false,
name: `Test User ${id}`,
});
await awaitAsync(); await awaitAsync();
}; };
@@ -588,7 +582,7 @@ describe("DefaultActiveUserState", () => {
}); });
it("does not await updates if the active user changes", async () => { it("does not await updates if the active user changes", async () => {
const initialUserId = (await firstValueFrom(activeAccountSubject)).id; const initialUserId = activeAccountSubject.value;
expect(initialUserId).toBe(userId); expect(initialUserId).toBe(userId);
trackEmissions(userState.state$); trackEmissions(userState.state$);
await awaitAsync(); // storage updates are behind a promise await awaitAsync(); // storage updates are behind a promise

View File

@@ -4,16 +4,16 @@
*/ */
import { Observable, of } from "rxjs"; import { Observable, of } from "rxjs";
import { UserId } from "@bitwarden/user-core";
import { awaitAsync, trackEmissions } from "../../../../spec"; import { awaitAsync, trackEmissions } from "../../../../spec";
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service";
import { import {
FakeActiveUserAccessor,
FakeActiveUserStateProvider, FakeActiveUserStateProvider,
FakeDerivedStateProvider, FakeDerivedStateProvider,
FakeGlobalStateProvider, FakeGlobalStateProvider,
FakeSingleUserStateProvider, FakeSingleUserStateProvider,
} from "../../../../spec/fake-state-provider"; } from "../../../../spec/fake-state-provider";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { UserId } from "../../../types/guid";
import { DeriveDefinition } from "../derive-definition"; import { DeriveDefinition } from "../derive-definition";
import { KeyDefinition } from "../key-definition"; import { KeyDefinition } from "../key-definition";
import { StateDefinition } from "../state-definition"; import { StateDefinition } from "../state-definition";
@@ -27,12 +27,12 @@ describe("DefaultStateProvider", () => {
let singleUserStateProvider: FakeSingleUserStateProvider; let singleUserStateProvider: FakeSingleUserStateProvider;
let globalStateProvider: FakeGlobalStateProvider; let globalStateProvider: FakeGlobalStateProvider;
let derivedStateProvider: FakeDerivedStateProvider; let derivedStateProvider: FakeDerivedStateProvider;
let accountService: FakeAccountService; let activeAccountAccessor: FakeActiveUserAccessor;
const userId = "fakeUserId" as UserId; const userId = "fakeUserId" as UserId;
beforeEach(() => { beforeEach(() => {
accountService = mockAccountServiceWith(userId); activeAccountAccessor = new FakeActiveUserAccessor(userId);
activeUserStateProvider = new FakeActiveUserStateProvider(accountService); activeUserStateProvider = new FakeActiveUserStateProvider(activeAccountAccessor);
singleUserStateProvider = new FakeSingleUserStateProvider(); singleUserStateProvider = new FakeSingleUserStateProvider();
globalStateProvider = new FakeGlobalStateProvider(); globalStateProvider = new FakeGlobalStateProvider();
derivedStateProvider = new FakeDerivedStateProvider(); derivedStateProvider = new FakeDerivedStateProvider();
@@ -70,12 +70,6 @@ describe("DefaultStateProvider", () => {
userId?: UserId, userId?: UserId,
) => Observable<string>, ) => Observable<string>,
) => { ) => {
const accountInfo = {
email: "email",
emailVerified: false,
name: "name",
status: AuthenticationStatus.LoggedOut,
};
const keyDefinition = new UserKeyDefinition<string>( const keyDefinition = new UserKeyDefinition<string>(
new StateDefinition("test", "disk"), new StateDefinition("test", "disk"),
"test", "test",
@@ -97,7 +91,7 @@ describe("DefaultStateProvider", () => {
}); });
it("should follow the current active user if no userId is provided", async () => { it("should follow the current active user if no userId is provided", async () => {
accountService.activeAccountSubject.next({ id: userId, ...accountInfo }); activeAccountAccessor.switch(userId);
const state = singleUserStateProvider.getFake(userId, keyDefinition); const state = singleUserStateProvider.getFake(userId, keyDefinition);
state.nextState("value"); state.nextState("value");
const emissions = trackEmissions(methodUnderTest(keyDefinition)); const emissions = trackEmissions(methodUnderTest(keyDefinition));
@@ -113,7 +107,7 @@ describe("DefaultStateProvider", () => {
state.nextState("value"); state.nextState("value");
const emissions = trackEmissions(methodUnderTest(keyDefinition)); const emissions = trackEmissions(methodUnderTest(keyDefinition));
accountService.activeAccountSubject.next({ id: "newUserId" as UserId, ...accountInfo }); activeAccountAccessor.switch("newUserId" as UserId);
const newUserEmissions = trackEmissions(sut.getUserState$(keyDefinition)); const newUserEmissions = trackEmissions(sut.getUserState$(keyDefinition));
state.nextState("value2"); state.nextState("value2");
state.nextState("value3"); state.nextState("value3");
@@ -125,12 +119,6 @@ describe("DefaultStateProvider", () => {
); );
describe("getUserState$", () => { describe("getUserState$", () => {
const accountInfo = {
email: "email",
emailVerified: false,
name: "name",
status: AuthenticationStatus.LoggedOut,
};
const keyDefinition = new UserKeyDefinition<string>( const keyDefinition = new UserKeyDefinition<string>(
new StateDefinition("test", "disk"), new StateDefinition("test", "disk"),
"test", "test",
@@ -141,7 +129,7 @@ describe("DefaultStateProvider", () => {
); );
it("should not emit any values until a truthy user id is supplied", async () => { it("should not emit any values until a truthy user id is supplied", async () => {
accountService.activeAccountSubject.next(null); activeAccountAccessor.switch(null);
const state = singleUserStateProvider.getFake(userId, keyDefinition); const state = singleUserStateProvider.getFake(userId, keyDefinition);
state.nextState("value"); state.nextState("value");
@@ -151,7 +139,7 @@ describe("DefaultStateProvider", () => {
expect(emissions).toHaveLength(0); expect(emissions).toHaveLength(0);
accountService.activeAccountSubject.next({ id: userId, ...accountInfo }); activeAccountAccessor.switch(userId);
await awaitAsync(); await awaitAsync();
@@ -170,7 +158,7 @@ describe("DefaultStateProvider", () => {
); );
it("should emit default value if no userId supplied and first active user id emission in falsy", async () => { it("should emit default value if no userId supplied and first active user id emission in falsy", async () => {
accountService.activeAccountSubject.next(null); activeAccountAccessor.switch(null);
const emissions = trackEmissions( const emissions = trackEmissions(
sut.getUserStateOrDefault$(keyDefinition, { sut.getUserStateOrDefault$(keyDefinition, {

View File

@@ -2,7 +2,7 @@ import { mock } from "jest-mock-extended";
import { StorageServiceProvider } from "@bitwarden/storage-core"; import { StorageServiceProvider } from "@bitwarden/storage-core";
import { mockAccountServiceWith } from "../../../../spec/fake-account-service"; import { FakeActiveUserAccessor } from "../../../../spec";
import { FakeStorageService } from "../../../../spec/fake-storage.service"; import { FakeStorageService } from "../../../../spec/fake-storage.service";
import { UserId } from "../../../types/guid"; import { UserId } from "../../../types/guid";
import { LogService } from "../../abstractions/log.service"; import { LogService } from "../../abstractions/log.service";
@@ -39,7 +39,7 @@ describe("Specific State Providers", () => {
stateEventRegistrarService, stateEventRegistrarService,
logService, logService,
); );
activeSut = new DefaultActiveUserStateProvider(mockAccountServiceWith(null), singleSut); activeSut = new DefaultActiveUserStateProvider(new FakeActiveUserAccessor(null), singleSut);
globalSut = new DefaultGlobalStateProvider(storageServiceProvider, logService); globalSut = new DefaultGlobalStateProvider(storageServiceProvider, logService);
}); });

View File

@@ -10,5 +10,6 @@ export { KeyDefinition, KeyDefinitionOptions } from "./key-definition";
export { StateUpdateOptions } from "./state-update-options"; export { StateUpdateOptions } from "./state-update-options";
export { UserKeyDefinitionOptions, UserKeyDefinition } from "./user-key-definition"; export { UserKeyDefinitionOptions, UserKeyDefinition } from "./user-key-definition";
export { StateEventRunnerService } from "./state-event-runner.service"; export { StateEventRunnerService } from "./state-event-runner.service";
export { ActiveUserAccessor } from "./active-user.accessor";
export * from "./state-definitions"; export * from "./state-definitions";