import assert from 'node:assert/strict' import {test} from 'node:test' import {desktopAuthorization,desktopPortal,desktopProjectBody} from '../lib/desktop-client/contract.mjs' test('desktop authorization requires S256 and an exact loopback callback',()=>{ const request={client_id:'jiansun-detail-desktop',response_type:'code',code_challenge_method:'S256',state:'a'.repeat(32),code_challenge:'b'.repeat(43),redirect_uri:'http://127.0.0.1:40001/oauth/callback'} assert.equal(desktopAuthorization(request).redirectUri,request.redirect_uri) for(const redirect_uri of ['https://example.com/oauth/callback','http://localhost:40001/oauth/callback','http://127.0.0.1:80/oauth/callback','http://127.0.0.1:40001/oauth/callback?next=elsewhere','http://127.0.0.1:40001/other']){ assert.throws(()=>desktopAuthorization({...request,redirect_uri})) } assert.throws(()=>desktopAuthorization({...request,code_challenge_method:'plain'})) assert.throws(()=>desktopAuthorization({...request,state:'short'})) }) test('desktop portal allowlist excludes unrelated services and credentials in URLs',()=>{ assert.equal(desktopPortal('https://sc.jiansun.vip'),'https://sc.jiansun.vip') assert.equal(desktopPortal('http://127.0.0.1:3138'),'http://127.0.0.1:3138') for(const value of ['https://api.vip98k.com','http://sc.jiansun.vip','https://sc.jiansun.vip/other','https://user@sc.jiansun.vip'])assert.throws(()=>desktopPortal(value)) }) test('native draft contract preserves chat and unsent input with bounded 1440px canvases',()=>{ const value={document:{version:1,id:'acceptance',name:'验收草稿',screens:[{width:1440,height:1800,layers:[]}]},revision:0,status:'draft',conversation:[{id:'one',role:'user',text:'保留品牌字体'}],composer:'尚未发送的要求'} assert.equal(desktopProjectBody(value).composer,value.composer) assert.deepEqual(desktopProjectBody(value).conversation,value.conversation) assert.throws(()=>desktopProjectBody({...value,document:{...value.document,screens:[{width:4096,height:1800,layers:[]}]}})) assert.throws(()=>desktopProjectBody({...value,revision:-1})) assert.throws(()=>desktopProjectBody({...value,composer:'x'.repeat(6001)})) }) test('native template projects preserve the complete long canvas while web draft limits stay unchanged',()=>{ const file=(path,sha)=>({path,sha256:sha.repeat(64),source:'native:'+sha.repeat(64),size:512}) const native={format:'photocraft',schemaVersion:1,documentSource:'native:'+'a'.repeat(64),files:[file('canvas.pcraft','a'),file('context.json','b'),file('session-backup.json','c')]} const heights=Array.from({length:22},(_,i)=>i===21?2830:2100) const document={version:3,id:'long-template',name:'完整详情模板',native,screens:heights.map((height,i)=>({id:'s'+i,width:1440,height,previewSource:'local:screen-'+i}))} const input={document,revision:1,status:'draft'} const accepted=desktopProjectBody(input).document assert.equal(accepted.screens.length,22) assert.equal(accepted.screens.reduce((sum,s)=>sum+s.height,0),46930) const full=Array.from({length:128},(_,i)=>({id:'s'+i,width:1440,height:i===127?2820:2340,previewSource:'local:screen-'+i})) assert.equal(desktopProjectBody({...input,document:{...document,screens:full}}).document.screens.length,128) assert.equal(full.reduce((sum,s)=>sum+s.height,0),300000) assert.throws(()=>desktopProjectBody({...input,document:{...document,screens:[...full,{id:'s128',width:1440,height:400}]}})) assert.throws(()=>desktopProjectBody({...input,document:{...document,screens:full.map((s,i)=>i===127?{...s,height:2821}:s)}})) for(const version of [1,2])assert.throws(()=>desktopProjectBody({...input,document:{...document,version}})) assert.throws(()=>desktopProjectBody({...input,document:{...document,screens:Array.from({length:101},(_,i)=>({id:'s'+i,width:1440,height:3000}))}})) assert.throws(()=>desktopProjectBody({...input,document:{...document,native:{...native,files:[...native.files,file('outside.json','d')]}}})) assert.throws(()=>desktopProjectBody({...input,document:{...document,native:{...native,files:native.files.map((f,i)=>i?f:{...f,size:512*1024*1024+1})}}})) })