사용자 로그인, 가입 처리
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
import "dotenv/config";
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
|
||||
import { hashPassword, signAccessToken } from "@/features/auth/authCrypto";
|
||||
|
||||
const BASE_URL = "http://localhost";
|
||||
|
||||
// PrismaClient 를 실제 DB 대신 메모리 기반 user 저장소를 사용하는 목으로 대체한다.
|
||||
// 이렇게 하면 CI/로컬 어디에서도 DATABASE_URL 이나 실제 DB 없이 Auth API 테스트를 실행할 수 있다.
|
||||
const inMemoryUsers: any[] = [];
|
||||
|
||||
vi.mock("@/generated/prisma/client", () => {
|
||||
class PrismaClientMock {
|
||||
user = {
|
||||
findUnique: async ({ where: { email } }: any) => {
|
||||
return inMemoryUsers.find((u) => u.email === email) ?? null;
|
||||
},
|
||||
create: async ({ data }: any) => {
|
||||
const now = new Date();
|
||||
const user = {
|
||||
id: String(inMemoryUsers.length + 1),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
...data,
|
||||
};
|
||||
inMemoryUsers.push(user);
|
||||
return user;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { PrismaClient: PrismaClientMock };
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// 각 테스트 전에 메모리 유저 저장소를 초기화한다.
|
||||
inMemoryUsers.length = 0;
|
||||
process.env.AUTH_JWT_SECRET = "test-jwt-secret-api";
|
||||
process.env.AUTH_ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef";
|
||||
});
|
||||
|
||||
describe("/api/auth", () => {
|
||||
describe("POST /api/auth/signup", () => {
|
||||
it("새 이메일과 8자 이상 비밀번호로 회원가입하면 User 가 생성되고 JWT 쿠키가 설정되어야 한다", async () => {
|
||||
const { POST: signup } = await import("@/app/api/auth/signup/route");
|
||||
|
||||
const payload = { email: "user@example.com", password: "securePass1" };
|
||||
|
||||
const res = await signup(
|
||||
new Request(`${BASE_URL}/api/auth/signup`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
|
||||
const json = (await res.json()) as any;
|
||||
expect(json.email).toBe(payload.email);
|
||||
expect(json.id).toBeDefined();
|
||||
// 응답에는 passwordHash 가 포함되면 안 된다.
|
||||
expect(json.passwordHash).toBeUndefined();
|
||||
|
||||
// 메모리 저장소에도 유저가 1명 생성되어야 한다.
|
||||
expect(inMemoryUsers.length).toBe(1);
|
||||
expect(inMemoryUsers[0].email).toBe(payload.email);
|
||||
expect(typeof inMemoryUsers[0].passwordHash).toBe("string");
|
||||
|
||||
// JWT 세션 쿠키가 설정되어야 한다.
|
||||
const setCookie = res.headers.get("set-cookie");
|
||||
expect(setCookie).toBeTruthy();
|
||||
expect(setCookie).toContain("pb_access=");
|
||||
});
|
||||
|
||||
it("비밀번호가 8자 미만이면 400 과 에러 메시지를 반환해야 한다", async () => {
|
||||
const { POST: signup } = await import("@/app/api/auth/signup/route");
|
||||
|
||||
const res = await signup(
|
||||
new Request(`${BASE_URL}/api/auth/signup`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "short@example.com", password: "short" }),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
const json = (await res.json()) as any;
|
||||
expect(json.message).toBeDefined();
|
||||
});
|
||||
|
||||
it("이미 존재하는 이메일로 회원가입하면 409 를 반환해야 한다", async () => {
|
||||
const { POST: signup } = await import("@/app/api/auth/signup/route");
|
||||
|
||||
// 먼저 한 번 성공적으로 가입
|
||||
await signup(
|
||||
new Request(`${BASE_URL}/api/auth/signup`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "dup@example.com", password: "securePass1" }),
|
||||
}),
|
||||
);
|
||||
|
||||
// 같은 이메일로 다시 요청
|
||||
const res = await signup(
|
||||
new Request(`${BASE_URL}/api/auth/signup`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "dup@example.com", password: "anotherPass1" }),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(409);
|
||||
const json = (await res.json()) as any;
|
||||
expect(json.message).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/auth/login", () => {
|
||||
it("올바른 이메일/비밀번호로 로그인하면 200 과 JWT 쿠키를 반환해야 한다", async () => {
|
||||
const { POST: signup } = await import("@/app/api/auth/signup/route");
|
||||
const { POST: login } = await import("@/app/api/auth/login/route");
|
||||
|
||||
const email = "login@example.com";
|
||||
const password = "securePass1";
|
||||
|
||||
// 사전 회원가입
|
||||
await signup(
|
||||
new Request(`${BASE_URL}/api/auth/signup`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
}),
|
||||
);
|
||||
|
||||
const res = await login(
|
||||
new Request(`${BASE_URL}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const json = (await res.json()) as any;
|
||||
expect(json.email).toBe(email);
|
||||
expect(json.id).toBeDefined();
|
||||
expect(json.passwordHash).toBeUndefined();
|
||||
|
||||
const setCookie = res.headers.get("set-cookie");
|
||||
expect(setCookie).toBeTruthy();
|
||||
expect(setCookie).toContain("pb_access=");
|
||||
});
|
||||
|
||||
it("잘못된 이메일 또는 비밀번호로 로그인하면 401 을 반환해야 한다", async () => {
|
||||
const { POST: signup } = await import("@/app/api/auth/signup/route");
|
||||
const { POST: login } = await import("@/app/api/auth/login/route");
|
||||
|
||||
const email = "wrong@example.com";
|
||||
const password = "securePass1";
|
||||
|
||||
// 사전 회원가입
|
||||
await signup(
|
||||
new Request(`${BASE_URL}/api/auth/signup`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
}),
|
||||
);
|
||||
|
||||
// 존재하지 않는 이메일
|
||||
const res1 = await login(
|
||||
new Request(`${BASE_URL}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "nope@example.com", password }),
|
||||
}),
|
||||
);
|
||||
expect(res1.status).toBe(401);
|
||||
|
||||
// 비밀번호 불일치
|
||||
const res2 = await login(
|
||||
new Request(`${BASE_URL}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password: "wrongPass1" }),
|
||||
}),
|
||||
);
|
||||
expect(res2.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/auth/logout", () => {
|
||||
it("로그아웃 시 pb_access 쿠키를 제거하는 Set-Cookie 헤더를 반환해야 한다", async () => {
|
||||
const { POST: logout } = await import("@/app/api/auth/logout/route");
|
||||
|
||||
const res = await logout(
|
||||
new Request(`${BASE_URL}/api/auth/logout`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
cookie: "pb_access=dummy.token.value",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const setCookie = res.headers.get("set-cookie");
|
||||
expect(setCookie).toBeTruthy();
|
||||
expect(setCookie).toContain("pb_access=");
|
||||
expect(setCookie?.toLowerCase()).toContain("max-age=0");
|
||||
});
|
||||
|
||||
it("쿠키가 없어도 200 과 쿠키 제거 헤더를 반환해야 한다", async () => {
|
||||
const { POST: logout } = await import("@/app/api/auth/logout/route");
|
||||
|
||||
const res = await logout(
|
||||
new Request(`${BASE_URL}/api/auth/logout`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const setCookie = res.headers.get("set-cookie");
|
||||
expect(setCookie).toBeTruthy();
|
||||
expect(setCookie).toContain("pb_access=");
|
||||
expect(setCookie?.toLowerCase()).toContain("max-age=0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/auth/me", () => {
|
||||
it("유효한 JWT 쿠키가 있을 때 현재 로그인 유저 정보를 반환해야 한다", async () => {
|
||||
const { GET: me } = await import("@/app/api/auth/me/route");
|
||||
|
||||
const user = { id: "user-1", email: "me@example.com", tokenVersion: 1 };
|
||||
const token = await signAccessToken(user);
|
||||
|
||||
const res = await me(
|
||||
new Request(`${BASE_URL}/api/auth/me`, {
|
||||
headers: {
|
||||
cookie: `pb_access=${token}`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const json = (await res.json()) as any;
|
||||
expect(json.id).toBe(user.id);
|
||||
expect(json.email).toBe(user.email);
|
||||
expect(json.passwordHash).toBeUndefined();
|
||||
});
|
||||
|
||||
it("JWT 쿠키가 없거나 잘못된 경우 401 을 반환해야 한다", async () => {
|
||||
const { GET: me } = await import("@/app/api/auth/me/route");
|
||||
|
||||
const res1 = await me(new Request(`${BASE_URL}/api/auth/me`));
|
||||
expect(res1.status).toBe(401);
|
||||
|
||||
const res2 = await me(
|
||||
new Request(`${BASE_URL}/api/auth/me`, {
|
||||
headers: { cookie: "pb_access=invalid.token.here" },
|
||||
}),
|
||||
);
|
||||
expect(res2.status).toBe(401);
|
||||
});
|
||||
});
|
||||
});
|
||||
+218
-12
@@ -1,5 +1,6 @@
|
||||
import "dotenv/config";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { signAccessToken } from "@/features/auth/authCrypto";
|
||||
|
||||
// PrismaClient를 실제 DB 대신 메모리 기반 저장소를 사용하는 목으로 대체한다.
|
||||
// 이렇게 하면 CI/로컬 어디에서도 DATABASE_URL 이나 실제 DB 없이 API 테스트를 실행할 수 있다.
|
||||
@@ -58,9 +59,13 @@ vi.mock("@/generated/prisma/client", () => {
|
||||
const [removed] = inMemoryProjects.splice(index, 1);
|
||||
return removed;
|
||||
},
|
||||
findMany: async ({ orderBy, take, select }: any = {}) => {
|
||||
findMany: async ({ where, orderBy, take, select }: any = {}) => {
|
||||
let items = [...inMemoryProjects];
|
||||
|
||||
if (where?.userId) {
|
||||
items = items.filter((p) => p.userId === where.userId);
|
||||
}
|
||||
|
||||
if (orderBy?.createdAt === "desc") {
|
||||
items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
||||
}
|
||||
@@ -91,6 +96,23 @@ vi.mock("@/generated/prisma/client", () => {
|
||||
|
||||
const BASE_URL = "http://localhost";
|
||||
|
||||
const TEST_USER = { id: "user-1", email: "projects@example.com", tokenVersion: 1 };
|
||||
const OTHER_USER = { id: "user-2", email: "projects2@example.com", tokenVersion: 1 };
|
||||
|
||||
async function buildAuthHeadersFor(user: { id: string; email: string; tokenVersion: number }) {
|
||||
const token = await signAccessToken(user);
|
||||
return { cookie: `pb_access=${token}` };
|
||||
}
|
||||
|
||||
async function buildAuthHeaders() {
|
||||
return buildAuthHeadersFor(TEST_USER);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.AUTH_JWT_SECRET = "test-jwt-secret-projects";
|
||||
inMemoryProjects.length = 0;
|
||||
});
|
||||
|
||||
describe("/api/projects", () => {
|
||||
it("POST /api/projects 로 프로젝트를 생성하고 GET /api/projects/[slug] 로 조회할 수 있어야 한다", async () => {
|
||||
const payload = {
|
||||
@@ -107,10 +129,12 @@ describe("/api/projects", () => {
|
||||
|
||||
const { POST: createProject } = await import("@/app/api/projects/route");
|
||||
|
||||
const headers = await buildAuthHeaders();
|
||||
|
||||
const createResponse = await createProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
);
|
||||
@@ -127,8 +151,10 @@ describe("/api/projects", () => {
|
||||
|
||||
const { GET: getProjectBySlug } = await import("@/app/api/projects/[slug]/route");
|
||||
|
||||
const getHeaders = await buildAuthHeaders();
|
||||
|
||||
const getResponse = await getProjectBySlug(
|
||||
new Request(`${BASE_URL}/api/projects/${payload.slug}`),
|
||||
new Request(`${BASE_URL}/api/projects/${payload.slug}`, { headers: getHeaders }),
|
||||
{ params: { slug: payload.slug } } as any,
|
||||
);
|
||||
|
||||
@@ -153,10 +179,12 @@ describe("/api/projects", () => {
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const headers = await buildAuthHeaders();
|
||||
|
||||
await createProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(firstPayload),
|
||||
}),
|
||||
);
|
||||
@@ -166,14 +194,16 @@ describe("/api/projects", () => {
|
||||
await createProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(secondPayload),
|
||||
}),
|
||||
);
|
||||
|
||||
const { GET: listProjects } = await import("@/app/api/projects/route");
|
||||
|
||||
const res = await listProjects(new Request(`${BASE_URL}/api/projects`));
|
||||
const listHeaders = await buildAuthHeaders();
|
||||
|
||||
const res = await listProjects(new Request(`${BASE_URL}/api/projects`, { headers: listHeaders }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const list = (await res.json()) as any[];
|
||||
@@ -193,25 +223,29 @@ describe("/api/projects", () => {
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const headers = await buildAuthHeaders();
|
||||
|
||||
await createProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
);
|
||||
|
||||
const { DELETE: deleteProject, GET: getProjectBySlug } = await import("@/app/api/projects/[slug]/route");
|
||||
|
||||
const deleteHeaders = await buildAuthHeaders();
|
||||
|
||||
const deleteResponse = await deleteProject(
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { method: "DELETE" }),
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { method: "DELETE", headers: deleteHeaders }),
|
||||
{ params: { slug } } as any,
|
||||
);
|
||||
|
||||
expect(deleteResponse.status).toBe(200);
|
||||
|
||||
const getAfterDelete = await getProjectBySlug(
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`),
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { headers: deleteHeaders }),
|
||||
{ params: { slug } } as any,
|
||||
);
|
||||
|
||||
@@ -247,10 +281,12 @@ describe("/api/projects", () => {
|
||||
],
|
||||
};
|
||||
|
||||
const headers = await buildAuthHeaders();
|
||||
|
||||
const firstRes = await handleProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(firstPayload),
|
||||
}),
|
||||
);
|
||||
@@ -260,7 +296,7 @@ describe("/api/projects", () => {
|
||||
const secondRes = await handleProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(secondPayload),
|
||||
}),
|
||||
);
|
||||
@@ -272,4 +308,174 @@ describe("/api/projects", () => {
|
||||
expect(updated.title).toBe("두 번째 제목");
|
||||
expect(updated.contentJson).toEqual(secondPayload.contentJson);
|
||||
});
|
||||
|
||||
it("GET /api/projects 는 현재 로그인 유저의 프로젝트만 반환해야 한다", async () => {
|
||||
const { POST: createProject, GET: listProjects } = await import("@/app/api/projects/route");
|
||||
|
||||
const slugA = "user-a-project";
|
||||
const slugB = "user-b-project";
|
||||
|
||||
const headersA = await buildAuthHeadersFor(TEST_USER);
|
||||
const headersB = await buildAuthHeadersFor(OTHER_USER);
|
||||
|
||||
const payloadA = {
|
||||
title: "유저 A 프로젝트",
|
||||
slug: slugA,
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const payloadB = {
|
||||
title: "유저 B 프로젝트",
|
||||
slug: slugB,
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
await createProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...headersA },
|
||||
body: JSON.stringify(payloadA),
|
||||
}),
|
||||
);
|
||||
|
||||
await createProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...headersB },
|
||||
body: JSON.stringify(payloadB),
|
||||
}),
|
||||
);
|
||||
|
||||
const resA = await listProjects(
|
||||
new Request(`${BASE_URL}/api/projects`, { headers: headersA }),
|
||||
);
|
||||
expect(resA.status).toBe(200);
|
||||
const listA = (await resA.json()) as any[];
|
||||
const slugsA = listA.map((p) => p.slug);
|
||||
expect(slugsA).toContain(slugA);
|
||||
expect(slugsA).not.toContain(slugB);
|
||||
|
||||
const resB = await listProjects(
|
||||
new Request(`${BASE_URL}/api/projects`, { headers: headersB }),
|
||||
);
|
||||
expect(resB.status).toBe(200);
|
||||
const listB = (await resB.json()) as any[];
|
||||
const slugsB = listB.map((p) => p.slug);
|
||||
expect(slugsB).toContain(slugB);
|
||||
expect(slugsB).not.toContain(slugA);
|
||||
});
|
||||
|
||||
it("다른 유저가 이미 소유한 slug 로 POST 하면 409 를 반환해야 한다", async () => {
|
||||
const { POST: handleProject } = await import("@/app/api/projects/route");
|
||||
|
||||
const slug = "shared-slug";
|
||||
|
||||
const ownerPayload = {
|
||||
title: "소유자 프로젝트",
|
||||
slug,
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const otherPayload = {
|
||||
title: "다른 유저 프로젝트",
|
||||
slug,
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const ownerHeaders = await buildAuthHeadersFor(TEST_USER);
|
||||
const otherHeaders = await buildAuthHeadersFor(OTHER_USER);
|
||||
|
||||
const firstRes = await handleProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...ownerHeaders },
|
||||
body: JSON.stringify(ownerPayload),
|
||||
}),
|
||||
);
|
||||
expect(firstRes.status).toBe(201);
|
||||
|
||||
const secondRes = await handleProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...otherHeaders },
|
||||
body: JSON.stringify(otherPayload),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(secondRes.status).toBe(409);
|
||||
});
|
||||
|
||||
it("GET /api/projects/[slug] 는 소유자가 아닌 유저에게 404 를 반환해야 한다", async () => {
|
||||
const { POST: handleProject } = await import("@/app/api/projects/route");
|
||||
const { GET: getProjectBySlug } = await import("@/app/api/projects/[slug]/route");
|
||||
|
||||
const slug = "owner-only-slug";
|
||||
|
||||
const ownerHeaders = await buildAuthHeadersFor(TEST_USER);
|
||||
const otherHeaders = await buildAuthHeadersFor(OTHER_USER);
|
||||
|
||||
const payload = {
|
||||
title: "소유자 프로젝트",
|
||||
slug,
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const createRes = await handleProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...ownerHeaders },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
);
|
||||
expect(createRes.status).toBe(201);
|
||||
|
||||
const resOther = await getProjectBySlug(
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { headers: otherHeaders }),
|
||||
{ params: { slug } } as any,
|
||||
);
|
||||
expect(resOther.status).toBe(404);
|
||||
|
||||
const resOwner = await getProjectBySlug(
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { headers: ownerHeaders }),
|
||||
{ params: { slug } } as any,
|
||||
);
|
||||
expect(resOwner.status).toBe(200);
|
||||
});
|
||||
|
||||
it("DELETE /api/projects/[slug] 는 소유자가 아닌 유저가 호출하면 404 를 반환하고 실제 데이터는 삭제되지 않아야 한다", async () => {
|
||||
const { POST: handleProject } = await import("@/app/api/projects/route");
|
||||
const { DELETE: deleteProject, GET: getProjectBySlug } = await import("@/app/api/projects/[slug]/route");
|
||||
|
||||
const slug = "owner-delete-slug";
|
||||
|
||||
const ownerHeaders = await buildAuthHeadersFor(TEST_USER);
|
||||
const otherHeaders = await buildAuthHeadersFor(OTHER_USER);
|
||||
|
||||
const payload = {
|
||||
title: "삭제 소유자 프로젝트",
|
||||
slug,
|
||||
contentJson: [],
|
||||
};
|
||||
|
||||
const createRes = await handleProject(
|
||||
new Request(`${BASE_URL}/api/projects`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...ownerHeaders },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
);
|
||||
expect(createRes.status).toBe(201);
|
||||
|
||||
const deleteResOther = await deleteProject(
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { method: "DELETE", headers: otherHeaders }),
|
||||
{ params: { slug } } as any,
|
||||
);
|
||||
expect(deleteResOther.status).toBe(404);
|
||||
|
||||
const resOwner = await getProjectBySlug(
|
||||
new Request(`${BASE_URL}/api/projects/${slug}`, { headers: ownerHeaders }),
|
||||
{ params: { slug } } as any,
|
||||
);
|
||||
expect(resOwner.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user