Files
triggerdotdev--trigger.dev/apps/webapp/test/organizationDataStoresRegistry.test.ts
2026-07-13 13:32:57 +08:00

240 lines
8.3 KiB
TypeScript

import { describe, expect } from "vitest";
vi.mock("~/db.server", () => ({ prisma: {}, $replica: {} }));
import { postgresTest } from "@internal/testcontainers";
import { OrganizationDataStoresRegistry } from "~/services/dataStores/organizationDataStoresRegistry.server";
import { ClickhouseConnectionSchema } from "~/services/clickhouse/clickhouseSecretSchemas.server";
vi.setConfig({ testTimeout: 60_000 });
const TEST_URL = "https://default:password@clickhouse.example.com:8443";
const TEST_URL_2 = "https://default:password@clickhouse2.example.com:8443";
describe("OrganizationDataStoresRegistry", () => {
postgresTest("isLoaded is false before loadFromDatabase", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
expect(registry.isLoaded).toBe(false);
expect(registry.get("any-org", "CLICKHOUSE")).toBeNull();
});
postgresTest("isLoaded is true after loadFromDatabase", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.loadFromDatabase();
expect(registry.isLoaded).toBe(true);
});
postgresTest("isReady resolves after loadFromDatabase", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
let resolved = false;
registry.isReady.then(() => {
resolved = true;
});
await registry.loadFromDatabase();
await registry.isReady;
expect(resolved).toBe(true);
});
postgresTest("get returns null when no data stores exist", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.loadFromDatabase();
expect(registry.get("org-1", "CLICKHOUSE")).toBeNull();
});
postgresTest("addDataStore creates a row and stores the secret", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "test-store",
kind: "CLICKHOUSE",
organizationIds: ["org-1", "org-2"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
const row = await prisma.organizationDataStore.findFirst({ where: { key: "test-store" } });
expect(row).not.toBeNull();
expect(row?.organizationIds).toEqual(["org-1", "org-2"]);
expect(row?.kind).toBe("CLICKHOUSE");
const secret = await prisma.secretStore.findFirst({
where: { key: "data-store:test-store:clickhouse" },
});
expect(secret).not.toBeNull();
});
postgresTest(
"loadFromDatabase resolves secrets and makes orgs available via get",
async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "hipaa-store",
kind: "CLICKHOUSE",
organizationIds: ["org-hipaa"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.loadFromDatabase();
const result = registry.get("org-hipaa", "CLICKHOUSE");
expect(result).not.toBeNull();
expect(result?.kind).toBe("CLICKHOUSE");
expect(result?.url).toBe(TEST_URL);
}
);
postgresTest("get returns null for orgs not in any data store", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "partial-store",
kind: "CLICKHOUSE",
organizationIds: ["org-a"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.loadFromDatabase();
expect(registry.get("org-a", "CLICKHOUSE")).not.toBeNull();
expect(registry.get("org-b", "CLICKHOUSE")).toBeNull();
});
postgresTest("multiple orgs can share the same data store", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "shared-store",
kind: "CLICKHOUSE",
organizationIds: ["org-x", "org-y", "org-z"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.loadFromDatabase();
const x = registry.get("org-x", "CLICKHOUSE");
const y = registry.get("org-y", "CLICKHOUSE");
const z = registry.get("org-z", "CLICKHOUSE");
expect(x?.url).toBe(TEST_URL);
expect(y?.url).toBe(TEST_URL);
expect(z?.url).toBe(TEST_URL);
});
postgresTest(
"when an org appears in multiple data stores, first row by id asc wins",
async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
const sharedOrg = "org-dup-overlap";
await registry.addDataStore({
key: "dup-overlap-first",
kind: "CLICKHOUSE",
organizationIds: [sharedOrg],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.addDataStore({
key: "dup-overlap-second",
kind: "CLICKHOUSE",
organizationIds: [sharedOrg],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL_2 }),
});
const [winner] = await prisma.organizationDataStore.findMany({
where: { key: { in: ["dup-overlap-first", "dup-overlap-second"] } },
orderBy: { id: "asc" },
});
expect(winner).toBeDefined();
await registry.loadFromDatabase();
const expectedUrl = winner!.key === "dup-overlap-first" ? TEST_URL : TEST_URL_2;
expect(registry.get(sharedOrg, "CLICKHOUSE")?.url).toBe(expectedUrl);
}
);
postgresTest(
"updateDataStore updates organizationIds and rotates the secret",
async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "update-store",
kind: "CLICKHOUSE",
organizationIds: ["org-old"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.updateDataStore({
key: "update-store",
kind: "CLICKHOUSE",
organizationIds: ["org-new-1", "org-new-2"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL_2 }),
});
const row = await prisma.organizationDataStore.findFirst({ where: { key: "update-store" } });
expect(row?.organizationIds).toEqual(["org-new-1", "org-new-2"]);
await registry.loadFromDatabase();
expect(registry.get("org-new-1", "CLICKHOUSE")?.url).toBe(TEST_URL_2);
expect(registry.get("org-old", "CLICKHOUSE")).toBeNull();
}
);
postgresTest("reload picks up changes made after initial load", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.loadFromDatabase();
expect(registry.get("org-reload", "CLICKHOUSE")).toBeNull();
await registry.addDataStore({
key: "reload-store",
kind: "CLICKHOUSE",
organizationIds: ["org-reload"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
expect(registry.get("org-reload", "CLICKHOUSE")).toBeNull();
await registry.reload();
expect(registry.get("org-reload", "CLICKHOUSE")?.url).toBe(TEST_URL);
});
postgresTest("deleteDataStore removes the row and its secret", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "delete-store",
kind: "CLICKHOUSE",
organizationIds: ["org-delete"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.deleteDataStore({ key: "delete-store", kind: "CLICKHOUSE" });
expect(
await prisma.organizationDataStore.findFirst({ where: { key: "delete-store" } })
).toBeNull();
expect(
await prisma.secretStore.findFirst({ where: { key: "data-store:delete-store:clickhouse" } })
).toBeNull();
});
postgresTest("after delete and reload, org no longer has a data store", async ({ prisma }) => {
const registry = new OrganizationDataStoresRegistry(prisma, prisma);
await registry.addDataStore({
key: "ephemeral-store",
kind: "CLICKHOUSE",
organizationIds: ["org-ephemeral"],
config: ClickhouseConnectionSchema.parse({ url: TEST_URL }),
});
await registry.loadFromDatabase();
expect(registry.get("org-ephemeral", "CLICKHOUSE")?.url).toBe(TEST_URL);
await registry.deleteDataStore({ key: "ephemeral-store", kind: "CLICKHOUSE" });
await registry.reload();
expect(registry.get("org-ephemeral", "CLICKHOUSE")).toBeNull();
});
});