import { describe, it, expect } from 'vitest'; import { buildEnvelope } from '../src/lib/envelope'; const base = { source: { uri: 'github:o/r@a.md', content_hash: 'abc' }, extractor: { model: 'local-harvest', tier: 'shallow' as const }, triplets: [{ subject: 'A', predicate: 'p', object: 'B', predicate_embed: true }], }; describe('buildEnvelope', () => { it('組合法 envelope(含向量化打標欄位)', () => { const env = buildEnvelope({ ...base, nodes: [{ name: 'A', gloss: 'a', aliases: ['a2'], embed: true, id: 'A' }], }); expect(env.source.uri).toBe('github:o/r@a.md'); expect(env.nodes?.[0].embed).toBe(true); expect(env.nodes?.[0].id).toBe('A'); expect(env.triplets[0].predicate_embed).toBe(true); }); it('node 帶禁送欄位(bridge_score)→ strict throw(本地提早攔,不等 graph 422)', () => { expect(() => buildEnvelope({ ...base, nodes: [{ name: 'A', embed: true }] })).not.toThrow(); expect(() => buildEnvelope({ ...base, nodes: [{ name: 'A', bridge_score: 0.5 } as any] }), ).toThrow(); }); it('node 帶 graph 領域 record id(非去重 id)以外的禁送鍵 → strict throw', () => { // 契約允許 nodes[].id(去重鍵);但 clusters 是 graph 領域 → strict 擋。 expect(() => buildEnvelope({ ...base, nodes: [{ name: 'A', id: 'A', embed: true }] })).not.toThrow(); expect(() => buildEnvelope({ ...base, nodes: [{ name: 'A', clusters: ['c'] } as any] })).toThrow(); }); it('禁送邊上 entity_type → strict throw', () => { expect(() => buildEnvelope({ ...base, triplets: [{ subject: 'A', predicate: 'p', object: 'B', subject_entity_type: 'person' } as any], }), ).toThrow(); }); it('無 triplets → throw(契約 min 1)', () => { expect(() => buildEnvelope({ ...base, triplets: [] })).toThrow(); }); });