Files
curriculum-project-hub/hub/test/integration/admin-provider-connections.test.ts

215 lines
8.1 KiB
TypeScript

import Fastify from "fastify";
import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { registerAdminPlugin } from "../../src/admin/plugin.js";
import { ProviderConnectionService } from "../../src/connections/providerConnections.js";
import { mintSessionToken, sessionCookieHeader } from "../../src/admin/routes/authRoutes.js";
import { DEFAULT_ORG_ID, prisma, resetDb, testSecretEnvelope } from "./helpers.js";
const SESSION_SECRET = "integration-test-session-secret";
const secrets = testSecretEnvelope;
async function buildApp() {
const app = Fastify({ logger: false });
await registerAdminPlugin(app, {
prisma,
sessionSecret: SESSION_SECRET,
publicBaseUrl: "http://127.0.0.1:8788",
feishuAppId: "cli_test",
feishuAppSecret: "secret_test",
projectWorkspaceRoot: "/tmp/cph-test-workspaces",
secretEnvelope: secrets,
providerReadinessProbe: async () => {},
cookieSecure: false,
});
await app.ready();
return app;
}
async function seedUser(role: "OWNER" | "ADMIN" | "MEMBER"): Promise<string> {
const id = `u-${role.toLowerCase()}`;
const openId = `ou_${role.toLowerCase()}`;
await prisma.user.create({ data: { id, feishuOpenId: openId, displayName: id } });
await prisma.organizationMembership.create({
data: { organizationId: DEFAULT_ORG_ID, userId: id, role },
});
return mintSessionToken({ userId: id, feishuOpenId: openId }, SESSION_SECRET);
}
describe("Organization BYOK provider connections", () => {
beforeEach(async () => {
await resetDb();
});
afterAll(async () => {
await prisma.$disconnect();
});
it("stores write-only credentials in an encrypted immutable version", async () => {
const token = await seedUser("OWNER");
const app = await buildApp();
const credential = {
baseUrl: "https://provider.example/api",
authToken: "org-a-auth-token-never-return",
anthropicApiKey: "org-a-api-key-never-return",
};
try {
const response = await app.inject({
method: "PUT",
url: "/api/org/test-default/provider-connections/openrouter",
headers: {
cookie: sessionCookieHeader(token),
"content-type": "application/json",
},
payload: credential,
});
expect(response.statusCode).toBe(201);
expect(response.json()).toMatchObject({
providerId: "openrouter",
mode: "BYOK",
status: "ACTIVE",
activeVersion: 1,
keyId: "test-active",
});
expect(response.body).not.toContain(credential.authToken);
expect(response.body).not.toContain(credential.anthropicApiKey);
expect(response.body).not.toContain(credential.baseUrl);
const persisted = await prisma.$queryRaw<Array<{
envelope: unknown;
activeSecretVersionId: string;
}>>`
SELECT v."envelope", c."activeSecretVersionId"
FROM "ProviderCredentialVersion" v
JOIN "OrganizationProviderConnection" c ON c."id" = v."connectionId"
WHERE c."organizationId" = ${DEFAULT_ORG_ID} AND c."providerId" = 'openrouter'
`;
expect(persisted).toHaveLength(1);
const serialized = JSON.stringify(persisted);
expect(serialized).not.toContain(credential.authToken);
expect(serialized).not.toContain(credential.anthropicApiKey);
expect(serialized).not.toContain(credential.baseUrl);
expect(persisted[0]?.activeSecretVersionId).toBeTruthy();
} finally {
await app.close();
}
});
it("rotates by appending a version and never exposes old or new plaintext", async () => {
const token = await seedUser("ADMIN");
const app = await buildApp();
const cookie = sessionCookieHeader(token);
try {
const first = await app.inject({
method: "PUT",
url: "/api/org/test-default/provider-connections/openrouter",
headers: { cookie, "content-type": "application/json" },
payload: { baseUrl: "https://one.example", authToken: "first-secret" },
});
const second = await app.inject({
method: "PUT",
url: "/api/org/test-default/provider-connections/openrouter",
headers: { cookie, "content-type": "application/json" },
payload: { baseUrl: "https://two.example", authToken: "second-secret" },
});
expect(first.statusCode).toBe(201);
expect(second.statusCode).toBe(200);
expect(second.json()).toMatchObject({ activeVersion: 2, status: "ACTIVE" });
const versions = await prisma.$queryRaw<Array<{ version: number; retiredAt: Date | null }>>`
SELECT v."version", v."retiredAt"
FROM "ProviderCredentialVersion" v
JOIN "OrganizationProviderConnection" c ON c."id" = v."connectionId"
WHERE c."organizationId" = ${DEFAULT_ORG_ID}
ORDER BY v."version"
`;
expect(versions).toEqual([
{ version: 1, retiredAt: expect.any(Date) },
{ version: 2, retiredAt: null },
]);
const read = await app.inject({
method: "GET",
url: "/api/org/test-default/provider-connections",
headers: { cookie },
});
expect(read.statusCode).toBe(200);
expect(read.json()).toEqual({ connections: [expect.objectContaining({ activeVersion: 2 })] });
expect(read.body).not.toContain("first-secret");
expect(read.body).not.toContain("second-secret");
} finally {
await app.close();
}
});
it("rejects members and refuses to overwrite a platform-managed connection", async () => {
const memberToken = await seedUser("MEMBER");
const app = await buildApp();
try {
const denied = await app.inject({
method: "PUT",
url: "/api/org/test-default/provider-connections/openrouter",
headers: {
cookie: sessionCookieHeader(memberToken),
"content-type": "application/json",
},
payload: { baseUrl: "https://provider.example", authToken: "denied-secret" },
});
expect(denied.statusCode).toBe(403);
const service = new ProviderConnectionService(prisma, secrets, async () => {});
await expect(service.rotateByok({
organizationId: DEFAULT_ORG_ID,
providerId: "another-provider",
actorUserId: "u-member",
baseUrl: "https://provider.example",
authToken: "service-bypass-secret",
})).rejects.toThrow("requires an active Organization OWNER or ADMIN");
await expect(prisma.organizationProviderConnection.count({
where: { organizationId: DEFAULT_ORG_ID, providerId: "another-provider" },
})).resolves.toBe(0);
await prisma.$executeRaw`
INSERT INTO "OrganizationProviderConnection"
("id", "organizationId", "providerId", "mode", "status", "createdAt", "updatedAt")
VALUES
('platform-connection', ${DEFAULT_ORG_ID}, 'openrouter', 'PLATFORM_MANAGED', 'DRAFT', NOW(), NOW())
`;
const ownerToken = await seedUser("OWNER");
const refused = await app.inject({
method: "PUT",
url: "/api/org/test-default/provider-connections/openrouter",
headers: {
cookie: sessionCookieHeader(ownerToken),
"content-type": "application/json",
},
payload: { baseUrl: "https://provider.example", authToken: "takeover-secret" },
});
expect(refused.statusCode).toBe(403);
expect(refused.json()).toMatchObject({
error: { code: "forbidden" },
});
} finally {
await app.close();
}
});
it("does not activate or persist a credential that fails readiness", async () => {
await seedUser("ADMIN");
const service = new ProviderConnectionService(prisma, secrets, async () => {
throw new Error("provider credential readiness check failed: status 401");
});
await expect(service.rotateByok({
organizationId: DEFAULT_ORG_ID,
providerId: "openrouter",
actorUserId: "u-admin",
baseUrl: "https://provider.example/api",
authToken: "rejected-secret",
})).rejects.toThrow("readiness check failed");
await expect(prisma.organizationProviderConnection.count({
where: { organizationId: DEFAULT_ORG_ID, providerId: "openrouter" },
})).resolves.toBe(0);
await expect(prisma.providerCredentialVersion.count()).resolves.toBe(0);
});
});