240 lines
8.3 KiB
TypeScript
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();
|
|
});
|
|
});
|