e2e/project-creator-deletion.spec.ts
Rama main · réplica de lectura del repositorio original.
import { test, expect } from "@playwright/test";
import { TestApiClient } from "./fixtures";
test("members can delete their own shared project while keeping its issues", async ({ page }, testInfo) => {
const owner = new TestApiClient();
const member = new TestApiClient();
const suffix = `${Date.now()}-${testInfo.workerIndex}`;
const memberEmail = `project-member-${suffix}@multica.ai`;
const apiBase = process.env.NEXT_PUBLIC_API_URL || `http://localhost:${process.env.PORT || "8080"}`;
await owner.login(`project-owner-${suffix}@multica.ai`, "Project owner");
const workspace = await owner.ensureWorkspace("Project permissions", `project-permissions-${suffix}`);
try {
await member.login(memberEmail, "Project member");
await owner.addMemberByEmail(memberEmail);
member.setWorkspaceId(workspace.id);
member.setWorkspaceSlug(workspace.slug);
await member.markUserOnboarded();
const ownProject = await member.createProject("My temporary project");
const otherProject = await owner.createProject("A teammate's project");
const issue = await owner.createIssue("Keep this team's issue", { project_id: ownProject.id });
const token = member.getToken();
expect(token).toBeTruthy();
await page.addInitScript((value) => {
localStorage.setItem("multica_token", value!);
localStorage.setItem("multica:chat:isOpen", "false");
}, token);
await page.goto(`/${workspace.slug}/projects/${otherProject.id}`, { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: otherProject.title, exact: true }).click({ timeout: 30000 });
await expect(page.getByRole("menuitem", { name: "Copy link", exact: true })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "Delete project", exact: true })).toHaveCount(0);
const headers = { Authorization: `Bearer ${token}`, "X-Workspace-ID": workspace.id };
const denied = await page.request.delete(`${apiBase}/api/projects/${otherProject.id}`, { headers });
expect(denied.status()).toBe(403);
await page.goto(`/${workspace.slug}/projects/${ownProject.id}`, { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: ownProject.title, exact: true }).click();
await expect(page.getByRole("menuitem", { name: "Delete project", exact: true })).toBeVisible();
const screenshot = testInfo.outputPath("creator-delete-menu.png");
await page.screenshot({ path: screenshot });
await testInfo.attach("Creator can delete their project", { path: screenshot, contentType: "image/png" });
await page.getByRole("menuitem", { name: "Delete project", exact: true }).click();
const confirmation = page.getByRole("alertdialog", { name: "Delete project", exact: true });
await expect(confirmation).toBeVisible();
const deleted = page.waitForResponse((response) =>
response.url().endsWith(`/api/projects/${ownProject.id}`) && response.request().method() === "DELETE",
);
await confirmation.getByRole("button", { name: "Delete", exact: true }).click();
expect((await deleted).status()).toBe(204);
await expect(page).toHaveURL(new RegExp(`/${workspace.slug}/projects$`));
const retainedIssue = await page.request.get(`${apiBase}/api/issues/${issue.id}`, { headers });
expect(retainedIssue.status()).toBe(200);
expect(await retainedIssue.json()).toMatchObject({ id: issue.id, project_id: null });
} finally {
await member.cleanup();
await owner.cleanup();
await owner.deleteWorkspace();
}
});