修复 suggest-item 接口缺失鉴权并补充测试
This commit is contained in:
@@ -55,7 +55,11 @@
|
|||||||
- 对 `undo/reset` 等相关接口同步增加 ID 合法性校验;
|
- 对 `undo/reset` 等相关接口同步增加 ID 合法性校验;
|
||||||
- 增加“非法 restaurantId”单测。
|
- 增加“非法 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 校验)
|
- `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 { apiHandler, ApiError } from "@/lib/api";
|
||||||
import { suggestAlternativeItems } from "@/lib/ai";
|
import { suggestAlternativeItems } from "@/lib/ai";
|
||||||
import { searchPois } from "@/lib/amap";
|
import { searchPois } from "@/lib/amap";
|
||||||
|
import { getAuthUserId } from "@/lib/auth";
|
||||||
|
|
||||||
export const POST = apiHandler(async (req) => {
|
export const POST = apiHandler(async (req) => {
|
||||||
|
await getAuthUserId(req);
|
||||||
const { activity, time, location } = await req.json();
|
const { activity, time, location } = await req.json();
|
||||||
if (!activity) throw new ApiError("activity 不能为空", 400);
|
if (!activity) throw new ApiError("activity 不能为空", 400);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user