mirror of
https://github.com/unjs/unstorage.git
synced 2025-12-08 21:26:09 +00:00
222 lines
6.6 KiB
TypeScript
222 lines
6.6 KiB
TypeScript
import { it, expect, beforeAll, afterAll, afterEach } from "vitest";
|
|
import {
|
|
type Storage,
|
|
type Driver,
|
|
createStorage,
|
|
restoreSnapshot,
|
|
} from "../../src/index.ts";
|
|
|
|
export interface TestContext {
|
|
storage: Storage;
|
|
driver: Driver;
|
|
}
|
|
|
|
export interface TestOptions {
|
|
driver: Driver | (() => Driver);
|
|
noKeysSupport?: boolean;
|
|
additionalTests?: (ctx: TestContext) => void;
|
|
}
|
|
|
|
export function testDriver(opts: TestOptions) {
|
|
const ctx = {} as TestContext;
|
|
|
|
beforeAll(() => {
|
|
ctx.driver =
|
|
typeof opts.driver === "function" ? opts.driver() : opts.driver;
|
|
|
|
ctx.storage = createStorage({
|
|
driver: ctx.driver,
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await ctx.driver?.dispose?.();
|
|
await ctx.storage?.dispose?.();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await ctx.storage.clear();
|
|
});
|
|
|
|
it("init", async () => {
|
|
await restoreSnapshot(ctx.storage, { initial: "works" });
|
|
expect(await ctx.storage.getItem("initial")).toBe("works");
|
|
await ctx.storage.clear();
|
|
});
|
|
|
|
it("initial state", async () => {
|
|
expect(await ctx.storage.hasItem("s1:a")).toBe(false);
|
|
expect(await ctx.storage.getItem("s2:a")).toBe(null);
|
|
expect(await ctx.storage.getKeys()).toMatchObject([]);
|
|
});
|
|
|
|
it("setItem", async () => {
|
|
await ctx.storage.setItem("s1:a", "test_data");
|
|
await ctx.storage.setItem("s2:a", "test_data");
|
|
await ctx.storage.setItem("s3:a?q=1", "test_data");
|
|
expect(await ctx.storage.hasItem("s1:a")).toBe(true);
|
|
expect(await ctx.storage.getItem("s1:a")).toBe("test_data");
|
|
expect(await ctx.storage.getItem("s3:a?q=2")).toBe("test_data");
|
|
});
|
|
|
|
it.skipIf(opts.noKeysSupport)("getKeys", async () => {
|
|
await ctx.storage.setItem("s1:a", "test_data");
|
|
await ctx.storage.setItem("s2:a", "test_data");
|
|
await ctx.storage.setItem("s3:a?q=1", "test_data");
|
|
expect(await ctx.storage.getKeys().then((k) => k.sort())).toMatchObject(
|
|
["s1:a", "s2:a", "s3:a"].sort()
|
|
);
|
|
expect(await ctx.storage.getKeys("s1").then((k) => k.sort())).toMatchObject(
|
|
["s1:a"].sort()
|
|
);
|
|
});
|
|
|
|
it.skipIf(opts.noKeysSupport)("getKeys with depth", async () => {
|
|
await ctx.storage.setItem("depth0_0", "test_data");
|
|
await ctx.storage.setItem("depth0:depth1:depth2_0", "test_data");
|
|
await ctx.storage.setItem("depth0:depth1:depth2_1", "test_data");
|
|
await ctx.storage.setItem("depth0:depth1_0", "test_data");
|
|
await ctx.storage.setItem("depth0:depth1_1", "test_data");
|
|
expect(await ctx.storage.getKeys(undefined, { maxDepth: 0 })).toMatchObject(
|
|
["depth0_0"]
|
|
);
|
|
expect(
|
|
(await ctx.storage.getKeys(undefined, { maxDepth: 1 })).sort()
|
|
).toMatchObject(["depth0:depth1_0", "depth0:depth1_1", "depth0_0"]);
|
|
expect(
|
|
(await ctx.storage.getKeys(undefined, { maxDepth: 2 })).sort()
|
|
).toMatchObject([
|
|
"depth0:depth1:depth2_0",
|
|
"depth0:depth1:depth2_1",
|
|
"depth0:depth1_0",
|
|
"depth0:depth1_1",
|
|
"depth0_0",
|
|
]);
|
|
});
|
|
|
|
it("serialize (object)", async () => {
|
|
await ctx.storage.setItem("/data/test.json", { json: "works" });
|
|
expect(await ctx.storage.getItem("/data/test.json")).toMatchObject({
|
|
json: "works",
|
|
});
|
|
});
|
|
|
|
it("serialize (primitive)", async () => {
|
|
await ctx.storage.setItem("/data/true.json", true);
|
|
expect(await ctx.storage.getItem("/data/true.json")).toBe(true);
|
|
});
|
|
|
|
it("serialize (lossy object with toJSON())", async () => {
|
|
class Test1 {
|
|
toJSON() {
|
|
return "SERIALIZED";
|
|
}
|
|
}
|
|
await ctx.storage.setItem("/data/serialized1.json", new Test1());
|
|
expect(await ctx.storage.getItem("/data/serialized1.json")).toBe(
|
|
"SERIALIZED"
|
|
);
|
|
class Test2 {
|
|
toJSON() {
|
|
return { serializedObj: "works" };
|
|
}
|
|
}
|
|
await ctx.storage.setItem("/data/serialized2.json", new Test2());
|
|
expect(await ctx.storage.getItem("/data/serialized2.json")).toMatchObject({
|
|
serializedObj: "works",
|
|
});
|
|
});
|
|
|
|
it("serialize (error for non primitives)", async () => {
|
|
class Test {}
|
|
await expect(
|
|
ctx.storage.setItem("/data/badvalue.json", new Test())
|
|
).rejects.toThrow("[unstorage] Cannot stringify value!");
|
|
});
|
|
|
|
it("raw support", async () => {
|
|
const value = new Uint8Array([1, 2, 3]);
|
|
await ctx.storage.setItemRaw("/data/raw.bin", value);
|
|
const rValue = await ctx.storage.getItemRaw("/data/raw.bin");
|
|
const rValueLen = rValue?.length || rValue?.byteLength;
|
|
if (rValueLen !== value.length) {
|
|
console.log("Invalid raw value length:", rValue, "Length:", rValueLen);
|
|
}
|
|
expect(rValueLen).toBe(value.length);
|
|
expect(Buffer.from(rValue).toString("base64")).toBe(
|
|
Buffer.from(value).toString("base64")
|
|
);
|
|
});
|
|
|
|
// Bulk tests
|
|
it("setItems", async () => {
|
|
await ctx.storage.setItems([
|
|
{ key: "t:1", value: "test_data_t1" },
|
|
{ key: "t:2", value: "test_data_t2" },
|
|
{ key: "t:3", value: "test_data_t3" },
|
|
]);
|
|
expect(await ctx.storage.getItem("t:1")).toBe("test_data_t1");
|
|
expect(await ctx.storage.getItem("t:2")).toBe("test_data_t2");
|
|
expect(await ctx.storage.getItem("t:3")).toBe("test_data_t3");
|
|
});
|
|
|
|
it("getItems", async () => {
|
|
await ctx.storage.setItem("v3:a?q=1", "test_data_v3:a?q=1");
|
|
await ctx.storage.setItem("v2:a", "test_data_v2:a");
|
|
await ctx.storage.setItem("v1:a", "test_data_v1:a");
|
|
|
|
expect(
|
|
await ctx.storage.getItems([
|
|
{ key: "v1:a" },
|
|
"v2:a",
|
|
{ key: "v3:a?q=1" },
|
|
"v4:undefined",
|
|
])
|
|
).toMatchObject([
|
|
{
|
|
key: "v1:a",
|
|
value: "test_data_v1:a",
|
|
},
|
|
{
|
|
key: "v2:a",
|
|
value: "test_data_v2:a",
|
|
},
|
|
{
|
|
key: "v3:a", // key should lose the querystring
|
|
value: "test_data_v3:a?q=1",
|
|
},
|
|
{
|
|
key: "v4:undefined",
|
|
value: null,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("getItem - return falsy values when set in storage", async () => {
|
|
await ctx.storage.setItem("zero", 0);
|
|
expect(await ctx.storage.getItem("zero")).toBe(0);
|
|
|
|
await ctx.storage.setItem("my-false-flag", false);
|
|
expect(await ctx.storage.getItem("my-false-flag")).toBe(false);
|
|
});
|
|
|
|
// TODO: Refactor to move after cleanup
|
|
if (opts.additionalTests) {
|
|
opts.additionalTests(ctx);
|
|
}
|
|
|
|
it("removeItem", async () => {
|
|
await ctx.storage.removeItem("s1:a", false);
|
|
expect(await ctx.storage.hasItem("s1:a")).toBe(false);
|
|
expect(await ctx.storage.getItem("s1:a")).toBe(null);
|
|
});
|
|
|
|
it("clear", async () => {
|
|
await ctx.storage.clear();
|
|
expect(await ctx.storage.getKeys()).toMatchObject([]);
|
|
// ensure we can clear empty storage as well: #162
|
|
await ctx.storage.clear();
|
|
expect(await ctx.storage.getKeys()).toMatchObject([]);
|
|
});
|
|
}
|