test: 添加完整测试套件(52 个文件,326 个用例)

基于 Vitest 搭建测试基础设施,覆盖后端纯函数、API 路由、
前端 hooks、UI 组件和页面级集成测试。
This commit is contained in:
2026-02-28 20:19:14 +08:00
parent 11eeec868e
commit 3ccd1262f9
59 changed files with 8131 additions and 3 deletions
+174
View File
@@ -0,0 +1,174 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { prismaMock, resetPrismaMock } from "@/__tests__/helpers/prisma-mock";
import { createRequest, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
import { TEST_USER } from "@/__tests__/helpers/fixtures";
vi.mock("bcryptjs", () => ({
default: {
compare: vi.fn(),
hash: vi.fn().mockResolvedValue("$2a$10$newhash"),
},
}));
import bcrypt from "bcryptjs";
import { GET, PUT } from "./route";
const mockCtx = { params: Promise.resolve({}) };
beforeEach(() => {
resetPrismaMock();
vi.mocked(bcrypt.compare).mockReset();
});
describe("GET /api/user", () => {
it("returns null when no userId provided", async () => {
const req = createRequest("/api/user");
const res = await GET(req, mockCtx);
const { data } = await parseJsonResponse(res);
expect(data).toBeNull();
});
it("returns null when user not found", async () => {
prismaMock.user.findUnique.mockResolvedValue(null as never);
const req = createRequest("/api/user?id=nonexistent");
const res = await GET(req, mockCtx);
const { data } = await parseJsonResponse(res);
expect(data).toBeNull();
});
it("returns user info with decision count", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
prismaMock.decision.count.mockResolvedValue(5 as never);
const req = createRequest("/api/user?id=user-1");
const res = await GET(req, mockCtx);
const { status, data } = await parseJsonResponse(res);
expect(status).toBe(200);
expect(data.id).toBe("user-1");
expect(data.username).toBe("testuser");
expect(data.decisionCount).toBe(5);
});
});
describe("PUT /api/user", () => {
it("updates username", async () => {
prismaMock.user.findUnique
.mockResolvedValueOnce(TEST_USER as never)
.mockResolvedValueOnce(null as never);
prismaMock.user.update.mockResolvedValue({
...TEST_USER,
username: "newname",
preferences: "{}",
} as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", username: "newname" },
});
const res = await PUT(req, mockCtx);
const { status, data } = await parseJsonResponse(res);
expect(status).toBe(200);
expect(data.username).toBe("newname");
});
it("returns 409 when new username is taken", async () => {
prismaMock.user.findUnique
.mockResolvedValueOnce(TEST_USER as never)
.mockResolvedValueOnce({ id: "other" } as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", username: "takenname" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(409);
});
it("updates password with correct current password", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
vi.mocked(bcrypt.compare).mockResolvedValue(true as never);
prismaMock.user.update.mockResolvedValue({ ...TEST_USER, preferences: "{}" } as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", currentPassword: "old", newPassword: "newpass123" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(200);
});
it("returns 403 when current password is wrong", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
vi.mocked(bcrypt.compare).mockResolvedValue(false as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", currentPassword: "wrong", newPassword: "newpass123" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(403);
});
it("returns 400 when no current password for password change", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", newPassword: "newpass123" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(400);
});
it("returns 401 when no userId", async () => {
const req = createRequest("/api/user", {
method: "PUT",
body: { username: "test" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(401);
});
it("updates avatar", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
prismaMock.user.update.mockResolvedValue({ ...TEST_USER, avatar: "🦊", preferences: "{}" } as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", avatar: "🦊" },
});
const res = await PUT(req, mockCtx);
const { data } = await parseJsonResponse(res);
expect(data.avatar).toBe("🦊");
});
it("updates email with validation", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
prismaMock.user.update.mockResolvedValue({
...TEST_USER,
email: "new@example.com",
preferences: "{}",
} as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", email: "new@example.com" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(200);
});
it("rejects invalid email", async () => {
prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
const req = createRequest("/api/user", {
method: "PUT",
body: { userId: "user-1", email: "notanemail" },
});
const res = await PUT(req, mockCtx);
expect(res.status).toBe(400);
});
});