Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { PersistentCache } from "lib/PersistentCache.sys.mjs";
import { stubGlobals } from "test/jest/test-utils";
describe("PersistentCache", () => {
let fakeIOUtils;
let fakePathUtils;
let cache;
let filename = "cache.json";
let consoleErrorStub;
let restoreGlobals;
beforeEach(() => {
fakeIOUtils = {
writeJSON: jest.fn().mockResolvedValue(0),
readJSON: jest.fn().mockResolvedValue({}),
};
fakePathUtils = {
join: jest.fn(() => filename),
localProfileDir: "/",
};
consoleErrorStub = jest
.spyOn(console, "error")
.mockImplementation(() => {});
restoreGlobals = stubGlobals({
IOUtils: fakeIOUtils,
PathUtils: fakePathUtils,
});
cache = new PersistentCache(filename);
});
afterEach(() => {
restoreGlobals();
consoleErrorStub.mockRestore();
});
describe("#get", () => {
it("tries to read the file", async () => {
await cache.get("foo");
expect(fakeIOUtils.readJSON).toHaveBeenCalledTimes(1);
});
it("doesnt try to read the file if it was already loaded", async () => {
await cache._load();
fakeIOUtils.readJSON.mockClear();
await cache.get("foo");
expect(fakeIOUtils.readJSON).not.toHaveBeenCalled();
});
it("should catch and report errors", async () => {
fakeIOUtils.readJSON.mockRejectedValue(
new SyntaxError("Failed to parse JSON")
);
await cache._load();
expect(consoleErrorStub).toHaveBeenCalledTimes(1);
cache._cache = undefined;
consoleErrorStub.mockClear();
fakeIOUtils.readJSON.mockRejectedValue(
new DOMException("IOUtils shutting down", "AbortError")
);
await cache._load();
expect(consoleErrorStub).toHaveBeenCalledTimes(1);
cache._cache = undefined;
consoleErrorStub.mockClear();
fakeIOUtils.readJSON.mockRejectedValue(
new DOMException("File not found", "NotFoundError")
);
await cache._load();
expect(consoleErrorStub).not.toHaveBeenCalled();
});
it("returns data for a given cache key", async () => {
fakeIOUtils.readJSON.mockResolvedValue({ foo: "bar" });
let value = await cache.get("foo");
expect(value).toEqual("bar");
});
it("returns undefined for a cache key that doesn't exist", async () => {
let value = await cache.get("baz");
expect(value).toBeUndefined();
});
it("returns all the data if no cache key is specified", async () => {
fakeIOUtils.readJSON.mockResolvedValue({ foo: "bar" });
let value = await cache.get();
expect(value).toEqual({ foo: "bar" });
});
});
describe("#set", () => {
it("tries to read the file on the first set", async () => {
await cache.set("foo", { x: 42 });
expect(fakeIOUtils.readJSON).toHaveBeenCalledTimes(1);
});
it("doesnt try to read the file if it was already loaded", async () => {
cache = new PersistentCache(filename, true);
await cache._load();
fakeIOUtils.readJSON.mockClear();
await cache.set("foo", { x: 42 });
expect(fakeIOUtils.readJSON).not.toHaveBeenCalled();
});
it("sets a string value", async () => {
const key = "testkey";
const value = "testvalue";
await cache.set(key, value);
const cachedValue = await cache.get(key);
expect(cachedValue).toEqual(value);
});
it("sets an object value", async () => {
const key = "testkey";
const value = { x: 1, y: 2, z: 3 };
await cache.set(key, value);
const cachedValue = await cache.get(key);
expect(cachedValue).toEqual(value);
});
it("writes the data to file", async () => {
const key = "testkey";
const value = { x: 1, y: 2, z: 3 };
await cache.set(key, value);
expect(fakeIOUtils.writeJSON).toHaveBeenCalledTimes(1);
expect(fakeIOUtils.writeJSON).toHaveBeenCalledWith(
filename,
{ [key]: value },
{ tmpPath: `${filename}.tmp` }
);
});
it("throws when failing to get file path", async () => {
Object.defineProperty(fakePathUtils, "localProfileDir", {
get() {
throw new Error();
},
});
let rejected = false;
try {
await cache.set("key", "val");
} catch (error) {
rejected = true;
}
expect(rejected).toBe(true);
});
});
});