修复 TypeScript 基线并补齐测试类型
This commit is contained in:
@@ -133,9 +133,15 @@
|
|||||||
- 先请求成功再更新 `planAccepted/activeContract`;
|
- 先请求成功再更新 `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` 已无报错。
|
||||||
- 影响:
|
- 影响:
|
||||||
- 类型系统对回归的兜底失效;重构时风险上升。
|
- 类型系统对回归的兜底失效;重构时风险上升。
|
||||||
- 建议:
|
- 建议:
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ export function createRequest(
|
|||||||
} = {},
|
} = {},
|
||||||
): NextRequest {
|
): NextRequest {
|
||||||
const { method = "GET", body, headers = {} } = options;
|
const { method = "GET", body, headers = {} } = options;
|
||||||
const init: RequestInit = { method, headers };
|
type NextRequestInit = ConstructorParameters<typeof NextRequest>[1];
|
||||||
|
const requestHeaders: Record<string, string> = { ...headers };
|
||||||
|
const init: NextRequestInit = { method, headers: requestHeaders };
|
||||||
if (body !== undefined) {
|
if (body !== undefined) {
|
||||||
init.body = JSON.stringify(body);
|
init.body = JSON.stringify(body);
|
||||||
(init.headers as Record<string, string>)["content-type"] = "application/json";
|
requestHeaders["content-type"] = "application/json";
|
||||||
}
|
}
|
||||||
return new NextRequest(new URL(url, "http://localhost:3721"), init);
|
return new NextRequest(new URL(url, "http://localhost:3721"), init);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
describe("POST /api/blindbox/draw", () => {
|
describe("POST /api/blindbox/draw", () => {
|
||||||
it("draws a random idea", async () => {
|
it("draws a random idea", async () => {
|
||||||
prismaMock.$transaction.mockImplementation(async (fn: (tx: unknown) => Promise<unknown>) => {
|
prismaMock.$transaction.mockImplementation((async (fn: unknown) => {
|
||||||
const tx = {
|
const tx = {
|
||||||
blindBoxIdea: {
|
blindBoxIdea: {
|
||||||
findMany: vi.fn().mockResolvedValue([{ id: "idea-1" }]),
|
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<unknown>)(tx);
|
||||||
});
|
}) as never);
|
||||||
|
|
||||||
const req = createRequest("/api/blindbox/draw", {
|
const req = createRequest("/api/blindbox/draw", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -52,7 +52,7 @@ describe("POST /api/blindbox/draw", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 when pool is empty", async () => {
|
it("returns 404 when pool is empty", async () => {
|
||||||
prismaMock.$transaction.mockImplementation(async (fn: (tx: unknown) => Promise<unknown>) => {
|
prismaMock.$transaction.mockImplementation((async (fn: unknown) => {
|
||||||
const tx = {
|
const tx = {
|
||||||
blindBoxIdea: {
|
blindBoxIdea: {
|
||||||
findMany: vi.fn().mockResolvedValue([]),
|
findMany: vi.fn().mockResolvedValue([]),
|
||||||
@@ -60,8 +60,8 @@ describe("POST /api/blindbox/draw", () => {
|
|||||||
findUnique: vi.fn(),
|
findUnique: vi.fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return fn(tx);
|
return (fn as (txArg: typeof tx) => Promise<unknown>)(tx);
|
||||||
});
|
}) as never);
|
||||||
|
|
||||||
const req = createRequest("/api/blindbox/draw", {
|
const req = createRequest("/api/blindbox/draw", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -72,7 +72,7 @@ describe("POST /api/blindbox/draw", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns 409 on race condition (count=0)", async () => {
|
it("returns 409 on race condition (count=0)", async () => {
|
||||||
prismaMock.$transaction.mockImplementation(async (fn: (tx: unknown) => Promise<unknown>) => {
|
prismaMock.$transaction.mockImplementation((async (fn: unknown) => {
|
||||||
const tx = {
|
const tx = {
|
||||||
blindBoxIdea: {
|
blindBoxIdea: {
|
||||||
findMany: vi.fn().mockResolvedValue([{ id: "idea-1" }]),
|
findMany: vi.fn().mockResolvedValue([{ id: "idea-1" }]),
|
||||||
@@ -80,8 +80,8 @@ describe("POST /api/blindbox/draw", () => {
|
|||||||
findUnique: vi.fn(),
|
findUnique: vi.fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return fn(tx);
|
return (fn as (txArg: typeof tx) => Promise<unknown>)(tx);
|
||||||
});
|
}) as never);
|
||||||
|
|
||||||
const req = createRequest("/api/blindbox/draw", {
|
const req = createRequest("/api/blindbox/draw", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ describe("POST /api/blindbox/plan/stream", () => {
|
|||||||
onProgress?.("正在生成行程...");
|
onProgress?.("正在生成行程...");
|
||||||
return {
|
return {
|
||||||
id: "plan-1",
|
id: "plan-1",
|
||||||
days: [{ date: "周六", items: [] }],
|
days: [{ date: "周六", items: [], summary: "轻松逛吃" }],
|
||||||
createdAt: new Date(),
|
createdAt: "2025-03-01T09:00:00.000Z",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { getAuthUserId } from "@/lib/auth";
|
|||||||
const mockSuggestAlternativeItems = vi.mocked(suggestAlternativeItems);
|
const mockSuggestAlternativeItems = vi.mocked(suggestAlternativeItems);
|
||||||
const mockSearchPois = vi.mocked(searchPois);
|
const mockSearchPois = vi.mocked(searchPois);
|
||||||
const mockGetAuthUserId = vi.mocked(getAuthUserId);
|
const mockGetAuthUserId = vi.mocked(getAuthUserId);
|
||||||
|
const mockCtx = { params: Promise.resolve({}) };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -35,7 +36,7 @@ describe("POST /api/blindbox/plan/suggest-item", () => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: { activity: "看展" },
|
body: { activity: "看展" },
|
||||||
});
|
});
|
||||||
const res = await POST(req);
|
const res = await POST(req, mockCtx);
|
||||||
expect(res.status).toBe(401);
|
expect(res.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ describe("POST /api/blindbox/plan/suggest-item", () => {
|
|||||||
location: "121.47,31.23",
|
location: "121.47,31.23",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const res = await POST(req);
|
const res = await POST(req, mockCtx);
|
||||||
const { status, data } = await parseJsonResponse(res);
|
const { status, data } = await parseJsonResponse(res);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ describe("GET /api/blindbox/rooms", () => {
|
|||||||
},
|
},
|
||||||
] as never);
|
] as never);
|
||||||
|
|
||||||
prismaMock.blindBoxIdea.groupBy.mockResolvedValue([
|
prismaMock.blindBoxIdea.groupBy = vi.fn().mockResolvedValue([
|
||||||
{ roomId: "bb-room-1", _count: 3 },
|
{ roomId: "bb-room-1", _count: 3 },
|
||||||
] as never);
|
]) as never;
|
||||||
|
|
||||||
const req = createRequest("/api/blindbox/rooms");
|
const req = createRequest("/api/blindbox/rooms");
|
||||||
const res = await GET(req, mockCtx);
|
const res = await GET(req, mockCtx);
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ vi.mock("@/hooks/useRoomPolling", () => ({
|
|||||||
users: ["user-1", "user-2"],
|
users: ["user-1", "user-2"],
|
||||||
userProfiles: {},
|
userProfiles: {},
|
||||||
scene: "eat",
|
scene: "eat",
|
||||||
|
isLoading: false,
|
||||||
|
error: undefined,
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -114,11 +116,13 @@ describe("RoomPage", () => {
|
|||||||
restaurants: [],
|
restaurants: [],
|
||||||
notFound: true,
|
notFound: true,
|
||||||
mutate: vi.fn(),
|
mutate: vi.fn(),
|
||||||
creatorId: null,
|
creatorId: "",
|
||||||
locked: false,
|
locked: false,
|
||||||
users: [],
|
users: [],
|
||||||
userProfiles: {},
|
userProfiles: {},
|
||||||
scene: "eat",
|
scene: "eat",
|
||||||
|
isLoading: false,
|
||||||
|
error: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
renderPage();
|
renderPage();
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const mockDays: WeekendPlanData[] = [
|
|||||||
duration: 90,
|
duration: 90,
|
||||||
lat: 39.9,
|
lat: 39.9,
|
||||||
lng: 116.4,
|
lng: 116.4,
|
||||||
|
reason: "补充体力",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -51,6 +52,7 @@ const mockDays: WeekendPlanData[] = [
|
|||||||
duration: 180,
|
duration: 180,
|
||||||
lat: 39.9,
|
lat: 39.9,
|
||||||
lng: 116.4,
|
lng: 116.4,
|
||||||
|
reason: "文化体验",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const defaultProps = {
|
|||||||
userId: "user-1",
|
userId: "user-1",
|
||||||
initialIndex: 0,
|
initialIndex: 0,
|
||||||
matchedRestaurantId: null,
|
matchedRestaurantId: null,
|
||||||
matchType: null as const,
|
matchType: null,
|
||||||
matchLikes: 0,
|
matchLikes: 0,
|
||||||
runnerUps: [],
|
runnerUps: [],
|
||||||
likeCounts: {},
|
likeCounts: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user