2023-11-09 23:06:42 +01:00
|
|
|
import { MockProxy, mock } from "jest-mock-extended";
|
|
|
|
import { Subject } from "rxjs";
|
|
|
|
|
|
|
|
import {
|
|
|
|
AbstractStorageService,
|
2024-01-29 20:42:58 +01:00
|
|
|
ObservableStorageService,
|
2023-11-09 23:06:42 +01:00
|
|
|
StorageUpdate,
|
|
|
|
} from "../src/platform/abstractions/storage.service";
|
|
|
|
import { StorageOptions } from "../src/platform/models/domain/storage-options";
|
|
|
|
|
2024-07-03 16:06:55 +02:00
|
|
|
const INTERNAL_KEY = "__internal__";
|
|
|
|
|
2024-01-29 20:42:58 +01:00
|
|
|
export class FakeStorageService implements AbstractStorageService, ObservableStorageService {
|
2023-11-09 23:06:42 +01:00
|
|
|
private store: Record<string, unknown>;
|
|
|
|
private updatesSubject = new Subject<StorageUpdate>();
|
2023-11-16 20:15:34 +01:00
|
|
|
private _valuesRequireDeserialization = false;
|
2023-11-09 23:06:42 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2023-11-17 15:20:42 +01:00
|
|
|
get internalStore() {
|
|
|
|
return this.store;
|
|
|
|
}
|
|
|
|
|
2023-11-16 20:15:34 +01:00
|
|
|
internalUpdateValuesRequireDeserialization(value: boolean) {
|
|
|
|
this._valuesRequireDeserialization = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
get valuesRequireDeserialization(): boolean {
|
|
|
|
return this._valuesRequireDeserialization;
|
|
|
|
}
|
|
|
|
|
2023-11-09 23:06:42 +01:00
|
|
|
get updates$() {
|
|
|
|
return this.updatesSubject.asObservable();
|
|
|
|
}
|
|
|
|
|
|
|
|
get<T>(key: string, options?: StorageOptions): Promise<T> {
|
2024-02-02 21:13:37 +01:00
|
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2023-11-09 23:06:42 +01:00
|
|
|
this.mock.get(key, options);
|
|
|
|
const value = this.store[key] as T;
|
|
|
|
return Promise.resolve(value);
|
|
|
|
}
|
|
|
|
has(key: string, options?: StorageOptions): Promise<boolean> {
|
2024-02-02 21:13:37 +01:00
|
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2023-11-09 23:06:42 +01:00
|
|
|
this.mock.has(key, options);
|
|
|
|
return Promise.resolve(this.store[key] != null);
|
|
|
|
}
|
2024-07-03 16:06:55 +02:00
|
|
|
async save<T>(key: string, obj: T, options?: StorageOptions): Promise<void> {
|
|
|
|
// These exceptions are copied from https://github.com/sindresorhus/conf/blob/608adb0c46fb1680ddbd9833043478367a64c120/source/index.ts#L193-L203
|
|
|
|
// which is a library that is used by `ElectronStorageService`. We add them here to ensure that the behavior in our testing mirrors the real world.
|
|
|
|
if (typeof key !== "string" && typeof key !== "object") {
|
|
|
|
throw new TypeError(
|
|
|
|
`Expected \`key\` to be of type \`string\` or \`object\`, got ${typeof key}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't throw this error because ElectronStorageService automatically detects this case
|
|
|
|
// and calls `delete()` instead of `set()`.
|
|
|
|
// if (typeof key !== "object" && obj === undefined) {
|
|
|
|
// throw new TypeError("Use `delete()` to clear values");
|
|
|
|
// }
|
|
|
|
|
|
|
|
if (this._containsReservedKey(key)) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Please don't use the ${INTERNAL_KEY} key, as it's used to manage this module internal operations.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-02-02 21:13:37 +01:00
|
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2023-12-13 14:06:24 +01:00
|
|
|
this.mock.save(key, obj, options);
|
2023-11-09 23:06:42 +01:00
|
|
|
this.store[key] = obj;
|
2023-11-16 20:15:34 +01:00
|
|
|
this.updatesSubject.next({ key: key, updateType: "save" });
|
2023-11-09 23:06:42 +01:00
|
|
|
}
|
|
|
|
remove(key: string, options?: StorageOptions): Promise<void> {
|
2024-02-02 21:13:37 +01:00
|
|
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2023-11-09 23:06:42 +01:00
|
|
|
this.mock.remove(key, options);
|
|
|
|
delete this.store[key];
|
2023-11-16 20:15:34 +01:00
|
|
|
this.updatesSubject.next({ key: key, updateType: "remove" });
|
2023-11-09 23:06:42 +01:00
|
|
|
return Promise.resolve();
|
|
|
|
}
|
2024-07-03 16:06:55 +02:00
|
|
|
|
|
|
|
private _containsReservedKey(key: string | Partial<unknown>): boolean {
|
|
|
|
if (typeof key === "object") {
|
|
|
|
const firsKey = Object.keys(key)[0];
|
|
|
|
|
|
|
|
if (firsKey === INTERNAL_KEY) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof key !== "string") {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2023-11-09 23:06:42 +01:00
|
|
|
}
|