import { afterAll, beforeEach, describe, expect, it } from "vitest"; import { FeishuApplicationConnectionService } from "../../src/connections/feishuApplicationConnections.js"; import { scopedFeishuPrincipalId, upsertScopedFeishuIdentity, resolveScopedFeishuIdentity, } from "../../src/feishu/identityNamespace.js"; import { DEFAULT_ORG_ID, prisma, resetDb, seedTestOrganization, testSecretEnvelope } from "./helpers.js"; describe("Feishu connection identity namespace", () => { beforeEach(async () => { await resetDb(); }); afterAll(async () => { await prisma.$disconnect(); }); it("keeps colliding provider-local user and chat ids isolated by connection", async () => { const first = await seedConnection(DEFAULT_ORG_ID, "identity-admin-a", "cli_identity_a"); await seedTestOrganization("org_identity_other", "identity-other"); const second = await seedConnection("org_identity_other", "identity-admin-b", "cli_identity_b"); const [identityA, identityB] = await Promise.all([ upsertScopedFeishuIdentity(prisma, { connectionId: first.id, openId: "ou_collision", displayName: "Teacher A", }), upsertScopedFeishuIdentity(prisma, { connectionId: second.id, openId: "ou_collision", displayName: "Teacher B", }), ]); expect(identityA.userId).not.toBe(identityB.userId); expect(identityA.principalId).not.toBe(identityB.principalId); expect(scopedFeishuPrincipalId("CHAT", first.id, "oc_collision")) .not.toBe(scopedFeishuPrincipalId("CHAT", second.id, "oc_collision")); await expect(resolveScopedFeishuIdentity(prisma, { connectionId: first.id, openId: "ou_collision", })).resolves.toMatchObject({ organizationId: DEFAULT_ORG_ID, userId: identityA.userId }); await expect(resolveScopedFeishuIdentity(prisma, { connectionId: second.id, openId: "ou_collision", })).resolves.toMatchObject({ organizationId: "org_identity_other", userId: identityB.userId }); }); it("fails closed for an Organization mismatch or disabled connection", async () => { const connection = await seedConnection(DEFAULT_ORG_ID, "identity-owner", "cli_identity"); await upsertScopedFeishuIdentity(prisma, { connectionId: connection.id, openId: "ou_teacher", displayName: "Teacher", }); await expect(resolveScopedFeishuIdentity(prisma, { connectionId: connection.id, expectedOrganizationId: "org_wrong", openId: "ou_teacher", })).rejects.toThrow("scope mismatch"); await new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {}).disable({ organizationId: DEFAULT_ORG_ID, actorUserId: "identity-owner", }); await expect(resolveScopedFeishuIdentity(prisma, { connectionId: connection.id, openId: "ou_teacher", })).rejects.toThrow("active Feishu Application Connection not found"); }); it("serializes identity creation behind a concurrent connection disable", async () => { const connection = await seedConnection(DEFAULT_ORG_ID, "identity-owner", "cli_race"); let release!: () => void; const releasePromise = new Promise((resolve) => { release = resolve; }); let locked!: () => void; const lockedPromise = new Promise((resolve) => { locked = resolve; }); const disabler = prisma.$transaction(async (tx) => { await tx.$queryRaw` SELECT "id" FROM "OrganizationFeishuApplicationConnection" WHERE "id" = ${connection.id} FOR UPDATE `; locked(); await releasePromise; await tx.organizationFeishuApplicationConnection.update({ where: { id: connection.id }, data: { status: "DISABLED", disabledAt: new Date() }, }); }, { timeout: 10_000 }); await lockedPromise; const identityAttempt = upsertScopedFeishuIdentity(prisma, { connectionId: connection.id, openId: "ou_racing_user", displayName: "Racing User", }); const stateBeforeRelease = await Promise.race([ identityAttempt.then(() => "settled", () => "settled"), new Promise<"blocked">((resolve) => setTimeout(() => resolve("blocked"), 50)), ]); expect(stateBeforeRelease).toBe("blocked"); release(); await disabler; await expect(identityAttempt).rejects.toThrow("active Feishu Application Connection not found"); await expect(prisma.feishuUserIdentity.count({ where: { connectionId: connection.id, openId: "ou_racing_user" }, })).resolves.toBe(0); }); }); async function seedConnection( organizationId: string, actorUserId: string, appId: string, ): Promise<{ readonly id: string }> { await prisma.user.create({ data: { id: actorUserId, feishuOpenId: `legacy_${actorUserId}`, displayName: actorUserId }, }); await prisma.organizationMembership.create({ data: { organizationId, userId: actorUserId, role: "OWNER" }, }); return new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {}) .rotateCustomerApplication({ organizationId, actorUserId, appId, appSecret: `secret_${appId}`, botOpenId: `ou_bot_${appId}`, }); }