summaryrefslogtreecommitdiffhomepage
path: root/packages/storage-sqlite/src/storage.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/storage-sqlite/src/storage.test.ts')
-rw-r--r--packages/storage-sqlite/src/storage.test.ts408
1 files changed, 204 insertions, 204 deletions
diff --git a/packages/storage-sqlite/src/storage.test.ts b/packages/storage-sqlite/src/storage.test.ts
index fc14498..bfadc67 100644
--- a/packages/storage-sqlite/src/storage.test.ts
+++ b/packages/storage-sqlite/src/storage.test.ts
@@ -5,214 +5,214 @@ import type { SqliteStorageBackend } from "./storage.js";
import { createSqliteStorage } from "./storage.js";
describe("computePending (pure)", () => {
- it("returns all migrations when none applied", () => {
- const migrations: Migration[] = [
- { version: 2, name: "b", up: "" },
- { version: 1, name: "a", up: "" },
- ];
- const result = computePending(new Set(), migrations);
- expect(result.map((m) => m.version)).toEqual([1, 2]);
- });
-
- it("skips already-applied versions", () => {
- const migrations: Migration[] = [
- { version: 1, name: "a", up: "" },
- { version: 2, name: "b", up: "" },
- { version: 3, name: "c", up: "" },
- ];
- const result = computePending(new Set([1, 3]), migrations);
- expect(result.map((m) => m.version)).toEqual([2]);
- });
-
- it("returns empty when all applied", () => {
- const migrations: Migration[] = [{ version: 1, name: "a", up: "" }];
- const result = computePending(new Set([1]), migrations);
- expect(result).toEqual([]);
- });
-
- it("sorts by version ascending", () => {
- const migrations: Migration[] = [
- { version: 3, name: "c", up: "" },
- { version: 1, name: "a", up: "" },
- { version: 2, name: "b", up: "" },
- ];
- const result = computePending(new Set(), migrations);
- expect(result.map((m) => m.version)).toEqual([1, 2, 3]);
- });
+ it("returns all migrations when none applied", () => {
+ const migrations: Migration[] = [
+ { version: 2, name: "b", up: "" },
+ { version: 1, name: "a", up: "" },
+ ];
+ const result = computePending(new Set(), migrations);
+ expect(result.map((m) => m.version)).toEqual([1, 2]);
+ });
+
+ it("skips already-applied versions", () => {
+ const migrations: Migration[] = [
+ { version: 1, name: "a", up: "" },
+ { version: 2, name: "b", up: "" },
+ { version: 3, name: "c", up: "" },
+ ];
+ const result = computePending(new Set([1, 3]), migrations);
+ expect(result.map((m) => m.version)).toEqual([2]);
+ });
+
+ it("returns empty when all applied", () => {
+ const migrations: Migration[] = [{ version: 1, name: "a", up: "" }];
+ const result = computePending(new Set([1]), migrations);
+ expect(result).toEqual([]);
+ });
+
+ it("sorts by version ascending", () => {
+ const migrations: Migration[] = [
+ { version: 3, name: "c", up: "" },
+ { version: 1, name: "a", up: "" },
+ { version: 2, name: "b", up: "" },
+ ];
+ const result = computePending(new Set(), migrations);
+ expect(result.map((m) => m.version)).toEqual([1, 2, 3]);
+ });
});
describe("createSqliteStorage", () => {
- let backend: SqliteStorageBackend;
-
- beforeEach(() => {
- backend = createSqliteStorage({ path: ":memory:" });
- });
-
- afterEach(() => {
- backend.close();
- });
-
- describe("StorageNamespace get/set/delete/has", () => {
- it("returns null for missing key", async () => {
- const ns = backend.storage("test");
- expect(await ns.get("missing")).toBeNull();
- });
-
- it("roundtrips set then get", async () => {
- const ns = backend.storage("test");
- await ns.set("key1", "value1");
- expect(await ns.get("key1")).toBe("value1");
- });
-
- it("overwrites existing value", async () => {
- const ns = backend.storage("test");
- await ns.set("key1", "v1");
- await ns.set("key1", "v2");
- expect(await ns.get("key1")).toBe("v2");
- });
-
- it("has returns false for missing, true for existing", async () => {
- const ns = backend.storage("test");
- expect(await ns.has("key1")).toBe(false);
- await ns.set("key1", "val");
- expect(await ns.has("key1")).toBe(true);
- });
-
- it("delete removes the key", async () => {
- const ns = backend.storage("test");
- await ns.set("key1", "val");
- await ns.delete("key1");
- expect(await ns.get("key1")).toBeNull();
- expect(await ns.has("key1")).toBe(false);
- });
-
- it("delete on missing key is a no-op", async () => {
- const ns = backend.storage("test");
- await ns.delete("nonexistent");
- });
- });
-
- describe("keys", () => {
- it("returns all keys in namespace", async () => {
- const ns = backend.storage("test");
- await ns.set("a", "1");
- await ns.set("b", "2");
- await ns.set("c", "3");
- const keys = await ns.keys();
- expect(keys.toSorted()).toEqual(["a", "b", "c"]);
- });
-
- it("filters by prefix", async () => {
- const ns = backend.storage("test");
- await ns.set("foo:1", "a");
- await ns.set("foo:2", "b");
- await ns.set("bar:1", "c");
- const keys = await ns.keys("foo");
- expect(keys.toSorted()).toEqual(["foo:1", "foo:2"]);
- });
-
- it("returns empty for no matches", async () => {
- const ns = backend.storage("test");
- await ns.set("a", "1");
- expect(await ns.keys("zzz")).toEqual([]);
- });
- });
-
- describe("namespace isolation", () => {
- it("same key in different namespaces does not collide", async () => {
- const ns1 = backend.storage("ns1");
- const ns2 = backend.storage("ns2");
- await ns1.set("key", "value1");
- await ns2.set("key", "value2");
- expect(await ns1.get("key")).toBe("value1");
- expect(await ns2.get("key")).toBe("value2");
- });
-
- it("delete in one namespace does not affect another", async () => {
- const ns1 = backend.storage("ns1");
- const ns2 = backend.storage("ns2");
- await ns1.set("key", "v1");
- await ns2.set("key", "v2");
- await ns1.delete("key");
- expect(await ns1.has("key")).toBe(false);
- expect(await ns2.get("key")).toBe("v2");
- });
-
- it("keys are scoped to namespace", async () => {
- const ns1 = backend.storage("ns1");
- const ns2 = backend.storage("ns2");
- await ns1.set("a", "1");
- await ns1.set("b", "2");
- await ns2.set("c", "3");
- expect((await ns1.keys()).toSorted()).toEqual(["a", "b"]);
- expect(await ns2.keys()).toEqual(["c"]);
- });
- });
-
- describe("migrate", () => {
- it("runs pending migrations in order", async () => {
- const migrations: Migration[] = [
- { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
- { version: 2, name: "create_bar", up: "CREATE TABLE bar (id INTEGER PRIMARY KEY);" },
- ];
- await backend.migrate("ext1", migrations);
-
- const ns = backend.storage("ext1");
- await ns.set("test", "val");
- expect(await ns.get("test")).toBe("val");
- });
-
- it("skips already-applied migrations", async () => {
- const migrations: Migration[] = [
- { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
- ];
- await backend.migrate("ext1", migrations);
- await backend.migrate("ext1", [
- ...migrations,
- { version: 2, name: "create_bar", up: "CREATE TABLE bar (id INTEGER PRIMARY KEY);" },
- ]);
- });
-
- it("is idempotent — calling migrate twice with same migrations is safe", async () => {
- const migrations: Migration[] = [
- { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
- ];
- await backend.migrate("ext1", migrations);
- await backend.migrate("ext1", migrations);
- });
-
- it("migrations are per-namespace", async () => {
- const m1: Migration[] = [
- { version: 1, name: "create_t1", up: "CREATE TABLE t1 (id INTEGER PRIMARY KEY);" },
- ];
- const m2: Migration[] = [
- { version: 1, name: "create_t2", up: "CREATE TABLE t2 (id INTEGER PRIMARY KEY);" },
- ];
- await backend.migrate("ext1", m1);
- await backend.migrate("ext2", m2);
- });
- });
+ let backend: SqliteStorageBackend;
+
+ beforeEach(() => {
+ backend = createSqliteStorage({ path: ":memory:" });
+ });
+
+ afterEach(() => {
+ backend.close();
+ });
+
+ describe("StorageNamespace get/set/delete/has", () => {
+ it("returns null for missing key", async () => {
+ const ns = backend.storage("test");
+ expect(await ns.get("missing")).toBeNull();
+ });
+
+ it("roundtrips set then get", async () => {
+ const ns = backend.storage("test");
+ await ns.set("key1", "value1");
+ expect(await ns.get("key1")).toBe("value1");
+ });
+
+ it("overwrites existing value", async () => {
+ const ns = backend.storage("test");
+ await ns.set("key1", "v1");
+ await ns.set("key1", "v2");
+ expect(await ns.get("key1")).toBe("v2");
+ });
+
+ it("has returns false for missing, true for existing", async () => {
+ const ns = backend.storage("test");
+ expect(await ns.has("key1")).toBe(false);
+ await ns.set("key1", "val");
+ expect(await ns.has("key1")).toBe(true);
+ });
+
+ it("delete removes the key", async () => {
+ const ns = backend.storage("test");
+ await ns.set("key1", "val");
+ await ns.delete("key1");
+ expect(await ns.get("key1")).toBeNull();
+ expect(await ns.has("key1")).toBe(false);
+ });
+
+ it("delete on missing key is a no-op", async () => {
+ const ns = backend.storage("test");
+ await ns.delete("nonexistent");
+ });
+ });
+
+ describe("keys", () => {
+ it("returns all keys in namespace", async () => {
+ const ns = backend.storage("test");
+ await ns.set("a", "1");
+ await ns.set("b", "2");
+ await ns.set("c", "3");
+ const keys = await ns.keys();
+ expect(keys.toSorted()).toEqual(["a", "b", "c"]);
+ });
+
+ it("filters by prefix", async () => {
+ const ns = backend.storage("test");
+ await ns.set("foo:1", "a");
+ await ns.set("foo:2", "b");
+ await ns.set("bar:1", "c");
+ const keys = await ns.keys("foo");
+ expect(keys.toSorted()).toEqual(["foo:1", "foo:2"]);
+ });
+
+ it("returns empty for no matches", async () => {
+ const ns = backend.storage("test");
+ await ns.set("a", "1");
+ expect(await ns.keys("zzz")).toEqual([]);
+ });
+ });
+
+ describe("namespace isolation", () => {
+ it("same key in different namespaces does not collide", async () => {
+ const ns1 = backend.storage("ns1");
+ const ns2 = backend.storage("ns2");
+ await ns1.set("key", "value1");
+ await ns2.set("key", "value2");
+ expect(await ns1.get("key")).toBe("value1");
+ expect(await ns2.get("key")).toBe("value2");
+ });
+
+ it("delete in one namespace does not affect another", async () => {
+ const ns1 = backend.storage("ns1");
+ const ns2 = backend.storage("ns2");
+ await ns1.set("key", "v1");
+ await ns2.set("key", "v2");
+ await ns1.delete("key");
+ expect(await ns1.has("key")).toBe(false);
+ expect(await ns2.get("key")).toBe("v2");
+ });
+
+ it("keys are scoped to namespace", async () => {
+ const ns1 = backend.storage("ns1");
+ const ns2 = backend.storage("ns2");
+ await ns1.set("a", "1");
+ await ns1.set("b", "2");
+ await ns2.set("c", "3");
+ expect((await ns1.keys()).toSorted()).toEqual(["a", "b"]);
+ expect(await ns2.keys()).toEqual(["c"]);
+ });
+ });
+
+ describe("migrate", () => {
+ it("runs pending migrations in order", async () => {
+ const migrations: Migration[] = [
+ { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
+ { version: 2, name: "create_bar", up: "CREATE TABLE bar (id INTEGER PRIMARY KEY);" },
+ ];
+ await backend.migrate("ext1", migrations);
+
+ const ns = backend.storage("ext1");
+ await ns.set("test", "val");
+ expect(await ns.get("test")).toBe("val");
+ });
+
+ it("skips already-applied migrations", async () => {
+ const migrations: Migration[] = [
+ { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
+ ];
+ await backend.migrate("ext1", migrations);
+ await backend.migrate("ext1", [
+ ...migrations,
+ { version: 2, name: "create_bar", up: "CREATE TABLE bar (id INTEGER PRIMARY KEY);" },
+ ]);
+ });
+
+ it("is idempotent — calling migrate twice with same migrations is safe", async () => {
+ const migrations: Migration[] = [
+ { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
+ ];
+ await backend.migrate("ext1", migrations);
+ await backend.migrate("ext1", migrations);
+ });
+
+ it("migrations are per-namespace", async () => {
+ const m1: Migration[] = [
+ { version: 1, name: "create_t1", up: "CREATE TABLE t1 (id INTEGER PRIMARY KEY);" },
+ ];
+ const m2: Migration[] = [
+ { version: 1, name: "create_t2", up: "CREATE TABLE t2 (id INTEGER PRIMARY KEY);" },
+ ];
+ await backend.migrate("ext1", m1);
+ await backend.migrate("ext2", m2);
+ });
+ });
});
describe("createSqliteStorage — persistence across reopen", () => {
- it("migrations survive close and reopen", async () => {
- const tmpPath = `/tmp/storage-sqlite-test-${Date.now()}.db`;
- const migrations: Migration[] = [
- { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
- ];
-
- const first = createSqliteStorage({ path: tmpPath });
- await first.migrate("ext1", migrations);
- first.close();
-
- const second = createSqliteStorage({ path: tmpPath });
- await second.migrate("ext1", [
- ...migrations,
- { version: 2, name: "create_bar", up: "CREATE TABLE bar (id INTEGER PRIMARY KEY);" },
- ]);
- second.close();
-
- const { unlinkSync } = await import("node:fs");
- unlinkSync(tmpPath);
- });
+ it("migrations survive close and reopen", async () => {
+ const tmpPath = `/tmp/storage-sqlite-test-${Date.now()}.db`;
+ const migrations: Migration[] = [
+ { version: 1, name: "create_foo", up: "CREATE TABLE foo (id INTEGER PRIMARY KEY);" },
+ ];
+
+ const first = createSqliteStorage({ path: tmpPath });
+ await first.migrate("ext1", migrations);
+ first.close();
+
+ const second = createSqliteStorage({ path: tmpPath });
+ await second.migrate("ext1", [
+ ...migrations,
+ { version: 2, name: "create_bar", up: "CREATE TABLE bar (id INTEGER PRIMARY KEY);" },
+ ]);
+ second.close();
+
+ const { unlinkSync } = await import("node:fs");
+ unlinkSync(tmpPath);
+ });
});