mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-29 07:59:42 +01:00
fd85d13b18
* Add a small default time to limit timing failures * Handle subscription race conditions * Add Symbols to tracked emission types This is a bit of a cheat, but Symbols can't be cloned, so we need to nudge them to something we can handle. They are rare enough that anyone hitting this is likely to expect some special handling. * Ref count state listeners to minimize storage activity * Ensure statuses are updated * Remove notes * Use `test` when gramatically more proper * Copy race and subscription improvements to single user * Simplify observer initialization * Correct parameter names * Simplify update promises test we don't accidentally deadlock along the `getFromState` path * Fix save mock * WIP: most tests working * Avoid infinite update loop * Avoid potential deadlocks with awaiting assigned promises We were awaiting a promise assigned in a thenable. It turns out that assignment occurs before all thenables are concatenated, which can cause deadlocks. Likely, these were not showing up in tests because we're using very quick memory storage. * Fix update deadlock test * Add user update tests * Assert no double emit for multiple observers * Add use intent to method name * Ensure new subscriptions receive only newest data TODO: is this worth doing for active user state? * Remove unnecessary design requirement We don't need to await an executing update promise, we can support two emissions as long as the observable is guaranteed to get the new data. * Cleanup await spam * test cleanup option behavior * Remove unnecessary typecast * Throw over coerce for definition options * Fix state$ observable durability on cleanup
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { MockProxy, mock } from "jest-mock-extended";
|
|
import { Subject } from "rxjs";
|
|
|
|
import {
|
|
AbstractStorageService,
|
|
StorageUpdate,
|
|
} from "../src/platform/abstractions/storage.service";
|
|
import { StorageOptions } from "../src/platform/models/domain/storage-options";
|
|
|
|
export class FakeStorageService implements AbstractStorageService {
|
|
private store: Record<string, unknown>;
|
|
private updatesSubject = new Subject<StorageUpdate>();
|
|
private _valuesRequireDeserialization = false;
|
|
|
|
/**
|
|
* Returns a mock of a {@see AbstractStorageService} for asserting the expected
|
|
* amount of calls. It is not recommended to use this to mock implementations as
|
|
* they are not respected.
|
|
*/
|
|
mock: MockProxy<AbstractStorageService>;
|
|
|
|
constructor(initial?: Record<string, unknown>) {
|
|
this.store = initial ?? {};
|
|
this.mock = mock<AbstractStorageService>();
|
|
}
|
|
|
|
/**
|
|
* Updates the internal store for this fake implementation, this bypasses any mock calls
|
|
* or updates to the {@link updates$} observable.
|
|
* @param store
|
|
*/
|
|
internalUpdateStore(store: Record<string, unknown>) {
|
|
this.store = store;
|
|
}
|
|
|
|
get internalStore() {
|
|
return this.store;
|
|
}
|
|
|
|
internalUpdateValuesRequireDeserialization(value: boolean) {
|
|
this._valuesRequireDeserialization = value;
|
|
}
|
|
|
|
get valuesRequireDeserialization(): boolean {
|
|
return this._valuesRequireDeserialization;
|
|
}
|
|
|
|
get updates$() {
|
|
return this.updatesSubject.asObservable();
|
|
}
|
|
|
|
get<T>(key: string, options?: StorageOptions): Promise<T> {
|
|
this.mock.get(key, options);
|
|
const value = this.store[key] as T;
|
|
return Promise.resolve(value);
|
|
}
|
|
has(key: string, options?: StorageOptions): Promise<boolean> {
|
|
this.mock.has(key, options);
|
|
return Promise.resolve(this.store[key] != null);
|
|
}
|
|
save<T>(key: string, obj: T, options?: StorageOptions): Promise<void> {
|
|
this.mock.save(key, obj, options);
|
|
this.store[key] = obj;
|
|
this.updatesSubject.next({ key: key, updateType: "save" });
|
|
return Promise.resolve();
|
|
}
|
|
remove(key: string, options?: StorageOptions): Promise<void> {
|
|
this.mock.remove(key, options);
|
|
delete this.store[key];
|
|
this.updatesSubject.next({ key: key, updateType: "remove" });
|
|
return Promise.resolve();
|
|
}
|
|
}
|