import type { AssetPublicationEnvelope, CatalogEntry } from '@forgeax/engine-types'; import { describe, expect, it } from 'vitest'; import { type CompactCatalogWire, decodeCatalogWire, encodeCatalogWire } from '../catalog-wire.js'; import { createRuntimePackPublication } from '../runtime-publication.js'; function sourceRows(sourcePath: string, count: number): CatalogEntry[] { const { publication } = createRuntimePackPublication({ pack: { assets: Array.from({ length: count }, (_, index) => ({ guid: `${sourcePath}-${index}`, kind: 'mesh' as const, payload: { marker: index }, refs: [], })), }, scopeId: 'gta-wire-fixture', sourcePath, sourceRevision: 'source-revision', packageUrl: `/assets/${sourcePath}.pack.json`, }); return publication.outputs.map((output, index) => ({ guid: output.guid, packageUrl: `/assets/${sourcePath}.pack.json`, kind: output.kind, sourcePath, sourceKey: output.sourceKey, sourceIndex: index, refs: output.refs, relations: [], subject: 'imported-output', execution: 'cooked', lifecycle: 'current', publication, })); } function required(value: T | undefined): T { if (value === undefined) throw new Error('missing required fixture value'); return value; } const rows = sourceRows('assets/market/market.pack.ts', 2); const envelope = required(required(rows[0]).publication); const wireRoundTrip = (entries: readonly CatalogEntry[]) => decodeCatalogWire(JSON.parse(JSON.stringify(encodeCatalogWire(entries)))).unwrap(); function compact(entries: readonly CatalogEntry[]): CompactCatalogWire { const encoded = encodeCatalogWire(entries); expect(Array.isArray(encoded)).toBe(false); return encoded as CompactCatalogWire; } function expectFailure( result: ReturnType, field?: string, index?: number, ) { expect(result.ok).toBe(false); if (result.ok) throw new Error('expected catalog admission failure'); expect(result.error.code).toBe('asset-parse-failed'); expect(result.error.expected.length).toBeGreaterThan(0); expect(result.error.hint.length).toBeGreaterThan(0); if (field !== undefined) expect(result.error.detail).toMatchObject({ field }); if (index !== undefined) expect(result.error.detail).toMatchObject({ value: { index } }); } describe('complete publication catalog transport', () => { it('round trips all fields through real JSON and preserves rows without a publication', () => { const legacyRow: CatalogEntry = { guid: 'legacy', kind: 'mesh', sourcePath: 'legacy.pack.json', packageUrl: '/legacy.pack.json', name: 'legacy mesh', }; const input = [...rows, legacyRow]; const before = JSON.stringify(input); const encoded = compact(input); expect(encoded.publications).toHaveLength(1); expect(encoded.entries[0]).not.toHaveProperty('publication'); expect(encoded.entries[0]).toHaveProperty('publicationIndex', 0); expect(wireRoundTrip(input)).toEqual(input); expect(JSON.stringify(input)).toBe(before); expect(decodeCatalogWire(input).unwrap()).toBe(input); }); it('retains every sibling and complete receipt at the actual GTA 219/188 output sizes', () => { const input = [ ...sourceRows('assets/characters/characters.pack.ts', 219), ...sourceRows('assets/buildings/buildings.pack.ts', 188), ]; const encoded = compact(input); expect(encoded.publications.map((item) => item.outputs.length)).toEqual([219, 188]); expect(encoded.entries).toHaveLength(407); expect(JSON.stringify(encoded).length).toBeLessThan(JSON.stringify(input).length / 20); const decoded = wireRoundTrip(input); expect(decoded).toEqual(input); expect(required(decoded[0]).publication).toBe(required(decoded[218]).publication); expect(required(decoded[219]).publication).toBe(required(decoded[406]).publication); expect(required(decoded[0]).publication).not.toBe(required(decoded[219]).publication); }); it.each([ ['digest', { ...envelope, digest: 'different-digest' }], ['sourcePath', { ...envelope, sourcePath: 'different-source.pack.ts' }], [ 'outputs', { ...envelope, outputs: [{ ...required(envelope.outputs[0]), digest: 'different-output' }] }, ], [ 'receipt', { ...envelope, receipt: { ...envelope.receipt, inputFingerprint: 'different-receipt' } }, ], ] as const)('does not conflate a distinct %s within the same source publication', (_, other) => { const input = [ ...rows, { ...required(rows[1]), guid: 'different-output-row', publication: other as AssetPublicationEnvelope, }, ]; expect(compact(input).publications).toHaveLength(2); expect(wireRoundTrip(input)).toEqual(input); }); it('deduplicates independently parsed equal envelopes rather than only object aliases', () => { const input = JSON.parse(JSON.stringify(rows)) as CatalogEntry[]; expect(required(input[0]).publication).not.toBe(required(input[1]).publication); expect(compact(input).publications).toHaveLength(1); expect(wireRoundTrip(input)).toEqual(input); }); it.each([ undefined, null, '0', -1, 0.5, 1, Number.MAX_SAFE_INTEGER + 1, ])('rejects invalid publicationIndex %s', (publicationIndex) => { const encoded = compact(rows); expectFailure( decodeCatalogWire({ ...encoded, entries: [{ ...encoded.entries[0], publicationIndex }], }), 'entries[0].publicationIndex', 0, ); }); it.each([ { schemaVersion: 'other', entries: [], publications: [] }, { schemaVersion: 'pack-index-publications/1', entries: {}, publications: [] }, { schemaVersion: 'pack-index-publications/1', entries: [], publications: {} }, { schemaVersion: 'pack-index-publications/1', entries: [], publications: [null] }, { schemaVersion: 'pack-index-publications/1', entries: [], publications: [{}] }, { schemaVersion: 'pack-index-publications/1', entries: [null], publications: [] }, { schemaVersion: 'pack-index-publications/1', entries: [], publications: [], extra: true }, ])('rejects an unsupported or malformed compact document', (input) => { expectFailure(decodeCatalogWire(input)); }); it('rejects orphan tables, inline publication conflicts and truncated envelopes', () => { const encoded = compact(rows); expectFailure(decodeCatalogWire({ ...encoded, entries: [] }), 'publications[0]', 0); expectFailure( decodeCatalogWire({ ...encoded, entries: [{ ...encoded.entries[0], publication: envelope }], }), 'entries[0].publication', 0, ); expectFailure( decodeCatalogWire({ ...encoded, publications: [{ ...envelope, receipt: undefined }], }), 'publications[0].receipt', 0, ); expect(() => encodeCatalogWire([{ ...required(rows[0]), publicationIndex: 0 } as CatalogEntry]), ).toThrow(TypeError); }); it('keeps zero, single and unrelated publications on the legacy array wire', () => { for (const input of [[], rows.slice(0, 1), [...rows.slice(0, 1), ...sourceRows('other', 1)]]) { expect(encodeCatalogWire(input)).toBe(input); expect(wireRoundTrip(input)).toEqual(input); } }); it('returns the specific first table field and index without throwing', () => { const encoded = compact(rows); expectFailure( decodeCatalogWire({ ...encoded, publications: [ { ...envelope, outputs: [{ ...required(envelope.outputs[0]), refs: null }] }, ], }), 'publications[0].outputs[0].refs', 0, ); expectFailure( decodeCatalogWire({ ...encoded, publications: new Array(1) }), 'publications[0]', 0, ); expectFailure(decodeCatalogWire({ ...encoded, entries: new Array(1) }), 'entries[0]', 0); }); });