Files
lingxi/apps/api/src/modules/catalog/dict.mapper.spec.ts
T
Stev_Wang e4330a43e4 feat(api): A4 数据字典与系统配置后端
背景:A4 需要字典三层合并、配置单一来源与阈值同源。\n改动:新增 catalog 模块(类型/条目/三层合并/编码不可改/快照工具);platform 新增 SystemConfigService/Controller、NamingService 与配置版本历史;roles/users/customers/customer-fields 改为读 system_configs 上限。\n影响面:新增 13 个 REST 端点与 4 个模块注入;system_configs 写入走事务/乐观锁/版本历史;OpenAPI 快照更新。\n回滚:回滚本提交并恢复 shared 契约;新增表可保留。\n验证:api typecheck/build;单测 137;集成 58;OpenAPI diff 无破坏性变更。
2026-11-04 11:49:05 +08:00

134 lines
3.9 KiB
TypeScript
Raw 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.
import { dictItemSnapshotSchema } from '@lingxi/shared';
import { describe, expect, it } from 'vitest';
import {
mapDictGroup,
mapDictKind,
mapDictStatus,
toDictItem,
toDictSnapshot,
toDictTypeSummary,
} from './dict.mapper';
describe('dict.mapper · Prisma 行 -> 共享 DTO', () => {
it('类型统计与枚举转换:group / kind / status 映射为契约小写值', () => {
const summary = toDictTypeSummary(
{
code: 'script_direction',
name: '脚本方向',
groupKey: 'content',
kind: 'LIST',
description: '脚本与素材的结构化方向',
industryScoped: true,
builtin: true,
status: 'ACTIVE',
sortOrder: 60,
updatedAt: new Date('2026-11-04T02:00:00.000Z'),
updatedBy: '江岚',
},
{ itemCount: 11, activeItemCount: 10, industryCount: 2 },
);
expect(summary).toEqual({
code: 'script_direction',
name: '脚本方向',
group: 'content',
kind: 'list',
description: '脚本与素材的结构化方向',
industryScoped: true,
builtin: true,
status: 'active',
sortOrder: 60,
itemCount: 11,
activeItemCount: 10,
industryCount: 2,
updatedAt: '2026-11-04T02:00:00.000Z',
updatedBy: '江岚',
});
});
it('库内异常分组回退 content;kind / status 只映射两个分支', () => {
expect(mapDictGroup('unknown_group')).toBe('content');
expect(mapDictGroup('quality')).toBe('quality');
expect(mapDictKind('TREE')).toBe('tree');
expect(mapDictKind('LIST')).toBe('list');
expect(mapDictStatus('ACTIVE')).toBe('active');
expect(mapDictStatus('DISABLED')).toBe('disabled');
});
it('条目映射:source / overridden / parentName / industryPath / refCount=0', () => {
const item = toDictItem(
{
id: '01HZZZZZZZZZZZZZZZZZZZZZZZ',
code: 'seeding',
name: '种草短视频',
parentCode: null,
industryId: 'ec',
sortOrder: 40,
status: 'ACTIVE',
remark: '父行业覆盖通用层',
updatedAt: new Date('2026-11-04T02:00:00.000Z'),
updatedBy: '江岚',
},
{
typeCode: 'script_direction',
source: 'parent',
overridden: true,
parentName: null,
industryPath: '电商',
},
);
expect(item).toMatchObject({
typeCode: 'script_direction',
code: 'seeding',
name: '种草短视频',
parentName: null,
industryId: 'ec',
industryPath: '电商',
status: 'active',
source: 'parent',
overridden: true,
refCount: 0,
updatedAt: '2026-11-04T02:00:00.000Z',
updatedBy: '江岚',
});
});
it('快照 R-10:先取快照再改字典名称,快照保留填写时名称', () => {
const item = toDictItem(
{
id: '01HZZZZZZZZZZZZZZZZZZZZZZZ',
code: 'oral',
name: '口播',
parentCode: null,
industryId: null,
sortOrder: 10,
status: 'ACTIVE',
remark: '',
updatedAt: new Date('2026-11-04T02:00:00.000Z'),
updatedBy: '江岚',
},
{
typeCode: 'script_direction',
source: 'generic',
overridden: false,
parentName: null,
industryPath: null,
},
);
const snapshot = toDictSnapshot(item, '2026-11-04T10:00:00.000Z');
// 模拟字典改名 / 停用(历史引用方不感知)
const renamed = { ...item, name: '口播(改名后)', status: 'disabled' as const };
expect(renamed.name).not.toBe(snapshot.name);
expect(snapshot.name).toBe('口播');
expect(snapshot).toEqual({
typeCode: 'script_direction',
code: 'oral',
name: '口播',
parentCode: null,
industryId: null,
capturedAt: '2026-11-04T10:00:00.000Z',
});
expect(dictItemSnapshotSchema.parse(snapshot)).toEqual(snapshot);
});
});