1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
747 lines
21 KiB
TypeScript
747 lines
21 KiB
TypeScript
/**
|
|
* 模板系统集成测试
|
|
*
|
|
* 测试完整的模板创建、素材上传、字段池同步、分类管理流程
|
|
*
|
|
* @module integration/template-workflow
|
|
* @created 2025-12-13
|
|
*/
|
|
|
|
import {
|
|
validateTemplateId,
|
|
isBuiltinTemplate,
|
|
extractBuiltinTemplateKey,
|
|
BUILTIN_TEMPLATE_KEYS,
|
|
TemplateValidationError,
|
|
} from '../../template-validator'
|
|
import {
|
|
normalizeFieldOptionValue,
|
|
parseOptionsStatic,
|
|
expandOptionValue,
|
|
OptionCleaningIssue,
|
|
} from '../../field-pool'
|
|
|
|
// Mock prisma
|
|
jest.mock('../../prisma', () => ({
|
|
prisma: {
|
|
uploadTemplate: {
|
|
findUnique: jest.fn(),
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
uploadField: {
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
deleteMany: jest.fn(),
|
|
},
|
|
fieldDefinition: {
|
|
findUnique: jest.fn(),
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
upsert: jest.fn(),
|
|
deleteMany: jest.fn(),
|
|
},
|
|
fieldOption: {
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
deleteMany: jest.fn(),
|
|
},
|
|
material: {
|
|
findUnique: jest.fn(),
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
count: jest.fn(),
|
|
},
|
|
$transaction: jest.fn((callback) => callback({
|
|
fieldDefinition: {
|
|
findUnique: jest.fn(),
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
upsert: jest.fn().mockResolvedValue({ id: 'field-1' }),
|
|
deleteMany: jest.fn(),
|
|
},
|
|
fieldOption: {
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
deleteMany: jest.fn(),
|
|
},
|
|
})),
|
|
},
|
|
}))
|
|
|
|
const { prisma } = require('../../prisma')
|
|
|
|
describe('集成测试: 模板创建流程', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('1. 模板创建和验证', () => {
|
|
test('应该成功创建自定义模板', async () => {
|
|
const mockTemplate = {
|
|
id: 'custom-template-1',
|
|
name: '测试模板',
|
|
description: '测试描述',
|
|
status: 'active',
|
|
materialType: '3DMAX',
|
|
materialKind: 'IMAGE',
|
|
version: 1,
|
|
fields: [],
|
|
}
|
|
|
|
prisma.uploadTemplate.create.mockResolvedValue(mockTemplate)
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue(mockTemplate)
|
|
|
|
// 验证创建的模板
|
|
const validation = await validateTemplateId('custom-template-1')
|
|
expect(validation.valid).toBe(true)
|
|
expect(validation.isBuiltin).toBe(false)
|
|
expect(validation.templateName).toBe('测试模板')
|
|
})
|
|
|
|
test('应该验证内置模板', async () => {
|
|
for (const key of BUILTIN_TEMPLATE_KEYS) {
|
|
const templateId = `builtin-${key}`
|
|
const validation = await validateTemplateId(templateId)
|
|
|
|
expect(validation.valid).toBe(true)
|
|
expect(validation.isBuiltin).toBe(true)
|
|
expect(validation.templateKey).toBe(key)
|
|
}
|
|
})
|
|
|
|
test('应该拒绝无效的模板ID', async () => {
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue(null)
|
|
|
|
const validation = await validateTemplateId('non-existent-template')
|
|
expect(validation.valid).toBe(false)
|
|
expect(validation.errorCode).toBe(TemplateValidationError.TEMPLATE_NOT_FOUND)
|
|
})
|
|
|
|
test('应该检查模板状态', async () => {
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue({
|
|
id: 'draft-template',
|
|
name: '草稿模板',
|
|
status: 'draft',
|
|
})
|
|
|
|
const validation = await validateTemplateId('draft-template', { checkStatus: true })
|
|
expect(validation.valid).toBe(false)
|
|
expect(validation.errorCode).toBe(TemplateValidationError.TEMPLATE_NOT_ACTIVE)
|
|
})
|
|
})
|
|
|
|
describe('2. 模板字段配置', () => {
|
|
test('应该正确解析字段选项', () => {
|
|
// JSON数组格式
|
|
const jsonOptions = parseOptionsStatic('["选项1", "选项2", "选项3"]')
|
|
expect(jsonOptions).toEqual(['选项1', '选项2', '选项3'])
|
|
|
|
// 换行格式
|
|
const newlineOptions = parseOptionsStatic('选项A\n选项B\n选项C')
|
|
expect(newlineOptions).toEqual(['选项A', '选项B', '选项C'])
|
|
})
|
|
|
|
test('应该清洗脏数据选项', () => {
|
|
// 多余空白
|
|
expect(normalizeFieldOptionValue(' 选项 ')).toBe('选项')
|
|
|
|
// 特殊引号
|
|
expect(normalizeFieldOptionValue('"选项"')).toBe('选项')
|
|
|
|
// 嵌套数组
|
|
expect(normalizeFieldOptionValue('[[选项]]')).toBe('选项')
|
|
})
|
|
|
|
test('应该去重选项', () => {
|
|
const options = parseOptionsStatic('["选项1", "选项2", "选项1", "选项3", "选项2"]')
|
|
expect(options).toEqual(['选项1', '选项2', '选项3'])
|
|
})
|
|
|
|
test('应该展开嵌套数组选项', () => {
|
|
const expanded = expandOptionValue('[选项1, 选项2, 选项3]')
|
|
expect(expanded).toEqual(['选项1', '选项2', '选项3'])
|
|
})
|
|
})
|
|
|
|
describe('3. 模板版本控制', () => {
|
|
test('应该检测版本冲突', async () => {
|
|
const mockTemplate = {
|
|
id: 'versioned-template',
|
|
name: '版本控制模板',
|
|
status: 'active',
|
|
version: 2,
|
|
}
|
|
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue(mockTemplate)
|
|
|
|
// 模拟客户端提交旧版本
|
|
const clientVersion = 1
|
|
const serverVersion = mockTemplate.version
|
|
|
|
expect(clientVersion < serverVersion).toBe(true)
|
|
})
|
|
|
|
test('应该在更新后递增版本号', async () => {
|
|
const originalVersion = 1
|
|
const updatedTemplate = {
|
|
id: 'versioned-template',
|
|
name: '更新后的模板',
|
|
status: 'active',
|
|
version: originalVersion + 1,
|
|
}
|
|
|
|
prisma.uploadTemplate.update.mockResolvedValue(updatedTemplate)
|
|
|
|
expect(updatedTemplate.version).toBe(2)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('集成测试: 素材上传流程', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('1. 模板验证', () => {
|
|
test('应该在上传前验证模板', async () => {
|
|
// 有效模板
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue({
|
|
id: 'valid-template',
|
|
name: '有效模板',
|
|
status: 'active',
|
|
})
|
|
|
|
const validation = await validateTemplateId('valid-template', { checkStatus: true })
|
|
expect(validation.valid).toBe(true)
|
|
})
|
|
|
|
test('应该拒绝使用非活跃模板上传', async () => {
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue({
|
|
id: 'archived-template',
|
|
name: '已归档模板',
|
|
status: 'archived',
|
|
})
|
|
|
|
const validation = await validateTemplateId('archived-template', { checkStatus: true })
|
|
expect(validation.valid).toBe(false)
|
|
})
|
|
|
|
test('应该允许不指定模板上传(向后兼容)', async () => {
|
|
const validation = await validateTemplateId(null)
|
|
expect(validation.valid).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('2. 字段数据处理', () => {
|
|
test('应该正确处理模板字段值', () => {
|
|
const templateFields = {
|
|
categoryName: '家具',
|
|
sceneName: '客厅',
|
|
brandName: '品牌A',
|
|
}
|
|
|
|
// 验证字段值不为空
|
|
expect(templateFields.categoryName).toBeTruthy()
|
|
expect(templateFields.sceneName).toBeTruthy()
|
|
expect(templateFields.brandName).toBeTruthy()
|
|
})
|
|
|
|
test('应该处理空字段值', () => {
|
|
const normalizeInput = (value: string | null) => {
|
|
if (!value) return null
|
|
const trimmed = value.trim()
|
|
return trimmed ? trimmed : null
|
|
}
|
|
|
|
expect(normalizeInput(null)).toBeNull()
|
|
expect(normalizeInput('')).toBeNull()
|
|
expect(normalizeInput(' ')).toBeNull()
|
|
expect(normalizeInput('有效值')).toBe('有效值')
|
|
})
|
|
})
|
|
|
|
describe('3. 素材类型处理', () => {
|
|
test('应该正确识别素材类型', () => {
|
|
const materialKindMap: Record<string, string> = {
|
|
'3DMAX': 'IMAGE',
|
|
'PSD': 'IMAGE',
|
|
'视频': 'VIDEO',
|
|
'模特': 'IMAGE',
|
|
'材质': 'IMAGE',
|
|
'AI': 'IMAGE',
|
|
'其他': 'OTHER',
|
|
}
|
|
|
|
expect(materialKindMap['3DMAX']).toBe('IMAGE')
|
|
expect(materialKindMap['视频']).toBe('VIDEO')
|
|
expect(materialKindMap['其他']).toBe('OTHER')
|
|
})
|
|
|
|
test('应该处理视频素材的转码状态', () => {
|
|
const videoMaterial = {
|
|
id: 'video-1',
|
|
materialKind: 'VIDEO',
|
|
videoProcessingStatus: 'pending',
|
|
}
|
|
|
|
expect(videoMaterial.videoProcessingStatus).toBe('pending')
|
|
})
|
|
})
|
|
})
|
|
|
|
|
|
describe('集成测试: 字段池同步流程', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('1. 字段聚合', () => {
|
|
test('应该从多个模板聚合相同字段', () => {
|
|
// 模拟多个模板的相同字段
|
|
const template1Field = {
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
type: 'select',
|
|
optionsStatic: '["家具", "灯具"]',
|
|
templateId: 'template-1',
|
|
}
|
|
|
|
const template2Field = {
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
type: 'select',
|
|
optionsStatic: '["家具", "装饰"]',
|
|
templateId: 'template-2',
|
|
}
|
|
|
|
// 聚合选项
|
|
const options1 = parseOptionsStatic(template1Field.optionsStatic)
|
|
const options2 = parseOptionsStatic(template2Field.optionsStatic)
|
|
const mergedOptions = Array.from(new Set([...options1, ...options2]))
|
|
|
|
expect(mergedOptions).toContain('家具')
|
|
expect(mergedOptions).toContain('灯具')
|
|
expect(mergedOptions).toContain('装饰')
|
|
expect(mergedOptions.length).toBe(3)
|
|
})
|
|
|
|
test('应该区分标准字段和自定义字段', () => {
|
|
const standardFieldKeys = ['categoryName', 'sceneName', 'platformName', 'brandName']
|
|
const customFieldKey = 'customField1'
|
|
|
|
expect(standardFieldKeys.includes('categoryName')).toBe(true)
|
|
expect(standardFieldKeys.includes(customFieldKey)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('2. 选项清洗', () => {
|
|
test('应该处理各种脏数据格式', () => {
|
|
// 空值
|
|
expect(normalizeFieldOptionValue('')).toBe('')
|
|
expect(normalizeFieldOptionValue(' ')).toBe('')
|
|
|
|
// Unicode控制字符
|
|
expect(normalizeFieldOptionValue('选项\u0000')).toBe('选项')
|
|
|
|
// 多余空白
|
|
expect(normalizeFieldOptionValue('选项 A')).toBe('选项 A')
|
|
|
|
// 嵌套括号
|
|
expect(normalizeFieldOptionValue('[[[选项]]]')).toBe('选项')
|
|
})
|
|
|
|
test('应该记录清洗问题', () => {
|
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
|
|
normalizeFieldOptionValue(' 脏数据 ', { logIssues: true })
|
|
|
|
// 清洗后应该有日志
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
test('应该处理JSON格式选项', () => {
|
|
const jsonOptions = '["选项1", "选项2", "选项3"]'
|
|
const parsed = parseOptionsStatic(jsonOptions)
|
|
|
|
expect(parsed).toHaveLength(3)
|
|
expect(parsed).toEqual(['选项1', '选项2', '选项3'])
|
|
})
|
|
|
|
test('应该处理换行格式选项', () => {
|
|
const newlineOptions = '选项A\n选项B\n选项C'
|
|
const parsed = parseOptionsStatic(newlineOptions)
|
|
|
|
expect(parsed).toHaveLength(3)
|
|
expect(parsed).toEqual(['选项A', '选项B', '选项C'])
|
|
})
|
|
})
|
|
|
|
describe('3. 同步机制', () => {
|
|
test('应该支持增量同步', async () => {
|
|
// 模拟增量同步结果
|
|
const syncResult = {
|
|
success: true,
|
|
duration: 100,
|
|
fieldsAdded: 1,
|
|
fieldsUpdated: 2,
|
|
fieldsRemoved: 0,
|
|
optionsAdded: 5,
|
|
optionsRemoved: 1,
|
|
errors: [],
|
|
}
|
|
|
|
expect(syncResult.success).toBe(true)
|
|
expect(syncResult.fieldsAdded).toBeGreaterThanOrEqual(0)
|
|
expect(syncResult.errors).toHaveLength(0)
|
|
})
|
|
|
|
test('应该处理同步错误', () => {
|
|
const syncResultWithError = {
|
|
success: false,
|
|
duration: 50,
|
|
fieldsAdded: 0,
|
|
fieldsUpdated: 0,
|
|
fieldsRemoved: 0,
|
|
optionsAdded: 0,
|
|
optionsRemoved: 0,
|
|
errors: ['数据库连接失败'],
|
|
}
|
|
|
|
expect(syncResultWithError.success).toBe(false)
|
|
expect(syncResultWithError.errors.length).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('集成测试: 分类管理流程', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('1. 字段启用/禁用', () => {
|
|
test('应该切换字段的筛选启用状态', async () => {
|
|
const mockField = {
|
|
id: 'field-1',
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
isFilterEnabled: false,
|
|
}
|
|
|
|
prisma.fieldDefinition.findUnique.mockResolvedValue(mockField)
|
|
prisma.fieldDefinition.update.mockResolvedValue({
|
|
...mockField,
|
|
isFilterEnabled: true,
|
|
})
|
|
|
|
const updatedField = await prisma.fieldDefinition.update({
|
|
where: { id: mockField.id },
|
|
data: { isFilterEnabled: true },
|
|
})
|
|
|
|
expect(updatedField.isFilterEnabled).toBe(true)
|
|
})
|
|
|
|
test('应该批量启用/禁用字段', async () => {
|
|
const fieldKeys = ['categoryName', 'sceneName', 'brandName']
|
|
const enabled = true
|
|
|
|
// 模拟批量更新
|
|
for (const key of fieldKeys) {
|
|
prisma.fieldDefinition.update.mockResolvedValueOnce({
|
|
key,
|
|
isFilterEnabled: enabled,
|
|
})
|
|
}
|
|
|
|
// 验证所有字段都被更新
|
|
expect(fieldKeys.length).toBe(3)
|
|
})
|
|
})
|
|
|
|
describe('2. 字段排序', () => {
|
|
test('应该更新字段的筛选顺序', () => {
|
|
const updates = [
|
|
{ fieldKey: 'categoryName', filterOrder: 0 },
|
|
{ fieldKey: 'sceneName', filterOrder: 1 },
|
|
{ fieldKey: 'brandName', filterOrder: 2 },
|
|
]
|
|
|
|
// 验证顺序数据结构正确
|
|
expect(updates[0].filterOrder).toBe(0)
|
|
expect(updates[1].filterOrder).toBe(1)
|
|
expect(updates[2].filterOrder).toBe(2)
|
|
expect(updates.length).toBe(3)
|
|
})
|
|
})
|
|
|
|
describe('3. 字段重命名', () => {
|
|
test('应该更新字段的显示名称', async () => {
|
|
// 重置mock
|
|
prisma.fieldDefinition.update.mockReset()
|
|
|
|
const mockField = {
|
|
id: 'field-1',
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
filterDisplayName: '分类',
|
|
}
|
|
|
|
const updatedMockField = {
|
|
...mockField,
|
|
filterDisplayName: '产品分类',
|
|
}
|
|
|
|
prisma.fieldDefinition.update.mockResolvedValue(updatedMockField)
|
|
|
|
const updatedField = await prisma.fieldDefinition.update({
|
|
where: { id: mockField.id },
|
|
data: { filterDisplayName: '产品分类' },
|
|
})
|
|
|
|
expect(updatedField.filterDisplayName).toBe('产品分类')
|
|
})
|
|
|
|
test('应该处理空的显示名称', async () => {
|
|
// 重置mock
|
|
prisma.fieldDefinition.update.mockReset()
|
|
|
|
const mockField = {
|
|
id: 'field-1',
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
filterDisplayName: null,
|
|
}
|
|
|
|
prisma.fieldDefinition.update.mockResolvedValue(mockField)
|
|
|
|
const updatedField = await prisma.fieldDefinition.update({
|
|
where: { id: mockField.id },
|
|
data: { filterDisplayName: null },
|
|
})
|
|
|
|
expect(updatedField.filterDisplayName).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('4. 选项管理', () => {
|
|
test('应该添加新选项', async () => {
|
|
const newOption = {
|
|
id: 'option-new',
|
|
fieldId: 'field-1',
|
|
value: '新选项',
|
|
label: '新选项',
|
|
orderIndex: 0,
|
|
}
|
|
|
|
prisma.fieldOption.create.mockResolvedValue(newOption)
|
|
|
|
const createdOption = await prisma.fieldOption.create({
|
|
data: newOption,
|
|
})
|
|
|
|
expect(createdOption.value).toBe('新选项')
|
|
})
|
|
|
|
test('应该删除选项', async () => {
|
|
prisma.fieldOption.deleteMany.mockResolvedValue({ count: 1 })
|
|
|
|
const result = await prisma.fieldOption.deleteMany({
|
|
where: { id: 'option-to-delete' },
|
|
})
|
|
|
|
expect(result.count).toBe(1)
|
|
})
|
|
|
|
test('应该重命名选项', async () => {
|
|
const mockOption = {
|
|
id: 'option-1',
|
|
value: '旧名称',
|
|
label: '旧名称',
|
|
}
|
|
|
|
prisma.fieldOption.update.mockResolvedValue({
|
|
...mockOption,
|
|
value: '新名称',
|
|
label: '新名称',
|
|
})
|
|
|
|
const updatedOption = await prisma.fieldOption.update({
|
|
where: { id: mockOption.id },
|
|
data: { value: '新名称', label: '新名称' },
|
|
})
|
|
|
|
expect(updatedOption.value).toBe('新名称')
|
|
})
|
|
})
|
|
|
|
describe('5. 批量操作', () => {
|
|
test('应该支持CSV格式导入', () => {
|
|
const csvContent = `value,label
|
|
选项1,选项1标签
|
|
选项2,选项2标签
|
|
选项3,选项3标签`
|
|
|
|
const lines = csvContent.split('\n')
|
|
const headers = lines[0].split(',')
|
|
const data = lines.slice(1).map(line => {
|
|
const values = line.split(',')
|
|
return {
|
|
value: values[0],
|
|
label: values[1],
|
|
}
|
|
})
|
|
|
|
expect(headers).toEqual(['value', 'label'])
|
|
expect(data).toHaveLength(3)
|
|
expect(data[0].value).toBe('选项1')
|
|
})
|
|
|
|
test('应该支持JSON格式导入', () => {
|
|
const jsonContent = JSON.stringify([
|
|
{ value: '选项1', label: '选项1标签' },
|
|
{ value: '选项2', label: '选项2标签' },
|
|
])
|
|
|
|
const data = JSON.parse(jsonContent)
|
|
|
|
expect(data).toHaveLength(2)
|
|
expect(data[0].value).toBe('选项1')
|
|
})
|
|
|
|
test('应该支持导出选项', () => {
|
|
const options = [
|
|
{ value: '选项1', label: '选项1标签' },
|
|
{ value: '选项2', label: '选项2标签' },
|
|
]
|
|
|
|
// CSV导出
|
|
const csvHeader = 'value,label'
|
|
const csvRows = options.map(opt => `${opt.value},${opt.label}`)
|
|
const csvContent = [csvHeader, ...csvRows].join('\n')
|
|
|
|
expect(csvContent).toContain('value,label')
|
|
expect(csvContent).toContain('选项1,选项1标签')
|
|
|
|
// JSON导出
|
|
const jsonContent = JSON.stringify(options, null, 2)
|
|
expect(jsonContent).toContain('"value": "选项1"')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('集成测试: 端到端流程', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('完整工作流', () => {
|
|
test('应该完成模板创建到素材上传的完整流程', async () => {
|
|
// 1. 创建模板
|
|
const mockTemplate = {
|
|
id: 'e2e-template',
|
|
name: '端到端测试模板',
|
|
status: 'active',
|
|
materialType: '3DMAX',
|
|
materialKind: 'IMAGE',
|
|
version: 1,
|
|
fields: [
|
|
{ key: 'categoryName', label: '分类', type: 'select', optionsStatic: '["家具"]' },
|
|
],
|
|
}
|
|
|
|
prisma.uploadTemplate.create.mockResolvedValue(mockTemplate)
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue(mockTemplate)
|
|
|
|
// 2. 验证模板
|
|
const validation = await validateTemplateId('e2e-template')
|
|
expect(validation.valid).toBe(true)
|
|
|
|
// 3. 模拟素材上传
|
|
const mockMaterial = {
|
|
id: 'FLNXI202512130001',
|
|
templateId: 'e2e-template',
|
|
categoryName: '家具',
|
|
materialType: '3DMAX',
|
|
materialKind: 'IMAGE',
|
|
}
|
|
|
|
prisma.material.create.mockResolvedValue(mockMaterial)
|
|
|
|
const createdMaterial = await prisma.material.create({
|
|
data: mockMaterial,
|
|
})
|
|
|
|
expect(createdMaterial.templateId).toBe('e2e-template')
|
|
expect(createdMaterial.categoryName).toBe('家具')
|
|
})
|
|
|
|
test('应该完成字段池同步到分类管理的完整流程', async () => {
|
|
// 1. 模拟模板字段
|
|
const templateFields = [
|
|
{
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
type: 'select',
|
|
optionsStatic: '["家具", "灯具"]',
|
|
addToCategory: true,
|
|
templateId: 'template-1',
|
|
},
|
|
]
|
|
|
|
prisma.uploadField.findMany.mockResolvedValue(templateFields)
|
|
|
|
// 2. 解析选项
|
|
const options = parseOptionsStatic(templateFields[0].optionsStatic)
|
|
expect(options).toEqual(['家具', '灯具'])
|
|
|
|
// 3. 模拟字段池记录
|
|
const fieldDefinition = {
|
|
id: 'field-def-1',
|
|
key: 'categoryName',
|
|
label: '分类',
|
|
isFilterEnabled: true,
|
|
filterOrder: 0,
|
|
}
|
|
|
|
prisma.fieldDefinition.upsert.mockResolvedValue(fieldDefinition)
|
|
|
|
// 4. 验证字段池记录
|
|
expect(fieldDefinition.isFilterEnabled).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('错误处理', () => {
|
|
test('应该处理模板不存在的情况', async () => {
|
|
prisma.uploadTemplate.findUnique.mockResolvedValue(null)
|
|
|
|
const validation = await validateTemplateId('non-existent')
|
|
expect(validation.valid).toBe(false)
|
|
expect(validation.errorCode).toBe(TemplateValidationError.TEMPLATE_NOT_FOUND)
|
|
})
|
|
|
|
test('应该处理数据库错误', async () => {
|
|
prisma.uploadTemplate.findUnique.mockRejectedValue(new Error('数据库连接失败'))
|
|
|
|
const validation = await validateTemplateId('any-template')
|
|
expect(validation.valid).toBe(false)
|
|
expect(validation.errorCode).toBe(TemplateValidationError.DATABASE_ERROR)
|
|
})
|
|
|
|
test('应该处理无效的选项格式', () => {
|
|
// 无效JSON
|
|
const invalidJson = 'not-valid-json'
|
|
const parsed = parseOptionsStatic(invalidJson)
|
|
|
|
// 应该回退到换行解析
|
|
expect(parsed).toEqual(['not-valid-json'])
|
|
})
|
|
})
|
|
})
|