* refactor: apply biome formatting to apps/web (batch 1) Formats apps/web non-modules directories: - apps/web/app - apps/web/lib - apps/web/pages - apps/web/styles - apps/web/server - apps/web/test - Root-level .ts and .mjs files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/modules (batch 2) Formats smaller apps/web/modules directories: - onboarding, data-table, users, apps, auth - integration-attribute-sync, shell, availability - feature-flags, team, webhooks, getting-started - feature-opt-in, troubleshooter, schedules, notifications - signup-view.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/modules (batch 3) Formats medium apps/web/modules directories: - bookings - event-types - insights - embed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/modules/ee (batch 4) Formats apps/web/modules/ee directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web (batch 5) Formats remaining apps/web directories: - apps/web/modules/settings - apps/web/modules/booking-audit - apps/web/playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/components (batch 6) Formats remaining apps/web/components files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to new apps/web files from main Formats newly added/modified files from main merge: - WorkflowStepContainer.tsx (resolved merge conflicts) - Newly moved files (blocklist, form-builder, schedules, etc.) - Other files modified in main Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to new apps/web files from main Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
159 lines
6.2 KiB
TypeScript
159 lines
6.2 KiB
TypeScript
import type { PrismaClient } from "@calcom/prisma";
|
|
import { expect, type Page } from "@playwright/test";
|
|
import { test } from "../lib/fixtures";
|
|
import {
|
|
closeOAuthClientDetails,
|
|
createPendingOAuthClient,
|
|
openOAuthClientDetailsFromList,
|
|
goToAdminOAuthSettings,
|
|
} from "./oauth-client-helpers";
|
|
|
|
async function expectClientStatusInDb(
|
|
prisma: PrismaClient,
|
|
clientId: string,
|
|
status: "PENDING" | "APPROVED" | "REJECTED"
|
|
) {
|
|
await expect
|
|
.poll(async () => {
|
|
const row = await prisma.oAuthClient.findUnique({
|
|
where: { clientId },
|
|
select: { status: true },
|
|
});
|
|
|
|
return row?.status ?? null;
|
|
})
|
|
.toBe(status);
|
|
}
|
|
|
|
async function waitForAdminSection(page: Page, sectionTestId: string) {
|
|
const section = page.getByTestId(sectionTestId);
|
|
await expect(section).toBeVisible({ timeout: 60_000 });
|
|
return section;
|
|
}
|
|
|
|
async function expectClientInAdminSection(
|
|
page: Page,
|
|
sectionTestId: string,
|
|
clientId: string
|
|
): Promise<void> {
|
|
const section = await waitForAdminSection(page, sectionTestId);
|
|
await expect(section.getByTestId(`oauth-client-list-item-${clientId}`)).toBeVisible({ timeout: 60_000 });
|
|
}
|
|
|
|
async function expectClientNotInAdminSection(
|
|
page: Page,
|
|
sectionTestId: string,
|
|
clientId: string
|
|
): Promise<void> {
|
|
const section = await waitForAdminSection(page, sectionTestId);
|
|
await expect(section.getByTestId(`oauth-client-list-item-${clientId}`)).toHaveCount(0, { timeout: 60_000 });
|
|
}
|
|
|
|
test.describe.configure({ mode: "parallel" });
|
|
|
|
test.describe("OAuth clients admin", () => {
|
|
test.afterEach(async ({ users, prisma }, testInfo) => {
|
|
const testPrefix = `e2e-oauth-client-admin-${testInfo.testId}-`;
|
|
|
|
await prisma.oAuthClient.deleteMany({
|
|
where: {
|
|
name: {
|
|
startsWith: testPrefix,
|
|
},
|
|
},
|
|
});
|
|
|
|
await users.deleteAll();
|
|
});
|
|
|
|
test("can manage pending/approved/rejected clients and can create approved clients as admin", async ({
|
|
page,
|
|
prisma,
|
|
users,
|
|
}, testInfo) => {
|
|
const adminUser = await users.create({ role: "ADMIN" });
|
|
await adminUser.apiLogin();
|
|
|
|
const testPrefix = `e2e-oauth-client-admin-${testInfo.testId}-`;
|
|
|
|
const makeClientInput = (name: string) => ({
|
|
name,
|
|
purpose: "Used for E2E testing (admin)",
|
|
redirectUri: "https://example.com/callback",
|
|
websiteUrl: "https://example.com",
|
|
logoFileName: "cal.png",
|
|
});
|
|
|
|
const pending1Name = `${testPrefix}pending-1-${Date.now()}`;
|
|
const pending2Name = `${testPrefix}pending-2-${Date.now()}`;
|
|
const pending3Name = `${testPrefix}pending-3-${Date.now()}`;
|
|
|
|
const toBeApproved = await createPendingOAuthClient(page, makeClientInput(pending1Name));
|
|
const toBeRejected = await createPendingOAuthClient(page, makeClientInput(pending2Name));
|
|
const staysPending = await createPendingOAuthClient(page, makeClientInput(pending3Name));
|
|
|
|
await goToAdminOAuthSettings(page);
|
|
|
|
await expectClientInAdminSection(page, "oauth-client-admin-pending-section", toBeApproved.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-pending-section", toBeRejected.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-pending-section", staysPending.clientId);
|
|
|
|
// Preview a pending client (readonly)
|
|
const pendingDetails = await openOAuthClientDetailsFromList(page, toBeApproved.clientId);
|
|
|
|
await expect(pendingDetails.locator("#name")).toBeDisabled();
|
|
await expect(pendingDetails.locator("#purpose")).toBeDisabled();
|
|
await expect(pendingDetails.locator("#redirectUri")).toBeDisabled();
|
|
await expect(pendingDetails.locator("#websiteUrl")).toBeDisabled();
|
|
|
|
// Upload/delete actions should be hidden for admin view
|
|
await expect(page.getByTestId("open-upload-oauth-client-logo-dialog")).toHaveCount(0);
|
|
await expect(page.getByTestId("oauth-client-details-delete-trigger")).toHaveCount(0);
|
|
|
|
// Approve pending1
|
|
await page.getByTestId("oauth-client-details-approve-trigger").click();
|
|
await closeOAuthClientDetails(page);
|
|
|
|
await expectClientStatusInDb(prisma, toBeApproved.clientId, "APPROVED");
|
|
|
|
await expectClientNotInAdminSection(page, "oauth-client-admin-pending-section", toBeApproved.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-approved-section", toBeApproved.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-pending-section", staysPending.clientId);
|
|
|
|
// Reject pending2
|
|
const rejectionReason = `Not acceptable (${testPrefix}reason-${Date.now()})`;
|
|
await openOAuthClientDetailsFromList(page, toBeRejected.clientId);
|
|
await page.getByTestId("oauth-client-details-reject-trigger").click();
|
|
await page.getByTestId("oauth-client-details-rejection-reason").fill(rejectionReason);
|
|
await page.getByTestId("oauth-client-details-reject-confirm").click();
|
|
await closeOAuthClientDetails(page);
|
|
|
|
await expectClientStatusInDb(prisma, toBeRejected.clientId, "REJECTED");
|
|
|
|
await expectClientNotInAdminSection(page, "oauth-client-admin-pending-section", toBeRejected.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-rejected-section", toBeRejected.clientId);
|
|
await expect(page.getByTestId("toast-success").first()).toBeVisible();
|
|
await page.reload();
|
|
|
|
await page
|
|
.getByTestId("oauth-client-admin-rejected-section")
|
|
.getByTestId(`oauth-client-list-item-${toBeRejected.clientId}`)
|
|
.click();
|
|
|
|
const rejectedDetails = page.getByTestId("oauth-client-details-form");
|
|
await expect(rejectedDetails).toBeVisible();
|
|
await expect(rejectedDetails.getByTestId("oauth-client-details-rejection-reason-display")).toContainText(
|
|
rejectionReason
|
|
);
|
|
await closeOAuthClientDetails(page);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-pending-section", staysPending.clientId);
|
|
|
|
// Reload to ensure list queries show consistent counts
|
|
await page.reload();
|
|
|
|
await expectClientInAdminSection(page, "oauth-client-admin-approved-section", toBeApproved.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-rejected-section", toBeRejected.clientId);
|
|
await expectClientInAdminSection(page, "oauth-client-admin-pending-section", staysPending.clientId);
|
|
});
|
|
});
|