/** @jest-environment node */ import { GET as getSchemaRoute } from '@/app/api/external/material-import/schema/route' import { POST as postJobsRoute } from '@/app/api/external/material-import/jobs/route' import { GET as getJobStatusRoute } from '@/app/api/external/material-import/jobs/[jobId]/route' import { getExternalMaterialImportJobStatus, getExternalMaterialImportSchema, createExternalMaterialImportJob, triggerExternalMaterialImportJob } from '@/lib/external-material-import' import { logApiCall, validateApiKey } from '@/lib/external-api' jest.mock('@/lib/external-api', () => { const { NextResponse } = require('next/server') return { getApiKeyFromRequest: jest.fn((request: { headers: Headers }) => request.headers.get('X-API-Key')), 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/external-material-import', () => ({ ExternalMaterialImportError: class ExternalMaterialImportError extends Error { status: number code: string constructor(status: number, code: string, message: string) { super(message) this.status = status this.code = code } }, getExternalMaterialImportSchema: jest.fn(), createExternalMaterialImportJob: jest.fn(), getExternalMaterialImportJobStatus: jest.fn(), triggerExternalMaterialImportJob: jest.fn(), })) function createRequest(url: string, body?: unknown) { return { nextUrl: new URL(url), headers: new Headers({ 'X-API-Key': 'test-key', 'user-agent': 'jest', }), json: jest.fn().mockResolvedValue(body), } as any } describe('external material import routes', () => { beforeEach(() => { jest.clearAllMocks() ;(validateApiKey as jest.Mock).mockResolvedValue({ id: 'api-1', name: 'test-key', isActive: true, }) }) test('schema 路由应返回服务层 schema 结果', async () => { ;(getExternalMaterialImportSchema as jest.Mock).mockResolvedValue({ materialType: '任务-详情', open: true, fields: [], imageGroups: { allowedKeys: ['main', 'sku', 'detail'], minTotalImages: 1, }, }) const response = await getSchemaRoute( createRequest('https://example.com/api/external/material-import/schema?materialType=任务-详情'), ) const body = await response.json() expect(response.status).toBe(200) expect(body.success).toBe(true) expect(body.data.materialType).toBe('任务-详情') expect(getExternalMaterialImportSchema).toHaveBeenCalledWith('任务-详情') expect(logApiCall).toHaveBeenCalled() }) test('jobs 路由创建新任务时应触发后台处理', async () => { ;(createExternalMaterialImportJob as jest.Mock).mockResolvedValue({ created: true, job: { jobId: 'job-1', materialType: '任务-详情', taskNo: 'TASK202603240001', status: 'processing', success: false, resultMaterialId: null, errorCode: null, errorMessage: null, completedAt: null, }, }) const response = await postJobsRoute( createRequest('https://example.com/api/external/material-import/jobs', { formData: { taskNo: 'TASK202603240001', }, }), ) const body = await response.json() expect(response.status).toBe(200) expect(body.success).toBe(true) expect(body.data.created).toBe(true) expect(triggerExternalMaterialImportJob).toHaveBeenCalledWith('job-1') }) test('job status 路由应透传 jobId 和 apiKey 范围查询', async () => { ;(getExternalMaterialImportJobStatus as jest.Mock).mockResolvedValue({ jobId: 'job-1', materialType: '任务-详情', taskNo: 'TASK202603240001', status: 'succeeded', success: true, resultMaterialId: 'material-1', errorCode: null, errorMessage: null, completedAt: '2026-03-24T09:02:00.000Z', }) const response = await getJobStatusRoute( createRequest('https://example.com/api/external/material-import/jobs/job-1'), { params: { jobId: 'job-1' } }, ) const body = await response.json() expect(response.status).toBe(200) expect(body.success).toBe(true) expect(body.data.success).toBe(true) expect(getExternalMaterialImportJobStatus).toHaveBeenCalledWith({ apiKeyId: 'api-1', jobId: 'job-1', }) }) })