225 lines · 7.3 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect";import {  makeInMemoryBlobStore,  pluginBlobStore,  type PluginStorageCollectionDefinition,  type PluginStorageEntry,  type PluginStorageFacade,} from "@executor-js/sdk"; import type { AppDescriptor } from "../pipeline/descriptor";import { descriptorCollection, makeAppsStore, toolCollection } from "./store"; const entry = <T>(input: {  owner: "org" | "user";  collection: string;  key: string;  data: T;}): PluginStorageEntry<T> => ({  id: `${input.collection}:${input.key}`,  owner: input.owner,  pluginId: "apps",  collection: input.collection,  key: input.key,  data: input.data,  createdAt: new Date(0),  updatedAt: new Date(0),}); const makeMemoryPluginStorage = (): PluginStorageFacade => {  const rows = new Map<string, PluginStorageEntry<unknown>>();  const rowKey = (owner: string, collection: string, key: string) =>    `${owner}:${collection}:${key}`;  const put = (input: {    readonly owner: "org" | "user";    readonly collection: string;    readonly key: string;    readonly data: object;  }) =>    Effect.sync(() => {      const next = entry({        owner: input.owner,        collection: input.collection,        key: input.key,        data: input.data,      });      rows.set(rowKey(input.owner, input.collection, input.key), next);      return next;    });  return {    collection: <const TDefinition extends PluginStorageCollectionDefinition>(      definition: TDefinition,    ) => ({      get: ({ key }) =>        Effect.sync(() => {          for (const row of rows.values()) {            if (row.collection === definition.name && row.key === key) return row as never;          }          return null;        }),      getForOwner: ({ owner, key }) =>        Effect.sync(          () => (rows.get(rowKey(owner, definition.name, key)) as never | undefined) ?? null,        ),      list: ({ keyPrefix } = {}) =>        Effect.sync(          () =>            [...rows.values()].filter(              (row) =>                row.collection === definition.name &&                (keyPrefix === undefined || row.key.startsWith(keyPrefix)),            ) as never,        ),      put: (input) => put({ ...input, collection: definition.name }) as never,      query: (input = {}) =>        Effect.sync(() => {          let out = [...rows.values()].filter((row) => row.collection === definition.name);          if (input.where) {            out = out.filter((row) =>              Object.entries(input.where ?? {}).every(([field, expected]) => {                const actual = (row.data as Record<string, unknown>)[field];                return typeof expected === "object" && expected !== null && "eq" in expected                  ? actual === expected.eq                  : actual === expected;              }),            );          }          return (input.limit ? out.slice(0, input.limit) : out) as never;        }),      count: () => Effect.sync(() => 0),      remove: ({ owner, key }) =>        Effect.sync(() => {          rows.delete(rowKey(owner, definition.name, key));        }),    }),    get: ({ collection, key }) =>      Effect.sync(() => {        for (const row of rows.values()) {          if (row.collection === collection && row.key === key) return row as never;        }        return null;      }),    getForOwner: ({ owner, collection, key }) =>      Effect.sync(() => (rows.get(rowKey(owner, collection, key)) as never | undefined) ?? null),    list: ({ collection, keyPrefix }) =>      Effect.sync(        () =>          [...rows.values()].filter(            (row) =>              row.collection === collection &&              (keyPrefix === undefined || row.key.startsWith(keyPrefix)),          ) as never,      ),    put: (input) => put(input as never) as never,    putMany: ({ owner, entries }) =>      Effect.sync(() => {        for (const item of entries) {          const next = entry({            owner,            collection: item.collection,            key: item.key,            data: item.data,          });          rows.set(rowKey(owner, item.collection, item.key), next);        }      }),    remove: ({ owner, collection, key }) =>      Effect.sync(() => {        rows.delete(rowKey(owner, collection, key));      }),    removeMany: ({ owner, entries }) =>      Effect.sync(() => {        for (const item of entries) rows.delete(rowKey(owner, item.collection, item.key));      }),  };}; const descriptor = (tools: readonly AppDescriptor["tools"][number][]): AppDescriptor => ({  version: 6,  app: "crm",  sourceRef: "sha-1",  publishedAt: 1,  toolchain: {    bundler: { name: "esbuild", version: "0" },    executor: { name: "test", version: "0" },    target: "es2022",  },  tools,  workflows: [],  ui: [],  skills: [],  skipped: [],}); const tool = (name: string): AppDescriptor["tools"][number] => ({  name,  sourcePath: `tools/${name}.ts`,  bundleKey: `bundle:${name}`,  source: { path: `tools/${name}.ts`, sourceHash: name },  description: name,  integrations: {},}); describe("apps store", () => {  it.effect("tombstones removed tools", () =>    Effect.gen(function* () {      const store = makeAppsStore({        blobs: pluginBlobStore(makeInMemoryBlobStore(), { org: "tenant", user: null }, "apps"),        pluginStorage: makeMemoryPluginStorage(),      });      yield* store.putPublished(        descriptor([tool("first"), tool("second")]),        "descriptor",        "org",        null,      );      yield* store.putPublished(descriptor([tool("first")]), "descriptor-2", "org", "sha-1");      const active = yield* store.listActiveTools();      expect(active.map((item) => item.name)).toEqual(["first"]);      expect(active.map((item) => item.app)).toEqual(["crm"]);    }),  );   it.effect("removes a published app catalog", () =>    Effect.gen(function* () {      const store = makeAppsStore({        blobs: pluginBlobStore(makeInMemoryBlobStore(), { org: "tenant", user: null }, "apps"),        pluginStorage: makeMemoryPluginStorage(),      });      yield* store.putPublished(        descriptor([tool("first"), tool("second")]),        "descriptor",        "org",        null,      );      yield* store.removePublished("crm", "org");      const active = yield* store.listActiveTools();      const descriptorRecord = yield* store.getDescriptorRecord("crm");      expect(active).toEqual([]);      expect(descriptorRecord).toBeNull();    }),  );   it.effect("keeps owner blob partitions isolated", () =>    Effect.gen(function* () {      const blobStore = makeInMemoryBlobStore();      const orgStore = makeAppsStore({        blobs: pluginBlobStore(blobStore, { org: "tenant-a", user: null }, "apps"),        pluginStorage: makeMemoryPluginStorage(),      });      const otherStore = makeAppsStore({        blobs: pluginBlobStore(blobStore, { org: "tenant-b", user: null }, "apps"),        pluginStorage: makeMemoryPluginStorage(),      });      const key = yield* orgStore.putBlob("same app", "org");      expect(yield* orgStore.getBlob(key)).toBe("same app");      expect(yield* otherStore.getBlob(key)).toBeNull();    }),  );   it("declares plugin storage collections", () => {    expect(descriptorCollection.name).toBe("apps_descriptors");    expect(toolCollection.name).toBe("apps_tools");  });});