125 lines · 4.4 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect"; import { StorageError } from "./fuma-runtime"; import { makeInMemoryBlobStore, pluginBlobStore, type OwnerPartitions } from "./blob"; // v2: owner partitions instead of a scope stack. Reads fall through// [user, org] (user = innermost); writes/deletes name an explicit owner.const partitions = (org: string, user: string | null): OwnerPartitions => ({  org,  user,}); describe("pluginBlobStore", () => {  it.effect("get returns user (innermost) value when both owners have one", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      yield* store.put("u/my-plugin", "k", "user-value");      yield* store.put("o/my-plugin", "k", "org-value");       const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");      const value = yield* plugin.get("k");      expect(value).toBe("user-value");    }),  );   it.effect("get falls through to org when user partition is empty", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      yield* store.put("o/my-plugin", "k", "org-value");       const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");      const value = yield* plugin.get("k");      expect(value).toBe("org-value");    }),  );   it.effect("get returns null when no owner has the key", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");      const value = yield* plugin.get("k");      expect(value).toBeNull();    }),  );   it.effect("has returns true when any owner has the key", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      yield* store.put("o/my-plugin", "k", "v");       const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");      const found = yield* plugin.has("k");      expect(found).toBe(true);    }),  );   it.effect("has returns false when no owner has the key", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");      const found = yield* plugin.has("k");      expect(found).toBe(false);    }),  );   it.effect("namespaces are keyed by partition/pluginId — different plugins don't collide", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      yield* store.put("u/plugin-a", "k", "a-value");      yield* store.put("u/plugin-b", "k", "b-value");       const pluginA = pluginBlobStore(store, partitions("o", "u"), "plugin-a");      const pluginB = pluginBlobStore(store, partitions("o", "u"), "plugin-b");      expect(yield* pluginA.get("k")).toBe("a-value");      expect(yield* pluginB.get("k")).toBe("b-value");    }),  );   it.effect("put rejects owner:user when the executor has no subject", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      // No user partition → owner:"user" writes fail.      const plugin = pluginBlobStore(store, partitions("o", null), "my-plugin");      const err = yield* plugin.put("k", "v", { owner: "user" }).pipe(Effect.flip);      expect(err).toBeInstanceOf(StorageError);      // Write must not have reached the store.      expect(yield* store.get("o/my-plugin", "k")).toBeNull();    }),  );   it.effect("delete rejects owner:user when the executor has no subject", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      const plugin = pluginBlobStore(store, partitions("o", null), "my-plugin");      const err = yield* plugin.delete("k", { owner: "user" }).pipe(Effect.flip);      expect(err).toBeInstanceOf(StorageError);    }),  );}); describe("BlobStore.getMany", () => {  it.effect("returns hits keyed by namespace", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      yield* store.put("ns-a", "k", "a");      yield* store.put("ns-c", "k", "c");       const hits = yield* store.getMany(["ns-a", "ns-b", "ns-c"], "k");      expect(hits.size).toBe(2);      expect(hits.get("ns-a")).toBe("a");      expect(hits.get("ns-b")).toBeUndefined();      expect(hits.get("ns-c")).toBe("c");    }),  );   it.effect("empty namespaces returns empty map", () =>    Effect.gen(function* () {      const store = makeInMemoryBlobStore();      const hits = yield* store.getMany([], "k");      expect(hits.size).toBe(0);    }),  );});