gongxiangp2/lib/__tests__/dingtalk-material-match-route.test.ts

205 lines
6.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @jest-environment node */
import { GET } from '@/app/api/external/dingtalk-material-match/route'
import { prisma } from '@/lib/prisma'
import { logApiCall, validateApiKey } from '@/lib/external-api'
jest.mock('@/lib/external-api', () => {
const { NextResponse } = require('next/server')
return {
getApiKeyFromRequest: jest.fn(
(request: { headers: Headers; nextUrl: URL }, options?: { allowQuery?: boolean }) =>
request.headers.get('X-API-Key') ||
(options?.allowQuery ? request.nextUrl.searchParams.get('apiKey') : null),
),
getClientIp: jest.fn(() => '127.0.0.1'),
logApiCall: jest.fn().mockResolvedValue(undefined),
successResponse: jest.fn((data: unknown, message?: string) =>
NextResponse.json({
success: true,
...(message ? { message } : {}),
data,
}),
),
validateApiKey: jest.fn(),
}
})
jest.mock('@/lib/prisma', () => ({
prisma: {
material: {
findFirst: jest.fn(),
},
},
}))
jest.mock('@/lib/public-app-url', () => ({
getPublicAppOrigin: jest.fn(() => 'https://sc.jiansun.vip'),
}))
jest.mock('@/lib/url-rewrite', () => ({
rewriteStorageUrlForClient: jest.fn((url?: string | null) => {
if (!url) {
return null
}
return /^https?:\/\//i.test(url) ? url : `/${String(url).replace(/^\/+/, '')}`
}),
}))
describe('external dingtalk-material-match route', () => {
beforeEach(() => {
jest.clearAllMocks()
;(prisma.material.findFirst as jest.Mock).mockReset()
;(logApiCall as jest.Mock).mockReset().mockResolvedValue(undefined)
;(validateApiKey as jest.Mock).mockReset().mockResolvedValue({
id: 'api-key-1',
name: 'test-key',
isActive: true,
})
})
function createRequest(url: string, apiKey = 'test-key') {
return {
nextUrl: new URL(url),
headers: new Headers({
...(apiKey ? { 'X-API-Key': apiKey } : {}),
'user-agent': 'jest',
}),
} as any
}
test('缺少 SPU 时应返回 400', async () => {
const response = await GET(createRequest('https://example.com/api/external/dingtalk-material-match'))
const body = await response.json()
expect(response.status).toBe(400)
expect(body).toEqual({
success: false,
error: {
code: 'MISSING_SPU',
message: 'SPU 为必填项',
},
})
})
test('命中素材时应返回任意地址与匹配摘要', async () => {
;(prisma.material.findFirst as jest.Mock).mockResolvedValueOnce({
id: 'material-1',
productModel: 'SPU001',
materialType: '任务-详情',
categoryName: '顾家家居',
sceneName: '客厅',
productCategoryName: '沙发',
previewUrl: 'uploads/previews/spu001.jpg',
videoPreview540Url: null,
videoCoverUrl: null,
zipFilePath: 'uploads/zip/spu001.zip',
updatedAt: new Date('2026-04-30T08:00:00.000Z'),
})
const response = await GET(
createRequest(
'https://example.com/api/external/dingtalk-material-match?requirementType=任务-详情&brand=顾家家居&sceneCategory=客厅&productCategory=沙发&spu=spu001',
),
)
const body = await response.json()
expect(response.status).toBe(200)
expect(body.success).toBe(true)
expect(body.data).toEqual({
matched: true,
message: '查询成功',
spu: 'SPU001',
materialId: 'material-1',
requirementType: '任务-详情',
brand: '顾家家居',
sceneCategory: '客厅',
productCategory: '沙发',
address: 'https://sc.jiansun.vip/uploads/previews/spu001.jpg',
updatedAt: '2026-04-30T08:00:00.000Z',
})
expect(prisma.material.findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
productModel: 'SPU001',
materialType: '任务-详情',
categoryName: '顾家家居',
sceneName: '客厅',
productCategoryName: '沙发',
}),
}),
)
})
test('只传 SPU 时也应查询,不强制要求其它筛选项', async () => {
;(prisma.material.findFirst as jest.Mock).mockResolvedValueOnce({
id: 'material-spu-only',
productModel: 'SPU002',
materialType: '抖音-千川素材',
categoryName: '顾家家居',
sceneName: '客厅',
productCategoryName: '沙发',
previewUrl: 'https://s3.example/preview.webp',
videoPreview540Url: null,
videoCoverUrl: null,
zipFilePath: 'uploads/zip/spu002.zip',
updatedAt: new Date('2026-05-07T08:00:00.000Z'),
})
const response = await GET(
createRequest('https://example.com/api/external/dingtalk-material-match?spu=spu002'),
)
const body = await response.json()
expect(response.status).toBe(200)
expect(body.success).toBe(true)
expect(body.data.matched).toBe(true)
expect(prisma.material.findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: {
deletedAt: null,
productModel: 'SPU002',
taskDetailPrivate: false,
},
}),
)
})
test('未命中时应返回该SPU下暂无素材', async () => {
;(prisma.material.findFirst as jest.Mock).mockResolvedValueOnce(null)
const response = await GET(
createRequest('https://example.com/api/external/dingtalk-material-match?spu=spu001&brand=顾家家居'),
)
const body = await response.json()
expect(response.status).toBe(200)
expect(body.success).toBe(true)
expect(body.data).toEqual({
matched: false,
message: '该SPU下暂无素材',
spu: 'SPU001',
requirementType: null,
brand: '顾家家居',
sceneCategory: null,
productCategory: null,
address: null,
})
})
test('允许钉钉用固定 URL 参数传 apiKey,避免每次手填请求头', async () => {
;(prisma.material.findFirst as jest.Mock).mockResolvedValueOnce(null)
const response = await GET(
createRequest(
'https://example.com/api/external/dingtalk-material-match?apiKey=test-key&spu=spu001',
'',
),
)
expect(response.status).toBe(200)
expect(validateApiKey).toHaveBeenCalledWith('test-key')
})
})