76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
// @vitest-environment node
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
row: null as null | {
|
|
id: string;
|
|
appId: string;
|
|
label: string;
|
|
connectionStatus: "active" | "degraded" | "broken" | "revoked" | "pending";
|
|
credentialsCiphertext: string | null;
|
|
},
|
|
decryptTenantField: vi.fn(async () => JSON.stringify({ accessToken: "fixture-token" })),
|
|
}));
|
|
|
|
vi.mock("~/utils/db/.server/tenantRls.server", () => ({
|
|
withTenantRLS: async (_tenantId: string, callback: (tx: unknown) => Promise<unknown>) =>
|
|
callback({ dataConnection: { findFirst: vi.fn(async () => mocks.row) } }),
|
|
}));
|
|
vi.mock("@hivegrid/db/crypto/tenant-envelope.server", () => ({
|
|
decryptTenantField: mocks.decryptTenantField,
|
|
encryptTenantField: vi.fn(),
|
|
}));
|
|
vi.mock("@hivegrid/db/audit-events.server", () => ({ createAuditEvent: vi.fn() }));
|
|
vi.mock("@hivegrid/db/notifications.server", () => ({ createNotificationDelivery: vi.fn() }));
|
|
vi.mock("~/utils/db.server", () => ({ db: {} }));
|
|
vi.mock("@hivegrid/integrations", () => ({ resolveAdapter: vi.fn() }));
|
|
|
|
import { AppsEngineServiceError, runWithConnectionCredentials } from "./appsEngineService.server";
|
|
|
|
describe("runWithConnectionCredentials", () => {
|
|
beforeEach(() => {
|
|
mocks.decryptTenantField.mockClear();
|
|
mocks.row = {
|
|
id: "connection-1",
|
|
appId: "aircall",
|
|
label: "Aircall",
|
|
connectionStatus: "active",
|
|
credentialsCiphertext: "sealed",
|
|
};
|
|
});
|
|
|
|
it("decrypts only after a tenant-scoped app match and passes frozen credentials to the callback", async () => {
|
|
const result = await runWithConnectionCredentials(
|
|
{ tenantId: "tenant-1", connectionId: "connection-1", expectedAppId: "aircall" },
|
|
async (credentials, connection) => ({ credentials, connection }),
|
|
);
|
|
expect(result.connection).toEqual({ id: "connection-1", appId: "aircall", label: "Aircall" });
|
|
expect(result.credentials).toEqual({ accessToken: "fixture-token" });
|
|
expect(Object.isFrozen(result.credentials)).toBe(true);
|
|
expect(mocks.decryptTenantField).toHaveBeenCalledWith(
|
|
"tenant-1",
|
|
"sealed",
|
|
expect.objectContaining({ client: expect.objectContaining({ dataConnection: expect.any(Object) }) }),
|
|
);
|
|
});
|
|
|
|
it("hides a connection belonging to another app", async () => {
|
|
await expect(
|
|
runWithConnectionCredentials({ tenantId: "tenant-1", connectionId: "connection-1", expectedAppId: "google" }, async () => undefined),
|
|
).rejects.toMatchObject({ status: 404 });
|
|
expect(mocks.decryptTenantField).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects revoked or credential-less connections with a Klartext key", async () => {
|
|
mocks.row = { ...mocks.row!, connectionStatus: "revoked", credentialsCiphertext: null };
|
|
await expect(
|
|
runWithConnectionCredentials(
|
|
{ tenantId: "tenant-1", connectionId: "connection-1", expectedAppId: "aircall" },
|
|
async () => undefined,
|
|
),
|
|
).rejects.toEqual(
|
|
expect.objectContaining<Partial<AppsEngineServiceError>>({ messageKey: "integrations.error.connectionUnavailable" }),
|
|
);
|
|
});
|
|
});
|