import { test, expect } from "@playwright/test"; async function signupAndGetAccessToken( request: import("@playwright/test").APIRequestContext, email: string, password: string, ): Promise { const signupRes = await request.post("/api/auth/signup", { headers: { "Content-Type": "application/json" }, data: { email, password }, }); const signupStatus = signupRes.status(); if (signupStatus !== 201) { // 디버깅용: CI 등에서 500 이 떨어질 때 응답 바디를 함께 로그로 출력해 원인을 파악할 수 있게 한다. console.log("[project-status-public-page] signup error status=", signupStatus); try { const bodyText = await signupRes.text(); console.log("[project-status-public-page] signup error body=", bodyText); } catch (e) { console.log("[project-status-public-page] signup error: body read failed", e); } } expect(signupStatus).toBe(201); const setCookieHeader = signupRes.headers()["set-cookie"]; expect(setCookieHeader).toBeTruthy(); const cookieHeaderString = Array.isArray(setCookieHeader) ? setCookieHeader.join("; ") : (setCookieHeader as string); const match = cookieHeaderString.match(/pb_access=([^;]+)/); expect(match).not.toBeNull(); const accessToken = match![1]; return accessToken; } async function createProject( request: import("@playwright/test").APIRequestContext, accessToken: string, params: { slug: string; title: string; contentJson?: any }, ) { const { slug, title, contentJson = [] } = params; const res = await request.post("/api/projects", { headers: { "Content-Type": "application/json", Cookie: `pb_access=${accessToken}`, }, data: { title, slug, contentJson, }, }); const status = res.status(); if (status !== 201) { console.log("[project-status-public-page] createProject error status=", status); try { const bodyText = await res.text(); console.log("[project-status-public-page] createProject error body=", bodyText); } catch (e) { console.log("[project-status-public-page] createProject error: body read failed", e); } } expect(status).toBe(201); } async function patchProjectStatus( request: import("@playwright/test").APIRequestContext, accessToken: string, slug: string, status: "DRAFT" | "PUBLISHED" | "ARCHIVED", ) { const res = await request.patch(`/api/projects/${slug}`, { headers: { "Content-Type": "application/json", Cookie: `pb_access=${accessToken}`, }, data: { status }, }); const code = res.status(); if (code !== 200) { console.log("[project-status-public-page] patchProjectStatus error status=", code); try { const bodyText = await res.text(); console.log("[project-status-public-page] patchProjectStatus error body=", bodyText); } catch (e) { console.log("[project-status-public-page] patchProjectStatus error: body read failed", e); } } expect(code).toBe(200); } // 프로젝트 status 에 따른 퍼블릭 페이지(/p/[slug]) 기본 동작을 검증하는 E2E. // - DRAFT: 404 // - PUBLISHED: 200 test.describe("Public project status behaviour", () => { test("DRAFT 프로젝트는 퍼블릭 페이지에서 404 를 반환해야 한다", async ({ page, request }) => { const now = Date.now(); const email = `status-draft-${now}@example.com`; const password = "status-draft-password"; const slug = `status-draft-${now}`; const accessToken = await signupAndGetAccessToken(request, email, password); await createProject(request, accessToken, { slug, title: "Status DRAFT project", contentJson: [], }); const response = await page.goto(`/p/${slug}`); expect(response?.status()).toBe(404); }); test("PUBLISHED 프로젝트는 퍼블릭 페이지에서 200 으로 열려야 한다", async ({ page, request }) => { const now = Date.now(); const email = `status-published-${now}@example.com`; const password = "status-published-password"; const slug = `status-published-${now}`; const accessToken = await signupAndGetAccessToken(request, email, password); await createProject(request, accessToken, { slug, title: "Status PUBLISHED project", contentJson: [], }); await patchProjectStatus(request, accessToken, slug, "PUBLISHED"); const response = await page.goto(`/p/${slug}`); expect(response?.status()).toBe(200); }); test("ARCHIVED 프로젝트는 퍼블릭 페이지에서 폼은 보이지만 제출이 차단되어야 한다", async ({ page, request }) => { const now = Date.now(); const email = `status-archived-${now}@example.com`; const password = "status-archived-password"; const slug = `status-archived-${now}`; const accessToken = await signupAndGetAccessToken(request, email, password); const inputId = "field_name"; const buttonId = "submit_btn"; const formId = "form_controller_1"; const disabledMessage = "Submission is disabled for this project."; const contentJson = [ { id: inputId, type: "formInput", props: { formFieldName: "name", label: "Name", inputType: "text", labelDisplay: "visible", align: "left", widthMode: "full", paddingX: 0, paddingY: 0, borderRadius: "md", }, sectionId: null, columnId: null, }, { id: buttonId, type: "button", props: { label: "Submit", href: "#", align: "left", size: "md", variant: "solid", colorPalette: "primary", borderRadius: "md", }, sectionId: null, columnId: null, }, { id: formId, type: "form", props: { kind: "contact", submitTarget: "internal", successMessage: "Submitted successfully.", errorMessage: disabledMessage, payloadFormat: "form", fieldIds: [inputId], requiredFieldIds: [inputId], submitButtonId: buttonId, formWidthMode: "auto", }, sectionId: null, columnId: null, }, ]; await createProject(request, accessToken, { slug, title: "Status ARCHIVED project", contentJson, }); await patchProjectStatus(request, accessToken, slug, "ARCHIVED"); const submitRequests: string[] = []; page.on("request", (req) => { const url = req.url(); if (url.includes("/api/forms/submit")) { submitRequests.push(url); } }); const response = await page.goto(`/p/${slug}`); expect(response?.status()).toBe(200); const input = page.locator("input.pb-input").first(); await input.waitFor(); // 퍼블릭 페이지 클라이언트 스크립트가 폼 submit 이벤트를 후킹해 data-pb-initialized 속성을 설정할 때까지 기다린다. // 이 폼은 hidden 일 수 있으므로, DOM 에만 붙어 있으면 되도록 state 는 attached 로 제한한다. await page.waitForSelector('form[data-pb-initialized="1"]', { state: "attached" }); await input.fill("Archived user"); await page.getByRole("button", { name: "Submit" }).click(); await expect(page.getByText(disabledMessage)).toBeVisible(); expect(submitRequests.length).toBe(0); }); });