1
0
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:
Matt Gibson
2023-10-04 15:51:23 -04:00
parent 931dba7f93
commit f0b16d0baf
5 changed files with 106 additions and 69 deletions

View 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;
}
}

View File

@@ -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()
);

View File

@@ -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();

View File

@@ -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,
});
}
});

View File

@@ -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);
}