修复 suggest-item 接口缺失鉴权并补充测试

This commit is contained in:
2026-03-03 12:08:35 +08:00
parent 8b4ab415fd
commit 486193c823
3 changed files with 89 additions and 1 deletions
+5 -1
View File
@@ -55,7 +55,11 @@
-`undo/reset` 等相关接口同步增加 ID 合法性校验;
- 增加“非法 restaurantId”单测。
### P1-1 `suggest-item` 接口缺失鉴权(可被匿名滥用,产生外部 API 成本)
### P1-1 `suggest-item` 接口缺失鉴权(可被匿名滥用,产生外部 API 成本)【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- `POST /api/blindbox/plan/suggest-item` 增加登录鉴权(无登录态返回 401);
- 新增接口测试,覆盖“未登录拒绝访问”与“登录成功返回推荐”。
- 证据:
- `src/app/api/blindbox/plan/suggest-item/route.ts:6-11`(无 `getAuthUserId` / 无 membership 校验)
- 影响:
@@ -0,0 +1,82 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { createRequest, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
import { ApiError } from "@/lib/api";
vi.mock("@/lib/ai", () => ({
suggestAlternativeItems: vi.fn(),
}));
vi.mock("@/lib/amap", () => ({
searchPois: vi.fn(),
}));
vi.mock("@/lib/auth", () => ({
getAuthUserId: vi.fn(),
}));
import { POST } from "./route";
import { suggestAlternativeItems } from "@/lib/ai";
import { searchPois } from "@/lib/amap";
import { getAuthUserId } from "@/lib/auth";
const mockSuggestAlternativeItems = vi.mocked(suggestAlternativeItems);
const mockSearchPois = vi.mocked(searchPois);
const mockGetAuthUserId = vi.mocked(getAuthUserId);
beforeEach(() => {
vi.clearAllMocks();
});
describe("POST /api/blindbox/plan/suggest-item", () => {
it("returns 401 when not authenticated", async () => {
mockGetAuthUserId.mockRejectedValue(new ApiError("请先登录", 401));
const req = createRequest("/api/blindbox/plan/suggest-item", {
method: "POST",
body: { activity: "看展" },
});
const res = await POST(req);
expect(res.status).toBe(401);
});
it("returns mapped suggestions for authenticated user", async () => {
mockGetAuthUserId.mockResolvedValue("user-1");
mockSuggestAlternativeItems.mockResolvedValue([
{
activity: "看展",
searchQuery: "上海博物馆",
reason: "交通方便",
},
]);
mockSearchPois.mockResolvedValue([
{
name: "上海博物馆",
address: "黄浦区人民大道201号",
lat: 31.2301,
lng: 121.4737,
},
]);
const req = createRequest("/api/blindbox/plan/suggest-item", {
method: "POST",
body: {
activity: "文艺活动",
time: "14:00",
location: "121.47,31.23",
},
});
const res = await POST(req);
const { status, data } = await parseJsonResponse(res);
expect(status).toBe(200);
expect(data.suggestions).toHaveLength(1);
expect(data.suggestions[0]).toMatchObject({
activity: "看展",
poi: "上海博物馆",
address: "黄浦区人民大道201号",
lat: 31.2301,
lng: 121.4737,
reason: "交通方便",
});
});
});
@@ -2,8 +2,10 @@ import { NextResponse } from "next/server";
import { apiHandler, ApiError } from "@/lib/api";
import { suggestAlternativeItems } from "@/lib/ai";
import { searchPois } from "@/lib/amap";
import { getAuthUserId } from "@/lib/auth";
export const POST = apiHandler(async (req) => {
await getAuthUserId(req);
const { activity, time, location } = await req.json();
if (!activity) throw new ApiError("activity 不能为空", 400);