computerControlRoutes.test.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839
  1. // @vitest-environment node
  2. import {expect,it,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 {WorkspaceNodes} from '../../src/server/workspaceGateway'
  12. import {loadServerConfig} from '../../src/server/config'
  13. import {ComputerControlService} from '../../src/server/computerControls'
  14. import {HttpError} from '../../src/server/errors'
  15. import type {LocalAuthStore} from '../../src/server/localAuth'
  16. import type {RunnerHub} from '../../src/server/runnerHub'
  17. it('returns a private control credential once, rejects other callers and invalidates revoked grants',async()=>{
  18. const home=mkdtempSync(join(tmpdir(),'yaoyao-control-routes-')),store=new WorkspaceStore(home),runnerId=randomUUID()
  19. let authVersion=1
  20. const auth={require:(ctx:any)=>({id:ctx.get('x-fixture-owner')||'owner'}),pushAuthorizationVersion:()=>authVersion} as unknown as LocalAuthStore
  21. const nodes=new WorkspaceNodes(store,loadServerConfig({HERMES_YAOYAO_HOME:home}),{} as any)
  22. const calls=vi.fn(async(_owner,_agent,op,_data,authorize)=>{authorize();return {mode:op==='take'?'pausing':'agent'}})
  23. const hub={computerRunner:()=>({id:runnerId}),computer:calls} as unknown as RunnerHub
  24. const service=new ComputerControlService(store,auth,nodes,hub),app=new Koa()
  25. app.use(async(ctx,next)=>{try{await next()}catch(error){ctx.status=error instanceof HttpError?error.status:500;ctx.body={code:(error as any).code}}});app.use(bodyParser());app.use(service.router().routes())
  26. try{
  27. const agent=store.createAgent('owner',{name:'电脑',profile:'default',execution:'computer'}),base=`/api/app/agents/${agent.id}/computer`
  28. const taken=await request(app.callback()).post(base+'/take').send({requestId:randomUUID()}).expect(200)
  29. expect(taken.body.token.length).toBeGreaterThan(32)
  30. expect(JSON.stringify((await request(app.callback()).get(base).expect(200)).body)).not.toContain(taken.body.token)
  31. expect(service.allowed(taken.body.controlId,runnerId)).toBe(true)
  32. await request(app.callback()).post(base+'/renew').send({controlId:taken.body.controlId,token:'x'.repeat(40)}).expect(403)
  33. await request(app.callback()).post(base+'/renew').set('x-fixture-owner','other').send({controlId:taken.body.controlId,token:taken.body.token}).expect(403)
  34. await request(app.callback()).post(base+'/take').send({requestId:randomUUID()}).expect(409)
  35. authVersion++
  36. expect(service.allowed(taken.body.controlId,runnerId)).toBe(false)
  37. await request(app.callback()).post(base+'/renew').send({controlId:taken.body.controlId,token:taken.body.token}).expect(410)
  38. }finally{nodes.close();store.close();rmSync(home,{recursive:true,force:true})}
  39. })