/** @jest-environment node */ import { desktopProjectBody } from '@/lib/desktop-client/contract.mjs' import { emptyCreation } from '@/lib/desktop-client/creation.mjs' const hash = 'a'.repeat(64) function fixture() { return { revision: 0, status: 'draft', document: { version: 3, id: 'project', name: '顾家五屏', screens: [{ id: 'one', width: 1440, height: 2200, previewSource: null }], native: { format: 'photocraft', schemaVersion: 1, documentSource: null as string | null, files: [] as any[] } } } } test('accepts an explicit incomplete draft but never labels it completed', () => { const value = fixture(); expect(desktopProjectBody(value).document.version).toBe(3) value.status = 'completed'; expect(() => desktopProjectBody(value)).toThrow('完结项目') }) test('requires a complete safe manifest and strips unrecognized fields from native metadata', () => { const value = fixture() value.document.native.files = ['canvas.pcraft','context.json','session-backup.json'].map(path => ({ path, source: 'native:' + hash, sha256: hash, size: 100 })) value.document.native.documentSource = 'native:' + hash expect(desktopProjectBody(value).document.native.files).toHaveLength(3) value.document.native.files[0].path = '../credentials.json'; expect(() => desktopProjectBody(value)).toThrow('原生工程文件') }) test('rejects unhashed native sources, missing session backup, duplicate screens and oversized geometry', () => { const value = fixture() value.document.native.files = [{ path: 'canvas.pcraft', source: 'native:' + hash, sha256: hash, size: 100 }] value.document.native.documentSource = 'native:' + hash expect(() => desktopProjectBody(value)).toThrow('会话恢复文件') value.document.native.files[0].source = 'native:uuid'; expect(() => desktopProjectBody(value)).toThrow('工程文件无效') const other = fixture(); other.document.screens[0].height = 30001; expect(() => desktopProjectBody(other)).toThrow('画布超出限制') }) function nativeCreationFixture() { const value: any = fixture() value.status = 'completed' value.document.native.files = ['canvas.pcraft', 'context.json', 'session-backup.json'].map(path => ({ path, source: 'native:' + hash, sha256: hash, size: 100 })) value.document.native.documentSource = 'native:' + hash value.document.screens[0].previewSource = 'local:first-screen' const inherited = Array.from({ length: 20 }, (_, index) => `事实待确认${index + 1}`) const own = Array.from({ length: 20 }, (_, index) => `模型待确认${index + 1}`) const creation: any = emptyCreation() creation.facts.missing = inherited creation.sellingPoints.batches = [{ id: 'batch-1', direction: '', items: ['可见桌面'], warnings: [...inherited, ...own] }] creation.plan.warnings = [...inherited, ...own] value.document.creation = creation return value } test('native cloud upload preserves all forty inherited and model warnings in selling points and plan', () => { const value = nativeCreationFixture() const original = JSON.stringify(value) const saved = desktopProjectBody(value) expect(saved.document.version).toBe(3) expect(saved.document.native.documentSource).toBe('native:' + hash) expect(saved.document.creation.sellingPoints.batches[0].warnings).toEqual(value.document.creation.sellingPoints.batches[0].warnings) expect(saved.document.creation.plan.warnings).toEqual(value.document.creation.plan.warnings) expect(saved.document.creation.sellingPoints.batches[0].warnings).toHaveLength(40) expect(saved.document.creation.plan.warnings).toHaveLength(40) expect(saved.document.creation.facts.missing).toHaveLength(20) expect(JSON.stringify(value)).toBe(original) }) test.each(['sellingPoints', 'plan'])('native cloud upload rejects forty-one %s warnings instead of truncating evidence', field => { const value = nativeCreationFixture() const warnings = field === 'sellingPoints' ? value.document.creation.sellingPoints.batches[0].warnings : value.document.creation.plan.warnings warnings.push('第41条不得静默丢弃') expect(() => desktopProjectBody(value)).toThrow(field === 'sellingPoints' ? '卖点待确认项超出限制' : '方案待确认项超出限制') expect(warnings).toHaveLength(41) })