mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 21:50:15 +00:00
Work to maintain equivalence to current folderState
This commit is contained in:
33
libs/common/spec/test-active-user-state.ts
Normal file
33
libs/common/spec/test-active-user-state.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { ActiveUserState } from "../src/platform/interfaces/active-user-state";
|
||||
|
||||
export class TestUserState<T> implements ActiveUserState<T> {
|
||||
private _state$: BehaviorSubject<T>;
|
||||
get state$() {
|
||||
return this._state$.asObservable();
|
||||
}
|
||||
|
||||
constructor(initialValue: T) {
|
||||
this._state$ = new BehaviorSubject(initialValue);
|
||||
}
|
||||
|
||||
getFromState = jest.fn();
|
||||
update = jest.fn().mockImplementation(this.minimalUpdate);
|
||||
createDerived = jest.fn();
|
||||
|
||||
next(next: T) {
|
||||
this._state$.next(next);
|
||||
}
|
||||
|
||||
complete() {
|
||||
this._state$.complete();
|
||||
}
|
||||
|
||||
private async minimalUpdate(configureState: (state: T) => T) {
|
||||
const currentState = await firstValueFrom(this.state$);
|
||||
const newState = configureState(currentState);
|
||||
this._state$.next(newState);
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@ import {
|
||||
BehaviorSubject,
|
||||
Subject,
|
||||
combineLatestWith,
|
||||
map,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
share,
|
||||
tap,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountInfo, InternalAccountService } from "../../auth/abstractions/account.service";
|
||||
@@ -24,7 +23,6 @@ export class AccountServiceImplementation implements InternalAccountService {
|
||||
activeAccount$ = this.activeAccountId.pipe(
|
||||
combineLatestWith(this.accounts$),
|
||||
map(([id, accounts]) => (id ? { id, ...accounts[id] } : undefined)),
|
||||
tap((stuff) => console.log("stuff", stuff)),
|
||||
distinctUntilChanged(),
|
||||
share()
|
||||
);
|
||||
|
||||
@@ -83,7 +83,10 @@ describe("DefaultStateProvider", () => {
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// Service does an update
|
||||
await fakeDomainState.update((state) => state.array.push("value3"));
|
||||
await fakeDomainState.update((state) => {
|
||||
state.array.push("value3");
|
||||
return state;
|
||||
});
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
subscription.unsubscribe();
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { TestUserState as TestActiveUserState } from "../../../../spec/test-active-user-state";
|
||||
import { ActiveUserStateProvider } from "../../../platform/abstractions/active-user-state.provider";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { ContainerService } from "../../../platform/services/container.service";
|
||||
import { DerivedActiveUserState } from "../../../platform/services/default-active-user-state.provider";
|
||||
import { StateService } from "../../../platform/services/state.service";
|
||||
import { CipherService } from "../../abstractions/cipher.service";
|
||||
import { FolderData } from "../../models/data/folder.data";
|
||||
import { Folder } from "../../models/domain/folder";
|
||||
import { FolderView } from "../../models/view/folder.view";
|
||||
import { FolderService } from "../../services/folder/folder.service";
|
||||
|
||||
@@ -23,6 +28,11 @@ describe("Folder Service", () => {
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let activeAccount: BehaviorSubject<string>;
|
||||
let activeAccountUnlocked: BehaviorSubject<boolean>;
|
||||
const activeUserStateProvider = mock<ActiveUserStateProvider>();
|
||||
let activeUserState: TestActiveUserState<Record<string, FolderData>>;
|
||||
const derivedActiveUserState =
|
||||
mock<DerivedActiveUserState<Record<string, FolderData>, FolderView[]>>();
|
||||
let folderViews$: BehaviorSubject<FolderView[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoService = Substitute.for();
|
||||
@@ -33,17 +43,38 @@ describe("Folder Service", () => {
|
||||
activeAccount = new BehaviorSubject("123");
|
||||
activeAccountUnlocked = new BehaviorSubject(true);
|
||||
|
||||
stateService.getEncryptedFolders().resolves({
|
||||
const initialState = {
|
||||
"1": folderData("1", "test"),
|
||||
});
|
||||
};
|
||||
stateService.getEncryptedFolders().resolves(initialState);
|
||||
stateService.activeAccount$.returns(activeAccount);
|
||||
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
folderService = new FolderService(cryptoService, i18nService, cipherService, stateService);
|
||||
activeUserState = new TestActiveUserState({});
|
||||
activeUserState.next(initialState);
|
||||
activeUserStateProvider.create.mockReturnValue(activeUserState);
|
||||
activeUserState.createDerived.mockReturnValue(derivedActiveUserState);
|
||||
|
||||
folderViews$ = new BehaviorSubject([]);
|
||||
derivedActiveUserState.state$ = folderViews$;
|
||||
|
||||
folderService = new FolderService(
|
||||
cryptoService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
activeUserStateProvider,
|
||||
stateService
|
||||
);
|
||||
});
|
||||
|
||||
it("encrypt", async () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
folderViews$.complete();
|
||||
activeUserState.complete();
|
||||
});
|
||||
|
||||
test("encrypt", async () => {
|
||||
const model = new FolderView();
|
||||
model.id = "2";
|
||||
model.name = "Test Folder";
|
||||
@@ -63,59 +94,34 @@ describe("Folder Service", () => {
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it("exists", async () => {
|
||||
it("returns the current state", async () => {
|
||||
const result = await folderService.get("1");
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "1",
|
||||
name: {
|
||||
decryptedValue: [],
|
||||
encryptedString: "test",
|
||||
encryptionType: 0,
|
||||
},
|
||||
revisionDate: null,
|
||||
});
|
||||
expect(result).toEqual(folder("1", "test"));
|
||||
});
|
||||
|
||||
it("not exists", async () => {
|
||||
it("returns a Folder object", async () => {
|
||||
const result = await folderService.get("1");
|
||||
|
||||
expect(result).toBeInstanceOf(Folder);
|
||||
});
|
||||
|
||||
it("returns null if not found", async () => {
|
||||
const result = await folderService.get("2");
|
||||
|
||||
expect(result).toBe(undefined);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it("upsert", async () => {
|
||||
test("upsert emits new folder$ array", async () => {
|
||||
await folderService.upsert(folderData("2", "test 2"));
|
||||
|
||||
expect(await firstValueFrom(folderService.folders$)).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
name: {
|
||||
decryptedValue: [],
|
||||
encryptedString: "test",
|
||||
encryptionType: 0,
|
||||
},
|
||||
revisionDate: null,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: {
|
||||
decryptedValue: [],
|
||||
encryptedString: "test 2",
|
||||
encryptionType: 0,
|
||||
},
|
||||
revisionDate: null,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
|
||||
{ id: "1", name: [], revisionDate: null },
|
||||
{ id: "2", name: [], revisionDate: null },
|
||||
{ id: null, name: [], revisionDate: null },
|
||||
folder("1", "test"),
|
||||
folder("2", "test 2"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("replace", async () => {
|
||||
test("replace", async () => {
|
||||
await folderService.replace({ "2": folderData("2", "test 2") });
|
||||
|
||||
expect(await firstValueFrom(folderService.folders$)).toEqual([
|
||||
@@ -129,37 +135,25 @@ describe("Folder Service", () => {
|
||||
revisionDate: null,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
|
||||
{ id: "2", name: [], revisionDate: null },
|
||||
{ id: null, name: [], revisionDate: null },
|
||||
]);
|
||||
});
|
||||
|
||||
it("delete", async () => {
|
||||
test("delete", async () => {
|
||||
await folderService.delete("1");
|
||||
|
||||
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
|
||||
|
||||
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
|
||||
{ id: null, name: [], revisionDate: null },
|
||||
]);
|
||||
});
|
||||
|
||||
it("clearCache", async () => {
|
||||
test("clearCache", async () => {
|
||||
await folderService.clearCache();
|
||||
|
||||
expect((await firstValueFrom(folderService.folders$)).length).toBe(1);
|
||||
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
|
||||
});
|
||||
|
||||
it("locking should clear", async () => {
|
||||
activeAccountUnlocked.next(false);
|
||||
// Sleep for 100ms to avoid timing issues
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
test("clear nulls folders", async () => {
|
||||
await folderService.clear();
|
||||
|
||||
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
|
||||
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
|
||||
expect(await firstValueFrom(folderService.folders$)).toEqual(expect.arrayContaining([]));
|
||||
});
|
||||
|
||||
describe("clear", () => {
|
||||
@@ -193,10 +187,18 @@ describe("Folder Service", () => {
|
||||
});
|
||||
|
||||
function folderData(id: string, name: string) {
|
||||
const data = new FolderData({} as any);
|
||||
data.id = id;
|
||||
data.name = name;
|
||||
return Object.assign(new FolderData({} as any), {
|
||||
id,
|
||||
name,
|
||||
revisionDate: null,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
function folder(id: string, name: string) {
|
||||
return Object.assign(new Folder({} as any), {
|
||||
id,
|
||||
name: new EncString(name),
|
||||
revisionDate: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
|
||||
|
||||
async get(id: string): Promise<Folder> {
|
||||
const folders = await firstValueFrom(this.folderState.state$);
|
||||
return new Folder(folders[id]);
|
||||
return folders[id] == null ? null : new Folder(folders[id]);
|
||||
}
|
||||
|
||||
async getAllFromState(): Promise<Folder[]> {
|
||||
@@ -115,6 +115,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<any> {
|
||||
// TODO: handle clear for non-active users
|
||||
await this.folderState.update((_) => null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user