1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add unit tests for BaseStore (#4404)

* fix possible race condition in BaseStore persistence

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* cleanup variable names in tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove async

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-11-23 19:41:16 +02:00 committed by GitHub
parent 7c5a0a9a6d
commit 32dfc74aff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 173 additions and 2 deletions

View File

@ -0,0 +1,171 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import mockFs from "mock-fs";
import { AppPaths } from "../app-paths";
import { BaseStore } from "../base-store";
import { action, comparer, makeObservable, observable, toJS } from "mobx";
import { readFileSync } from "fs";
AppPaths.init();
jest.mock("electron", () => ({
app: {
getVersion: () => "99.99.99",
getName: () => "lens",
setName: jest.fn(),
setPath: jest.fn(),
getPath: () => "tmp",
getLocale: () => "en",
setLoginItemSettings: jest.fn(),
},
ipcMain: {
handle: jest.fn(),
on: jest.fn(),
removeAllListeners: jest.fn(),
off: jest.fn(),
send: jest.fn(),
},
}));
interface TestStoreModel {
a: string;
b: string;
c: string;
}
class TestStore extends BaseStore<TestStoreModel> {
@observable a: string;
@observable b: string;
@observable c: string;
constructor() {
super({
configName: "test-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
});
makeObservable(this);
this.load();
}
@action updateAll(data: TestStoreModel) {
this.a = data.a;
this.b = data.b;
this.c = data.c;
}
@action fromStore(data: Partial<TestStoreModel> = {}) {
this.a = data.a || "";
this.b = data.b || "";
this.c = data.c || "";
}
onSync(data: TestStoreModel) {
super.onSync(data);
}
async saveToFile(model: TestStoreModel) {
return super.saveToFile(model);
}
toJSON(): TestStoreModel {
const data: TestStoreModel = {
a: this.a,
b: this.b,
c: this.c,
};
return toJS(data);
}
}
describe("BaseStore", () => {
let store: TestStore;
beforeEach(async () => {
store = undefined;
TestStore.resetInstance();
const mockOpts = {
"tmp": {
"test-store.json": JSON.stringify({}),
},
};
mockFs(mockOpts);
store = TestStore.createInstance();
});
afterEach(() => {
store.disableSync();
TestStore.resetInstance();
mockFs.restore();
});
describe("persistence", () => {
it("persists changes to the filesystem", () => {
store.updateAll({
a: "foo", b: "bar", c: "hello",
});
const data = JSON.parse(readFileSync("tmp/test-store.json").toString());
expect(data).toEqual({ a: "foo", b: "bar", c: "hello" });
});
it("persists transaction only once", () => {
const fileSpy = jest.spyOn(store, "saveToFile");
store.updateAll({
a: "foo", b: "bar", c: "hello",
});
expect(fileSpy).toHaveBeenCalledTimes(1);
});
it("persists changes one-by-one without transaction", () => {
const fileSpy = jest.spyOn(store, "saveToFile");
store.a = "a";
store.b = "b";
expect(fileSpy).toHaveBeenCalledTimes(2);
const data = JSON.parse(readFileSync("tmp/test-store.json").toString());
expect(data).toEqual({ a: "a", b: "b", c: "" });
});
it("persists changes coming via onSync (sync from different process)", () => {
const fileSpy = jest.spyOn(store, "saveToFile");
store.onSync({ a: "foo", b: "", c: "bar" });
expect(store.toJSON()).toEqual({ a: "foo", b: "", c: "bar" });
expect(fileSpy).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -102,7 +102,7 @@ export abstract class BaseStore<T> extends Singleton {
return AppPaths.get("userData"); return AppPaths.get("userData");
} }
protected async saveToFile(model: T) { protected saveToFile(model: T) {
logger.info(`[STORE]: SAVING ${this.path}`); logger.info(`[STORE]: SAVING ${this.path}`);
// todo: update when fixed https://github.com/sindresorhus/conf/issues/114 // todo: update when fixed https://github.com/sindresorhus/conf/issues/114
@ -166,7 +166,7 @@ export abstract class BaseStore<T> extends Singleton {
} }
} }
protected async onModelChange(model: T) { protected onModelChange(model: T) {
if (ipcMain) { if (ipcMain) {
this.saveToFile(model); // save config file this.saveToFile(model); // save config file
broadcastMessage(this.syncRendererChannel, model); broadcastMessage(this.syncRendererChannel, model);