/** * 四域十九对象 —— 声明即物化的输入。守三件事:数量与域别齐、主键齐、按名可取。 */ import { describe, expect, it } from 'vitest'; import { MVP_OBJECTS, declarationsFor } from '../seed-mvp-data.ts'; describe('MVP_OBJECTS', () => { it('四域十九对象,不多不少 —— 定价审批表载体是审批流,不入台账', () => { expect(MVP_OBJECTS).toHaveLength(19); const count = (domain: string) => MVP_OBJECTS.filter((o) => o.domain === domain).length; expect(count('project')).toBe(13); expect(count('customer')).toBe(1); expect(count('intel')).toBe(3); expect(count('supplier')).toBe(2); }); it('每个对象至少一个主键字段 —— 只增不破坏靠主键立身', () => { for (const o of MVP_OBJECTS) { expect(o.fields.some((f) => f.primaryKey), `${o.domain}.${o.object}`).toBe(true); } }); it('每个字段都有中文名 —— 界面上不许只见键不见名', () => { for (const o of MVP_OBJECTS) { expect(o.label, `${o.domain}.${o.object} 缺对象名`).toBeTruthy(); for (const f of o.fields) { expect(f.label, `${o.domain}.${o.object}.${f.name} 缺字段名`).toBeTruthy(); } } }); /* * 受理结论是**一单一行**,主键只有询价单号。 * * 这条断言守的是一个会静默出错的设计:出口的标量字段取自 `landed.flat`, * 而 `flat` 是把这一单所有落地行 reduce 展开成一个平对象。两枚去向旗若挂在 * 按料号分行的对象上,六个料号就有六行同名列、最后合并的那行赢 —— * 而归并规则是「任一条命中 C 即需选型」,网关会照着错值分叉且无人察觉。 */ it('受理结论一单一行 —— 去向旗不能挂在按料号分行的对象上', () => { const verdict = MVP_OBJECTS.find((o) => o.object === 'intake_verdict'); expect(verdict, '缺 project.intake_verdict').toBeDefined(); expect(verdict?.domain).toBe('project'); expect(verdict?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['rfq_ref']); for (const flag of ['need_alternative', 'need_sourcing']) { expect(verdict?.fields.find((f) => f.name === flag)?.type, flag).toBe('boolean'); } // 反过来也要守:按料号分行的对象上不许出现去向旗 for (const o of MVP_OBJECTS) { if (o.object === 'intake_verdict') continue; const names = o.fields.map((f) => f.name); expect(names, `${o.domain}.${o.object} 不该带去向旗`).not.toContain('need_alternative'); expect(names, `${o.domain}.${o.object} 不该带去向旗`).not.toContain('need_sourcing'); } }); it('declarationsFor 按名取子集,名不存在即抛', () => { expect(declarationsFor(['project.intake_record', 'intel.history_price'])).toHaveLength(2); expect(() => declarationsFor(['project.nope'])).toThrow('未知对象'); }); });