사용자 로그인, 가입 처리
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
// 인증 라우트 가드 E2E
|
||||
// - 비로그인 사용자가 보호된 페이지에 접근하면 /login 으로 리다이렉트되어야 한다.
|
||||
|
||||
test("비로그인 사용자가 /projects 에 접근하면 /login 으로 리다이렉트되어야 한다", async ({ page }) => {
|
||||
// /api/projects 응답을 401 으로 목 처리해 비로그인 상태를 시뮬레이션한다.
|
||||
await page.route("**/api/projects", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 401,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "로그인이 필요합니다." }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/projects");
|
||||
|
||||
// 최종적으로 로그인 페이지의 헤더가 보여야 한다.
|
||||
await expect(page.getByRole("heading", { name: "로그인" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("비로그인 사용자가 /editor 에 접근하면 /login 으로 리다이렉트되어야 한다", async ({ page }) => {
|
||||
// /api/auth/me 를 401 으로 응답하도록 목 처리해 비로그인 상태를 시뮬레이션한다.
|
||||
await page.route("**/api/auth/me", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 401,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "인증이 필요합니다." }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/editor");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "로그인" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("비로그인 사용자가 /preview 에 접근하면 /login 으로 리다이렉트되어야 한다", async ({ page }) => {
|
||||
await page.route("**/api/auth/me", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 401,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "인증이 필요합니다." }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/preview");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "로그인" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("회원가입 후에는 /projects, /editor, /preview 에 정상 접근할 수 있어야 한다", async ({ page }) => {
|
||||
const email = `e2e+${Date.now()}@example.com`;
|
||||
const password = "securePass1";
|
||||
|
||||
// 백엔드/DB 에 의존하지 않고 프론트 동작만 검증하기 위해 관련 API 를 모두 목 처리한다.
|
||||
// isLoggedIn 플래그를 두고, 회원가입 전에는 /api/auth/me 가 401 을, 회원가입 후에는 200 을 반환하도록 시뮬레이션한다.
|
||||
let isLoggedIn = false;
|
||||
|
||||
await page.route("**/api/auth/signup", async (route) => {
|
||||
isLoggedIn = true;
|
||||
|
||||
await route.fulfill({
|
||||
status: 201,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ id: "user-1", email }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route("**/api/auth/me", async (route) => {
|
||||
if (!isLoggedIn) {
|
||||
await route.fulfill({
|
||||
status: 401,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "인증이 필요합니다." }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ id: "user-1", email, tokenVersion: 1 }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route("**/api/projects", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify([]),
|
||||
});
|
||||
});
|
||||
|
||||
// 1) 회원가입
|
||||
await page.goto("/signup");
|
||||
|
||||
await page.getByLabel("이메일").fill(email);
|
||||
await page.getByLabel("비밀번호").fill(password);
|
||||
await page.getByRole("button", { name: "회원가입" }).click();
|
||||
|
||||
// /projects 로 이동했는지, 헤더가 보이는지 확인
|
||||
await expect(page).toHaveURL(/\/projects/);
|
||||
await expect(page.getByRole("heading", { name: "프로젝트 목록" })).toBeVisible();
|
||||
|
||||
// 2) /editor 접근 확인 – 로그인 페이지로 리다이렉트되면 안 된다.
|
||||
await page.goto("/editor");
|
||||
await expect(page.getByRole("heading", { name: "Page Editor" })).toBeVisible();
|
||||
|
||||
// 3) /preview 접근 확인
|
||||
await page.goto("/preview");
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("서로 다른 유저는 /projects 에서 자신의 프로젝트만 볼 수 있어야 한다", async ({ page }) => {
|
||||
type Project = {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
let currentUser: "A" | "B" = "A";
|
||||
const projectsA: Project[] = [];
|
||||
const projectsB: Project[] = [];
|
||||
let nextId = 1;
|
||||
|
||||
const nowIso = () => new Date().toISOString();
|
||||
|
||||
await page.route("**/api/auth/me", async (route) => {
|
||||
const email = currentUser === "A" ? "user-a@example.com" : "user-b@example.com";
|
||||
const id = currentUser === "A" ? "user-a" : "user-b";
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ id, email, tokenVersion: 1 }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route("**/api/projects*", async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
const method = request.method();
|
||||
|
||||
const ownerProjects = currentUser === "A" ? projectsA : projectsB;
|
||||
const otherProjects = currentUser === "A" ? projectsB : projectsA;
|
||||
|
||||
if (url.pathname === "/api/projects" && method === "GET") {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(ownerProjects),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === "/api/projects" && method === "POST") {
|
||||
const bodyText = request.postData() ?? "{}";
|
||||
const body = JSON.parse(bodyText) as Partial<Project> & { title?: string; slug?: string };
|
||||
const slug = body.slug ?? "";
|
||||
const title = body.title ?? "";
|
||||
|
||||
const conflict = otherProjects.find((p) => p.slug === slug);
|
||||
if (conflict) {
|
||||
await route.fulfill({
|
||||
status: 409,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "이미 다른 사용자가 사용 중인 프로젝트 주소입니다." }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const existingIndex = ownerProjects.findIndex((p) => p.slug === slug);
|
||||
const now = nowIso();
|
||||
|
||||
let project: Project;
|
||||
if (existingIndex >= 0) {
|
||||
const existing = ownerProjects[existingIndex];
|
||||
project = { ...existing, title: title || existing.title, updatedAt: now };
|
||||
ownerProjects[existingIndex] = project;
|
||||
} else {
|
||||
project = {
|
||||
id: String(nextId++),
|
||||
title: title || "제목 없음",
|
||||
slug,
|
||||
status: "DRAFT",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
ownerProjects.push(project);
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 201,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(project),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 404,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "Not implemented in E2E mock" }),
|
||||
});
|
||||
});
|
||||
|
||||
// 1) 유저 A: 에디터에서 프로젝트를 저장하고, /projects 에서 자신의 프로젝트만 보여야 한다.
|
||||
await page.goto("/editor");
|
||||
|
||||
const propertiesSidebarA = page.getByTestId("properties-sidebar");
|
||||
await propertiesSidebarA.getByLabel("프로젝트 제목").fill("유저 A 프로젝트");
|
||||
await propertiesSidebarA.getByLabel("프로젝트 주소 (slug)").fill("user-a-project");
|
||||
|
||||
await page.getByRole("button", { name: "메뉴 ▼" }).click();
|
||||
await page.getByRole("button", { name: "프로젝트 저장/불러오기" }).click();
|
||||
await page.getByRole("button", { name: "저장 (로컬 + 서버)" }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/_?projects/);
|
||||
await expect(page.getByText("유저 A 프로젝트")).toBeVisible();
|
||||
await expect(page.getByText("user-a-project")).toBeVisible();
|
||||
await expect(page.getByText("user-b-project")).toHaveCount(0);
|
||||
|
||||
// 2) 유저 B: currentUser 전환 후 별도 프로젝트를 저장하고, /projects 에서 자신의 것만 보여야 한다.
|
||||
currentUser = "B";
|
||||
|
||||
await page.goto("/editor");
|
||||
|
||||
const propertiesSidebarB = page.getByTestId("properties-sidebar");
|
||||
await propertiesSidebarB.getByLabel("프로젝트 제목").fill("유저 B 프로젝트");
|
||||
await propertiesSidebarB.getByLabel("프로젝트 주소 (slug)").fill("user-b-project");
|
||||
|
||||
await page.getByRole("button", { name: "메뉴 ▼" }).click();
|
||||
await page.getByRole("button", { name: "프로젝트 저장/불러오기" }).click();
|
||||
await page.getByRole("button", { name: "저장 (로컬 + 서버)" }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/_?projects/);
|
||||
await expect(page.getByText("유저 B 프로젝트")).toBeVisible();
|
||||
await expect(page.getByText("user-b-project")).toBeVisible();
|
||||
await expect(page.getByText("유저 A 프로젝트")).toHaveCount(0);
|
||||
|
||||
// 3) 다시 유저 A 로 전환했을 때, /projects 에서는 유저 A 의 프로젝트만 보여야 한다.
|
||||
currentUser = "A";
|
||||
|
||||
await page.goto("/projects");
|
||||
|
||||
await expect(page.getByText("유저 A 프로젝트")).toBeVisible();
|
||||
await expect(page.getByText("user-a-project")).toBeVisible();
|
||||
await expect(page.getByText("유저 B 프로젝트")).toHaveCount(0);
|
||||
});
|
||||
Reference in New Issue
Block a user