localVmRoutes.test.ts 4.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. // @vitest-environment node
  2. import {it,expect,vi} from 'vitest'
  3. import Koa from 'koa'
  4. import {bodyParser} from '@koa/bodyparser'
  5. import request from 'supertest'
  6. import {mkdtempSync,rmSync} from 'node:fs'
  7. import {tmpdir} from 'node:os'
  8. import {join} from 'node:path'
  9. import {randomUUID} from 'node:crypto'
  10. import {WorkspaceStore} from '../../src/server/workspaceStore'
  11. import {LocalVmService} from '../../src/server/localVm'
  12. import {SharedComputers} from '../../src/server/sharedComputers'
  13. import {HttpError} from '../../src/server/errors'
  14. it('protects preparation, removes the old catalog API and scopes sharing to the account and profile',async()=>{
  15. const home=mkdtempSync(join(tmpdir(),'local-vm-routes-')),store=new WorkspaceStore(home)
  16. let version=1,active=true
  17. const auth:any={require:(ctx:any)=>({id:ctx.get('x-owner')||'alice'}),requireAdmin:(ctx:any)=>{if(ctx.get('x-admin')!=='yes'||!active)throw new HttpError(403,'需要管理员权限','admin_required');return {id:'alice'}},isAdminActive:()=>active,pushAuthorizationVersion:()=>version}
  18. const runner={id:randomUUID(),enabled:true,sourceNodeId:'local',sourceOwner:'_system'}
  19. const nodes:any={requireSource:vi.fn()}
  20. const hub:any={records:()=>[runner],summary:()=>({online:true}),localVm:vi.fn(async()=>({configured:true,daemonUp:true,image:true,mode:'per-bot',maxInstances:2,busy:false})),computer:vi.fn(async()=>({container:'running',ready:true}))}
  21. const shared=new SharedComputers(store,auth,nodes,hub),service=new LocalVmService(store,auth,nodes,hub,shared,{home} as any),app=new Koa()
  22. app.use(async(ctx,next)=>{try{await next()}catch(e){ctx.status=(e as any).status||500;ctx.body={error:String(e)}}});app.use(bodyParser());app.use(service.router().routes())
  23. try{
  24. const first=store.createAgent('alice',{name:'甲',profile:'default',execution:'computer'}),second=store.createAgent('alice',{name:'乙',profile:'default',execution:'computer'})
  25. const other=store.createAgent('bob',{name:'其他账号',profile:'default',execution:'computer'})
  26. const id=randomUUID(),base='/api/app/admin/local-vm'
  27. await request(app.callback()).post(base+'/prepare').send({requestId:id}).expect(403)
  28. await request(app.callback()).post(base+'/prepare').set('x-admin','yes').send({requestId:id,imageId:'arbitrary'}).expect(400)
  29. await request(app.callback()).post(base+'/prepare').set('x-admin','yes').send({requestId:id}).expect(200)
  30. expect(service.allowed(id,runner.id)).toBe(true);expect(service.allowed(id,randomUUID())).toBe(false)
  31. version++;expect(service.allowed(id,runner.id)).toBe(false);version--
  32. await request(app.callback()).get(`/api/app/admin/runners/${runner.id}/images`).set('x-admin','yes').expect(404)
  33. await request(app.callback()).get(`/api/app/agents/${first.id}/local-vm`).set('x-owner','bob').expect(404)
  34. await request(app.callback()).put(base+'/policy').set('x-admin','yes').send({requestId:randomUUID(),mode:'shared',maxInstances:2}).expect(200)
  35. const a=store.require<any>('alice','agent',first.id),b=store.require<any>('alice','agent',second.id)
  36. expect(a.computerEnvironmentId).toBeTruthy();expect(b.computerEnvironmentId).toBe(a.computerEnvironmentId)
  37. const later=store.createAgent('alice',{name:'后加入成员',profile:'default',execution:'computer'})
  38. expect(later.computerEnvironmentId).toBe(a.computerEnvironmentId)
  39. const disabled=await request(app.callback()).put(`/api/app/agents/${later.id}/local-vm`).send({enabled:false}).expect(200)
  40. expect(disabled.body.agent.execution).toBe('profile');expect(disabled.body.agent.computerEnvironmentId).toBeUndefined()
  41. expect(store.require<any>('alice','agent',first.id).computerEnvironmentId).toBe(a.computerEnvironmentId)
  42. expect(store.require<any>('bob','agent',other.id).computerEnvironmentId).toBeUndefined()
  43. await request(app.callback()).put(base+'/policy').set('x-admin','yes').send({requestId:randomUUID(),mode:'per-bot',maxInstances:2}).expect(200)
  44. expect(store.require<any>('alice','agent',first.id).computerEnvironmentId).toBeUndefined()
  45. const helper=store.createAgent('alice',{name:'临时任务助手',profile:'default',execution:'computer'},{createdByAgentId:first.id,createdFromRunId:randomUUID(),temporaryGoalId:randomUUID()})
  46. await request(app.callback()).post(`/api/app/agents/${helper.id}/local-vm/create`).send({}).expect(409)
  47. active=false;expect(service.allowed(id,runner.id)).toBe(false)
  48. }finally{store.close();rmSync(home,{recursive:true,force:true})}
  49. })