unstorage/test/storage.test.ts
2025-10-07 20:36:43 +02:00

292 lines
8.6 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { resolve } from "node:path";
import {
createStorage,
snapshot,
restoreSnapshot,
prefixStorage,
} from "../src/index.ts";
import memory from "../src/drivers/memory.ts";
import fs from "../src/drivers/fs.ts";
const data = {
"etc:conf": "test",
"data:foo": 123,
};
describe("storage", () => {
it("mount/unmount", async () => {
const storage = createStorage().mount("/mnt", memory());
await restoreSnapshot(storage, data, "mnt");
expect(await snapshot(storage, "/mnt")).toMatchObject(data);
});
it("getMount and getMounts", () => {
const storage = createStorage();
storage.mount("/mnt", memory());
storage.mount("cache", memory());
storage.mount("cache:sub", memory());
expect(storage.getMount("/cache:").base).toBe("cache:");
expect(storage.getMount("/cache:foo").base).toBe("cache:");
expect(storage.getMount("/cache:sub").base).toBe("cache:sub:");
expect(storage.getMount("/cache:sub:foo").base).toBe("cache:sub:");
expect(storage.getMounts("/cache").map((m) => m.base))
.toMatchInlineSnapshot(`
[
"cache:sub:",
"cache:",
]
`);
expect(storage.getMounts("/cache:sub").map((m) => m.base))
.toMatchInlineSnapshot(`
[
"cache:sub:",
]
`);
expect(
storage.getMounts("/cache:sub", { parents: true }).map((m) => m.base)
).toMatchInlineSnapshot(`
[
"cache:sub:",
"cache:",
"",
]
`);
expect(storage.getMounts().map((m) => m.base)).toMatchInlineSnapshot(`
[
"cache:sub:",
"cache:",
"mnt:",
"",
]
`);
});
it("snapshot", async () => {
const storage = createStorage();
await restoreSnapshot(storage, data);
expect(await snapshot(storage, "")).toMatchObject(data);
});
it("watch", async () => {
const onChange = vi.fn();
const storage = createStorage().mount("/mnt", memory());
await storage.watch(onChange);
await restoreSnapshot(storage, data, "mnt");
expect(onChange).toHaveBeenCalledWith("update", "mnt:etc:conf");
expect(onChange).toHaveBeenCalledWith("update", "mnt:data:foo");
expect(onChange).toHaveBeenCalledTimes(2);
});
it("unwatch return", async () => {
const onChange = vi.fn();
const storage = createStorage().mount("/mnt", memory());
const unwatch = await storage.watch(onChange);
await storage.setItem("mnt:data:foo", 42);
await unwatch();
await storage.setItem("mnt:data:foo", 41);
expect(onChange).toHaveBeenCalledTimes(1);
});
it("unwatch all", async () => {
const onChange = vi.fn();
const storage = createStorage().mount("/mnt", memory());
await storage.watch(onChange);
await storage.setItem("mnt:data:foo", 42);
await storage.unwatch();
await storage.setItem("mnt:data:foo", 41);
expect(onChange).toHaveBeenCalledTimes(1);
});
it("mount overides", async () => {
const mainStorage = memory();
const storage = createStorage({ driver: mainStorage });
await storage.setItem("/mnt/test.txt", "v1");
await storage.setItem("/mnt/test.base.txt", "v1");
const initialKeys = await storage.getKeys();
expect(initialKeys).toMatchInlineSnapshot(`
[
"mnt:test.txt",
"mnt:test.base.txt",
]
`);
storage.mount("/mnt", memory());
await storage.setItem("/mnt/test.txt", "v2");
await storage.setItem("/mnt/foo/test.txt", "v3");
storage.mount("/mnt/foo", memory());
expect(await storage.getItem("/mnt/foo/test.txt")).toBe(null);
expect(await storage.getItem("/mnt/test.txt")).toBe("v2");
expect(await storage.getKeys()).toMatchInlineSnapshot(`
[
"mnt:test.txt",
]
`);
await storage.clear("/mnt");
await storage.unmount("/mnt");
expect(await storage.getKeys()).toMatchObject(initialKeys);
expect(await storage.getItem("/mnt/test.txt")).toBe("v1");
});
});
describe("utils", () => {
it("prefixStorage", async () => {
const storage = createStorage();
const pStorage = prefixStorage(storage, "foo");
await pStorage.setItem("x", "bar");
await pStorage.setItem("y", "baz");
expect(await storage.getItem("foo:x")).toBe("bar");
expect(await pStorage.getItem("x")).toBe("bar");
expect(await pStorage.getKeys()).toStrictEqual(["x", "y"]);
// Higher order storage
const secondStorage = createStorage();
secondStorage.mount("/mnt", storage);
const mntStorage = prefixStorage(secondStorage, "mnt");
expect(await mntStorage.getKeys()).toStrictEqual(["foo:x", "foo:y"]);
// Get keys from sub-storage
expect(await mntStorage.getKeys("foo")).toStrictEqual(["foo:x", "foo:y"]);
});
it("stringify", () => {
const storage = createStorage();
expect(async () => await storage.setItem("foo", [])).not.toThrow();
});
it("has aliases", async () => {
const storage = createStorage();
await storage.setItem("foo", "bar");
expect(await storage.has("foo")).toBe(true);
expect(await storage.get("foo")).toBe("bar");
expect(await storage.keys()).toEqual(["foo"]);
await storage.del("foo");
expect(await storage.has("foo")).toBe(false);
await storage.setItem("bar", "baz");
await storage.remove("bar");
expect(await storage.has("bar")).toBe(false);
});
});
describe("Regression", () => {
it("setItems doeesn't upload twice", async () => {
/**
* https://github.com/unjs/unstorage/pull/392
*/
const setItem = vi.fn();
const setItems = vi.fn();
const driver = memory();
const storage = createStorage({
driver: {
...driver,
setItem: (...args) => {
setItem(...args);
return driver.setItem?.(...args);
},
setItems: (...args) => {
setItems(...args);
return driver.setItems?.(...args);
},
},
});
await storage.setItems([{ key: "foo.txt", value: "bar" }]);
expect(setItem).toHaveBeenCalledTimes(0);
expect(setItems).toHaveBeenCalledTimes(1);
});
it("prefixed storage supports aliases", async () => {
const storage = createStorage();
const pStorage = prefixStorage(storage, "foo");
await pStorage.set("x", "foo");
await pStorage.set("y", "bar");
expect(await pStorage.get("x")).toBe("foo");
expect(await pStorage.get("x")).toBe("foo");
expect(await pStorage.has("x")).toBe(true);
expect(await pStorage.get("y")).toBe("bar");
await pStorage.del("x");
expect(await pStorage.has("x")).toBe(false);
await pStorage.remove("y");
expect(await pStorage.has("y")).toBe(false);
});
it("getKeys supports maxDepth with mixed native support", async () => {
const base = resolve(__dirname, "tmp/fs");
const mainStorage = memory();
const secondaryStorage = fs({ base });
const storage = createStorage({ driver: mainStorage });
storage.mount("/storage_b", secondaryStorage);
try {
await storage.setItem("/storage_a/file_depth1", "contents");
await storage.setItem("/storage_a/depth1/file_depth2", "contents");
await storage.setItem("/storage_b/file_depth1", "contents");
await storage.setItem("/storage_b/depth1/file_depth2", "contents");
const keys = await storage.getKeys(undefined, { maxDepth: 1 });
expect(keys.sort()).toMatchObject([
"storage_a:file_depth1",
"storage_b:file_depth1",
]);
} finally {
await storage.clear();
}
});
it("prefixStorage getItems to not returns null (issue #396)", async () => {
const storage = createStorage();
await storage.setItem("namespace:key", "value");
const plainResult = await storage.getItems(["namespace:key"]);
expect(plainResult).toEqual([{ key: "namespace:key", value: "value" }]);
const prefixed = prefixStorage(storage, "namespace");
const prefixedResult = await prefixed.getItems(["key"]);
expect(prefixedResult).toEqual([{ key: "key", value: "value" }]);
});
it("prefixStorage setItems works correctly (related to issue #396)", async () => {
const storage = createStorage();
const prefixed = prefixStorage(storage, "namespace");
await prefixed.setItems([
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
]);
const plainResult = await storage.getItems([
"namespace:key1",
"namespace:key2",
]);
expect(plainResult).toEqual([
{ key: "namespace:key1", value: "value1" },
{ key: "namespace:key2", value: "value2" },
]);
const prefixedResult = await prefixed.getItems(["key1", "key2"]);
expect(prefixedResult).toEqual([
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
]);
});
});