agentIdentity.test.ts 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344
  1. import {describe,expect,it} from 'vitest'
  2. import {AGENT_MASCOT_SHAPES,AGENT_MASCOT_BODIES,AGENT_MASCOT_COLORS,AGENT_MASCOT_EXPRESSIONS,AGENT_IMAGE_CROPS,agentIdentityFromProfile,agentIdentityMetadata,defaultAgentIdentity,randomAgentIdentity,decodeAgentAvatar,encodeAgentAvatar,normalizeAvatar,YAOYAO_AGENT_IDENTITY_NAMESPACE} from '../../src/shared/agentIdentity'
  3. const photo='data:image/png;base64,aGVsbG8='
  4. describe('avatar v2',()=>{
  5. it('resets legacy internal appearance while retaining the configured name',()=>{
  6. const identity=agentIdentityFromProfile({name:'bot',ui_meta:{[YAOYAO_AGENT_IDENTITY_NAMESPACE]:{version:1,display_name:'瑶儿',avatar_mode:'mascot',shape:'triangle',color:'#ff0000',expression:'curious'}}})
  7. expect(identity).toMatchObject({displayName:'瑶儿',shape:'circle',color:'#00c875',expression:'idle',bodyId:null})
  8. expect(decodeAgentAvatar('yaoyao-mascot:v1:triangle:ff0000:curious')).toMatchObject({shape:'circle',color:'#00c875',expression:'idle'})
  9. })
  10. it('retains raw photo bytes and uses the prior rounded crop',()=>{
  11. const decoded=decodeAgentAvatar(normalizeAvatar(photo))
  12. expect(decoded).toMatchObject({avatarMode:'image',imageDataURL:photo,imageCrop:'rounded'})
  13. expect(agentIdentityFromProfile({name:'bot',ui_meta:{[YAOYAO_AGENT_IDENTITY_NAMESPACE]:{version:1,avatar_mode:'image',image_data_url:photo}}})).toMatchObject({avatarMode:'image',imageDataURL:photo})
  14. })
  15. it('retains hidden photos when resetting the old mascot',()=>{
  16. expect(agentIdentityFromProfile({name:'bot',ui_meta:{[YAOYAO_AGENT_IDENTITY_NAMESPACE]:{version:1,avatar_mode:'mascot',image_data_url:photo}}})).toMatchObject({avatarMode:'mascot',imageDataURL:photo,color:'#00c875'})
  17. })
  18. it('round trips every supported picker value and metadata without resetting v2',()=>{
  19. expect([AGENT_MASCOT_COLORS.length,AGENT_MASCOT_SHAPES.length,AGENT_MASCOT_BODIES.length,AGENT_MASCOT_EXPRESSIONS.length]).toEqual([11,8,10,10])
  20. for(const shape of AGENT_MASCOT_SHAPES) for(const bodyId of [null,...AGENT_MASCOT_BODIES]) {
  21. const identity={...defaultAgentIdentity('bot'),shape,bodyId,color:'#1488ff' as const,expression:'proud' as const}
  22. const encoded=encodeAgentAvatar(identity)
  23. expect(normalizeAvatar(encoded)).toBe(encoded)
  24. expect(decodeAgentAvatar(encoded)).toMatchObject({shape,bodyId,color:'#1488ff',expression:'proud'})
  25. expect(agentIdentityFromProfile({name:'bot',ui_meta:{[YAOYAO_AGENT_IDENTITY_NAMESPACE]:agentIdentityMetadata(identity)}})).toEqual(identity)
  26. }
  27. for(const imageCrop of AGENT_IMAGE_CROPS) expect(decodeAgentAvatar(encodeAgentAvatar({...defaultAgentIdentity('bot'),avatarMode:'image',imageDataURL:photo,imageCrop}))).toMatchObject({imageDataURL:photo,imageCrop})
  28. })
  29. it('uses the same green circle for all defaults, regardless of name or role',()=>{
  30. for(const name of ['samien','developer','研究员']) expect(defaultAgentIdentity(name)).toMatchObject({shape:'circle',color:'#00c875',expression:'idle'})
  31. })
  32. it('creates a complete random v2 identity without falling back to the fixed green circle',()=>{
  33. const values=[0,0,.99];let index=0
  34. expect(randomAgentIdentity('new-agent','新 Agent',()=>values[index++]!)).toMatchObject({shape:'circle',bodyId:null,color:'#000000',expression:'proud'})
  35. index=0;values[0]=.99;values[1]=.99;values[2]=0
  36. expect(randomAgentIdentity('new-agent','新 Agent',()=>values[index++]!)).toMatchObject({bodyId:'star',color:'#808080',expression:'idle'})
  37. })
  38. it('rejects untrusted geometry, invalid crops and external image URLs',()=>{
  39. const base={...defaultAgentIdentity('bot')}
  40. for(const patch of [{shape:'<script>'},{bodyId:'untrusted'},{color:'url(https://x)'},{imageCrop:'blob'},{avatarMode:'image',imageDataURL:'https://external.test/avatar.png'}]) {
  41. expect(decodeAgentAvatar('yaoyao-avatar:v2:'+JSON.stringify({...base,...patch}))).toBeNull()
  42. }
  43. })
  44. })