diff --git a/PROJECT_AUDIT_2026-03-03.md b/PROJECT_AUDIT_2026-03-03.md index 27dd9a0..cb340ec 100644 --- a/PROJECT_AUDIT_2026-03-03.md +++ b/PROJECT_AUDIT_2026-03-03.md @@ -133,9 +133,15 @@ - 先请求成功再更新 `planAccepted/activeContract`; - 失败回滚并展示错误信息。 -### P2-3 TypeScript 基线不通过(测试代码类型漂移) +### P2-3 TypeScript 基线不通过(测试代码类型漂移)【已完成】 +- 修复状态:✅ 已完成(2026-03-03) +- 修复内容: + - 修复 `createRequest` 与 `NextRequest` 构造参数类型不兼容问题; + - 修复盲盒测试中的 Prisma 事务 mock 签名、SSE 计划 mock 返回结构; + - 补齐路由测试上下文参数与测试数据类型字段(`reason`、`creatorId`、`isLoading/error`、`matchType`); + - `npx tsc --noEmit` 已恢复通过。 - 证据: - - `npx tsc --noEmit` 当前报多个错误(测试辅助类型、mock 签名、类型定义漂移等)。 + - 修复后执行 `npx tsc --noEmit` 已无报错。 - 影响: - 类型系统对回归的兜底失效;重构时风险上升。 - 建议: diff --git a/src/__tests__/helpers/api-test-utils.ts b/src/__tests__/helpers/api-test-utils.ts index c76884d..2583009 100644 --- a/src/__tests__/helpers/api-test-utils.ts +++ b/src/__tests__/helpers/api-test-utils.ts @@ -9,10 +9,12 @@ export function createRequest( } = {}, ): NextRequest { const { method = "GET", body, headers = {} } = options; - const init: RequestInit = { method, headers }; + type NextRequestInit = ConstructorParameters[1]; + const requestHeaders: Record = { ...headers }; + const init: NextRequestInit = { method, headers: requestHeaders }; if (body !== undefined) { init.body = JSON.stringify(body); - (init.headers as Record)["content-type"] = "application/json"; + requestHeaders["content-type"] = "application/json"; } return new NextRequest(new URL(url, "http://localhost:3721"), init); } diff --git a/src/app/api/blindbox/draw/route.test.ts b/src/app/api/blindbox/draw/route.test.ts index 59a842a..69d2ab3 100644 --- a/src/app/api/blindbox/draw/route.test.ts +++ b/src/app/api/blindbox/draw/route.test.ts @@ -20,7 +20,7 @@ beforeEach(() => { describe("POST /api/blindbox/draw", () => { it("draws a random idea", async () => { - prismaMock.$transaction.mockImplementation(async (fn: (tx: unknown) => Promise) => { + prismaMock.$transaction.mockImplementation((async (fn: unknown) => { const tx = { blindBoxIdea: { findMany: vi.fn().mockResolvedValue([{ id: "idea-1" }]), @@ -34,8 +34,8 @@ describe("POST /api/blindbox/draw", () => { }), }, }; - return fn(tx); - }); + return (fn as (txArg: typeof tx) => Promise)(tx); + }) as never); const req = createRequest("/api/blindbox/draw", { method: "POST", @@ -52,7 +52,7 @@ describe("POST /api/blindbox/draw", () => { }); it("returns 404 when pool is empty", async () => { - prismaMock.$transaction.mockImplementation(async (fn: (tx: unknown) => Promise) => { + prismaMock.$transaction.mockImplementation((async (fn: unknown) => { const tx = { blindBoxIdea: { findMany: vi.fn().mockResolvedValue([]), @@ -60,8 +60,8 @@ describe("POST /api/blindbox/draw", () => { findUnique: vi.fn(), }, }; - return fn(tx); - }); + return (fn as (txArg: typeof tx) => Promise)(tx); + }) as never); const req = createRequest("/api/blindbox/draw", { method: "POST", @@ -72,7 +72,7 @@ describe("POST /api/blindbox/draw", () => { }); it("returns 409 on race condition (count=0)", async () => { - prismaMock.$transaction.mockImplementation(async (fn: (tx: unknown) => Promise) => { + prismaMock.$transaction.mockImplementation((async (fn: unknown) => { const tx = { blindBoxIdea: { findMany: vi.fn().mockResolvedValue([{ id: "idea-1" }]), @@ -80,8 +80,8 @@ describe("POST /api/blindbox/draw", () => { findUnique: vi.fn(), }, }; - return fn(tx); - }); + return (fn as (txArg: typeof tx) => Promise)(tx); + }) as never); const req = createRequest("/api/blindbox/draw", { method: "POST", diff --git a/src/app/api/blindbox/plan/stream/route.test.ts b/src/app/api/blindbox/plan/stream/route.test.ts index fc8dcb8..e3e3350 100644 --- a/src/app/api/blindbox/plan/stream/route.test.ts +++ b/src/app/api/blindbox/plan/stream/route.test.ts @@ -45,8 +45,8 @@ describe("POST /api/blindbox/plan/stream", () => { onProgress?.("正在生成行程..."); return { id: "plan-1", - days: [{ date: "周六", items: [] }], - createdAt: new Date(), + days: [{ date: "周六", items: [], summary: "轻松逛吃" }], + createdAt: "2025-03-01T09:00:00.000Z", }; }); diff --git a/src/app/api/blindbox/plan/suggest-item/route.test.ts b/src/app/api/blindbox/plan/suggest-item/route.test.ts index bb556da..fd5fde6 100644 --- a/src/app/api/blindbox/plan/suggest-item/route.test.ts +++ b/src/app/api/blindbox/plan/suggest-item/route.test.ts @@ -22,6 +22,7 @@ import { getAuthUserId } from "@/lib/auth"; const mockSuggestAlternativeItems = vi.mocked(suggestAlternativeItems); const mockSearchPois = vi.mocked(searchPois); const mockGetAuthUserId = vi.mocked(getAuthUserId); +const mockCtx = { params: Promise.resolve({}) }; beforeEach(() => { vi.clearAllMocks(); @@ -35,7 +36,7 @@ describe("POST /api/blindbox/plan/suggest-item", () => { method: "POST", body: { activity: "看展" }, }); - const res = await POST(req); + const res = await POST(req, mockCtx); expect(res.status).toBe(401); }); @@ -65,7 +66,7 @@ describe("POST /api/blindbox/plan/suggest-item", () => { location: "121.47,31.23", }, }); - const res = await POST(req); + const res = await POST(req, mockCtx); const { status, data } = await parseJsonResponse(res); expect(status).toBe(200); diff --git a/src/app/api/blindbox/rooms/route.test.ts b/src/app/api/blindbox/rooms/route.test.ts index c5ce695..5b698ff 100644 --- a/src/app/api/blindbox/rooms/route.test.ts +++ b/src/app/api/blindbox/rooms/route.test.ts @@ -36,9 +36,9 @@ describe("GET /api/blindbox/rooms", () => { }, ] as never); - prismaMock.blindBoxIdea.groupBy.mockResolvedValue([ + prismaMock.blindBoxIdea.groupBy = vi.fn().mockResolvedValue([ { roomId: "bb-room-1", _count: 3 }, - ] as never); + ]) as never; const req = createRequest("/api/blindbox/rooms"); const res = await GET(req, mockCtx); diff --git a/src/app/room/[id]/page.test.tsx b/src/app/room/[id]/page.test.tsx index d2e94f1..54af858 100644 --- a/src/app/room/[id]/page.test.tsx +++ b/src/app/room/[id]/page.test.tsx @@ -46,6 +46,8 @@ vi.mock("@/hooks/useRoomPolling", () => ({ users: ["user-1", "user-2"], userProfiles: {}, scene: "eat", + isLoading: false, + error: undefined, }), })); @@ -114,11 +116,13 @@ describe("RoomPage", () => { restaurants: [], notFound: true, mutate: vi.fn(), - creatorId: null, + creatorId: "", locked: false, users: [], userProfiles: {}, scene: "eat", + isLoading: false, + error: undefined, }); renderPage(); diff --git a/src/components/BlindboxPlan.test.tsx b/src/components/BlindboxPlan.test.tsx index 5457406..7875b65 100644 --- a/src/components/BlindboxPlan.test.tsx +++ b/src/components/BlindboxPlan.test.tsx @@ -36,6 +36,7 @@ const mockDays: WeekendPlanData[] = [ duration: 90, lat: 39.9, lng: 116.4, + reason: "补充体力", }, ], }, @@ -51,6 +52,7 @@ const mockDays: WeekendPlanData[] = [ duration: 180, lat: 39.9, lng: 116.4, + reason: "文化体验", }, ], }, diff --git a/src/components/SwipeDeck.test.tsx b/src/components/SwipeDeck.test.tsx index 00a6657..8105aea 100644 --- a/src/components/SwipeDeck.test.tsx +++ b/src/components/SwipeDeck.test.tsx @@ -41,7 +41,7 @@ const defaultProps = { userId: "user-1", initialIndex: 0, matchedRestaurantId: null, - matchType: null as const, + matchType: null, matchLikes: 0, runnerUps: [], likeCounts: {},