test: 添加完整测试套件(52 个文件,326 个用例)
基于 Vitest 搭建测试基础设施,覆盖后端纯函数、API 路由、 前端 hooks、UI 组件和页面级集成测试。
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
||||
import { TEST_ROOM_DATA } from "@/__tests__/helpers/fixtures";
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
||||
|
||||
vi.mock("@/lib/store", () => ({
|
||||
atomicUpdateRoom: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/roomEvents", () => ({
|
||||
notify: vi.fn(),
|
||||
}));
|
||||
|
||||
import { POST } from "./route";
|
||||
import { atomicUpdateRoom } from "@/lib/store";
|
||||
|
||||
const mockAtomicUpdate = vi.mocked(atomicUpdateRoom);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("POST /api/room/[id]/join", () => {
|
||||
it("joins a room successfully", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = { ...TEST_ROOM_DATA, users: ["user-1"] };
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/join", {
|
||||
method: "POST",
|
||||
body: { userId: "user-2" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
const { status, data } = await parseJsonResponse(res);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(data.roomId).toBe("ROOM01");
|
||||
expect(data.userCount).toBe(2);
|
||||
});
|
||||
|
||||
it("returns 403 when kicked", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = { ...TEST_ROOM_DATA, kickedUsers: ["user-2"] };
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/join", {
|
||||
method: "POST",
|
||||
body: { userId: "user-2" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 403 when room is locked", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = { ...TEST_ROOM_DATA, locked: true, users: ["user-1"] };
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/join", {
|
||||
method: "POST",
|
||||
body: { userId: "user-2" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 404 when room not found", async () => {
|
||||
mockAtomicUpdate.mockResolvedValue(null);
|
||||
|
||||
const req = createRequest("/api/room/NONEXIST/join", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "NONEXIST" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it("returns 401 when no userId", async () => {
|
||||
const req = createRequest("/api/room/ROOM01/join", {
|
||||
method: "POST",
|
||||
body: {},
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
||||
import { TEST_ROOM_DATA, TEST_RESTAURANT } from "@/__tests__/helpers/fixtures";
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
||||
|
||||
vi.mock("@/lib/store", () => ({
|
||||
atomicUpdateRoom: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/roomEvents", () => ({
|
||||
notify: vi.fn(),
|
||||
}));
|
||||
|
||||
import { POST } from "./route";
|
||||
import { atomicUpdateRoom } from "@/lib/store";
|
||||
|
||||
const mockAtomicUpdate = vi.mocked(atomicUpdateRoom);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("POST /api/room/[id]/manage", () => {
|
||||
it("locks the room", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
const result = updater(data);
|
||||
expect(result.locked).toBe(true);
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "lock" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("unlocks the room", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.locked = true;
|
||||
const result = updater(data);
|
||||
expect(result.locked).toBe(false);
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "unlock" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("kicks a user", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
const result = updater(data);
|
||||
expect(result.users).not.toContain("user-2");
|
||||
expect(result.kickedUsers).toContain("user-2");
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "kick", targetUserId: "user-2" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("prevents kicking yourself", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "kick", targetUserId: "user-1" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("ends voting by setting all swipeCounts to total", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
const result = updater(data);
|
||||
expect(result.swipeCounts["user-1"]).toBe(3);
|
||||
expect(result.swipeCounts["user-2"]).toBe(3);
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "end_voting" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("returns 403 when not the creator", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.creatorId = "other-user";
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "lock" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 400 for unknown action", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/manage", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "unknown" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
||||
import { TEST_ROOM_DATA, TEST_RESTAURANT, TEST_RESTAURANT_2 } from "@/__tests__/helpers/fixtures";
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
||||
|
||||
vi.mock("@/lib/store", () => ({
|
||||
atomicUpdateRoom: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/roomEvents", () => ({
|
||||
notify: vi.fn(),
|
||||
}));
|
||||
|
||||
import { POST } from "./route";
|
||||
import { atomicUpdateRoom } from "@/lib/store";
|
||||
|
||||
const mockAtomicUpdate = vi.mocked(atomicUpdateRoom);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("POST /api/room/[id]/reset", () => {
|
||||
it("resets the room (clears likes/swipeCounts/match)", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.likes = { "rest-1": ["user-1"] };
|
||||
data.swipeCounts = { "user-1": 3 };
|
||||
data.match = "rest-1";
|
||||
const result = updater(data);
|
||||
expect(result.likes).toEqual({});
|
||||
expect(result.swipeCounts).toEqual({});
|
||||
expect(result.match).toBeNull();
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/reset", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
const { status, data } = await parseJsonResponse(res);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(data.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("filters restaurants when restaurantIds provided", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
const result = updater(data);
|
||||
expect(result.restaurants).toHaveLength(1);
|
||||
expect(result.restaurants[0].id).toBe(TEST_RESTAURANT.id);
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/reset", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantIds: [TEST_RESTAURANT.id] },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
await POST(req, ctx);
|
||||
});
|
||||
|
||||
it("returns 403 when not a member or creator", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.users = ["other-user"];
|
||||
data.creatorId = "other-user";
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/reset", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 404 when room not found", async () => {
|
||||
mockAtomicUpdate.mockResolvedValue(null);
|
||||
|
||||
const req = createRequest("/api/room/NONEXIST/reset", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "NONEXIST" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: { user: { findMany: vi.fn().mockResolvedValue([]) } },
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/buildRoomStatus", () => ({
|
||||
buildRoomStatus: vi.fn(),
|
||||
}));
|
||||
|
||||
import { GET } from "./route";
|
||||
import { buildRoomStatus } from "@/lib/buildRoomStatus";
|
||||
|
||||
const mockBuildRoomStatus = vi.mocked(buildRoomStatus);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("GET /api/room/[id]", () => {
|
||||
it("returns room status", async () => {
|
||||
mockBuildRoomStatus.mockResolvedValue({
|
||||
roomId: "ROOM01",
|
||||
userCount: 2,
|
||||
match: null,
|
||||
matchType: null,
|
||||
matchLikes: 0,
|
||||
runnerUps: [],
|
||||
likeCounts: {},
|
||||
swipeCounts: {},
|
||||
restaurants: [],
|
||||
creatorId: "user-1",
|
||||
locked: false,
|
||||
users: ["user-1"],
|
||||
userProfiles: {},
|
||||
scene: "eat",
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01");
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await GET(req, ctx);
|
||||
const { status, data } = await parseJsonResponse(res);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(data.roomId).toBe("ROOM01");
|
||||
});
|
||||
|
||||
it("returns 404 for nonexistent room", async () => {
|
||||
mockBuildRoomStatus.mockResolvedValue(null);
|
||||
|
||||
const req = createRequest("/api/room/NONEXIST");
|
||||
const ctx = createRouteContext({ id: "NONEXIST" });
|
||||
const res = await GET(req, ctx);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
||||
import { TEST_ROOM_DATA, TEST_RESTAURANT } from "@/__tests__/helpers/fixtures";
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
||||
|
||||
vi.mock("@/lib/store", () => ({
|
||||
atomicUpdateRoom: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/roomEvents", () => ({
|
||||
notify: vi.fn(),
|
||||
}));
|
||||
|
||||
import { POST } from "./route";
|
||||
import { atomicUpdateRoom } from "@/lib/store";
|
||||
|
||||
const mockAtomicUpdate = vi.mocked(atomicUpdateRoom);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("POST /api/room/[id]/swipe", () => {
|
||||
it("records a like action", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id, action: "like" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
const { status, data } = await parseJsonResponse(res);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(data.likeCount).toBe(1);
|
||||
});
|
||||
|
||||
it("records a pass action", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id, action: "pass" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
const { status, data } = await parseJsonResponse(res);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(data.match).toBeNull();
|
||||
});
|
||||
|
||||
it("sets match when all users like same restaurant", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.likes[TEST_RESTAURANT.id] = ["user-2"];
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id, action: "like" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
const { data } = await parseJsonResponse(res);
|
||||
|
||||
expect(data.match).toBe(TEST_RESTAURANT.id);
|
||||
expect(data.likeCount).toBe(2);
|
||||
});
|
||||
|
||||
it("returns 403 when user is not a member", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.users = ["other-user"];
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id, action: "like" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 400 for invalid action", async () => {
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id, action: "invalid" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("returns 400 when missing restaurantId", async () => {
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", action: "like" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("returns 404 when room not found", async () => {
|
||||
mockAtomicUpdate.mockResolvedValue(null);
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/swipe", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id, action: "like" },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
||||
import { TEST_ROOM_DATA, TEST_RESTAURANT } from "@/__tests__/helpers/fixtures";
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
||||
|
||||
vi.mock("@/lib/store", () => ({
|
||||
atomicUpdateRoom: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/roomEvents", () => ({
|
||||
notify: vi.fn(),
|
||||
}));
|
||||
|
||||
import { POST } from "./route";
|
||||
import { atomicUpdateRoom } from "@/lib/store";
|
||||
|
||||
const mockAtomicUpdate = vi.mocked(atomicUpdateRoom);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("POST /api/room/[id]/undo", () => {
|
||||
it("undoes a like and decrements swipe count", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.likes[TEST_RESTAURANT.id] = ["user-1"];
|
||||
data.swipeCounts["user-1"] = 1;
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/undo", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
const { status, data } = await parseJsonResponse(res);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(data.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("clears match when undoing the matched restaurant", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.match = TEST_RESTAURANT.id;
|
||||
data.likes[TEST_RESTAURANT.id] = ["user-1", "user-2"];
|
||||
data.swipeCounts["user-1"] = 1;
|
||||
const result = updater(data);
|
||||
expect(result.match).toBeNull();
|
||||
return result;
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/undo", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
await POST(req, ctx);
|
||||
});
|
||||
|
||||
it("returns 403 when user is not a member", async () => {
|
||||
mockAtomicUpdate.mockImplementation(async (_id, updater) => {
|
||||
const data = structuredClone(TEST_ROOM_DATA);
|
||||
data.users = ["other-user"];
|
||||
return updater(data);
|
||||
});
|
||||
|
||||
const req = createRequest("/api/room/ROOM01/undo", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "ROOM01" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 404 when room not found", async () => {
|
||||
mockAtomicUpdate.mockResolvedValue(null);
|
||||
|
||||
const req = createRequest("/api/room/NONEXIST/undo", {
|
||||
method: "POST",
|
||||
body: { userId: "user-1", restaurantId: TEST_RESTAURANT.id },
|
||||
});
|
||||
const ctx = createRouteContext({ id: "NONEXIST" });
|
||||
const res = await POST(req, ctx);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user