|
|
@@ -12,7 +12,7 @@ test('a default installation creates and saves encrypted passwords through the s
|
|
|
await page.getByRole('menuitem',{name:'我的设置',exact:true}).click()
|
|
|
await page.getByRole('button',{name:'密码管理',exact:true}).click()
|
|
|
const panel=page.locator('.credential-vault')
|
|
|
- await expect(panel.getByText(/需要你授权具体任务/)).toBeVisible()
|
|
|
+ await expect(panel.getByText(/授权一次即可使用/)).toBeVisible()
|
|
|
await panel.getByLabel('主密码',{exact:true}).fill(master)
|
|
|
await panel.getByLabel('确认主密码',{exact:true}).fill(master)
|
|
|
await panel.getByRole('button',{name:'创建密码库',exact:true}).click()
|
|
|
@@ -27,7 +27,7 @@ test('a default installation creates and saves encrypted passwords through the s
|
|
|
await panel.getByRole('button',{name:'加密保存',exact:true}).click()
|
|
|
await expect(panel.locator('li strong').filter({hasText:'浏览器测试网站'})).toBeVisible()
|
|
|
await expect(panel.getByRole('status').filter({hasText:'凭据已加密保存'})).toBeVisible()
|
|
|
- await expect(panel.getByRole('button',{name:'授予本任务 60 秒引用权限'})).toBeDisabled()
|
|
|
+ await expect(panel.getByRole('button',{name:'授权此 Bot 使用'})).toBeDisabled()
|
|
|
expect(await panel.textContent()).not.toContain(secret)
|
|
|
await panel.getByRole('button',{name:'添加凭据',exact:true}).click()
|
|
|
await panel.getByRole('combobox',{name:/^用途/}).selectOption('ssh')
|
|
|
@@ -48,7 +48,7 @@ test('a default installation creates and saves encrypted passwords through the s
|
|
|
await panel.getByRole('button',{name:'加密保存',exact:true}).click()
|
|
|
const sshEntry=panel.locator('li').filter({hasText:'浏览器测试 SSH 密码'})
|
|
|
await expect(sshEntry).toContainText('密码登录')
|
|
|
- await expect(panel.getByRole('heading',{name:'本次任务授权',exact:true})).toBeVisible()
|
|
|
+ await expect(panel.getByRole('heading',{name:'授权 Bot 使用',exact:true})).toBeVisible()
|
|
|
expect(await panel.textContent()).not.toContain('DUMMY-browser-ssh-password')
|
|
|
await sshEntry.getByRole('button',{name:'编辑',exact:true}).click()
|
|
|
await expect(sshPassword).toHaveValue('')
|
|
|
@@ -64,7 +64,7 @@ test('a default installation creates and saves encrypted passwords through the s
|
|
|
expect(await panel.evaluate(el=>el.scrollWidth<=el.clientWidth)).toBe(true)
|
|
|
await page.screenshot({path:evidence+'/mobile-saved.png'})
|
|
|
await page.setViewportSize({width:1440,height:1000})
|
|
|
- await panel.getByRole('button',{name:'立即锁定并撤销授权',exact:true}).click()
|
|
|
+ await panel.getByRole('button',{name:'锁定管理界面',exact:true}).click()
|
|
|
await expect(panel.getByText('浏览器测试网站',{exact:true})).toHaveCount(0)
|
|
|
await panel.getByLabel('主密码',{exact:true}).fill('DUMMY-wrong-master-password')
|
|
|
await panel.getByRole('button',{name:'手动解锁 5 分钟',exact:true}).click()
|
|
|
@@ -73,3 +73,59 @@ test('a default installation creates and saves encrypted passwords through the s
|
|
|
await expect(panel.getByLabel('主密码',{exact:true})).toHaveValue('')
|
|
|
await page.screenshot({path:evidence+'/wrong-master-stays-signed-in.png'})
|
|
|
})
|
|
|
+
|
|
|
+test('UI approval survives port edits, closing settings and a full server process restart with real dummy SSH execution',async({page})=>{
|
|
|
+ const {mkdtempSync,rmSync}=await import('node:fs'),{tmpdir}=await import('node:os'),{join}=await import('node:path'),{spawn}=await import('node:child_process')
|
|
|
+ const {sshFixture}=await import('../fixtures/credentialProtocols')
|
|
|
+ const first=await sshFixture('password'),second=await sshFixture('password')
|
|
|
+ const home=mkdtempSync(join(tmpdir(),'yaoyao-vault-browser-restart-')),base='http://127.0.0.1:18837',master='DUMMY-browser-persistent-master'
|
|
|
+ let child:ReturnType<typeof spawn>|undefined,logs=''
|
|
|
+ const start=async()=>{
|
|
|
+ child=spawn(process.execPath,['--import','tsx','tests/fixtures/workspace-server.ts'],{env:{...process.env,WORKSPACE_FIXTURE_HOME:home,WORKSPACE_FIXTURE_PORT:'18837',WORKSPACE_FIXTURE_UPSTREAM_PORT:'19137',WORKSPACE_FIXTURE_VAULT:'1'},stdio:['ignore','pipe','pipe']})
|
|
|
+ child.stdout!.on('data',c=>logs+=c);child.stderr!.on('data',c=>logs+=c)
|
|
|
+ await expect.poll(async()=>{try{return (await fetch(base+'/healthz')).status}catch{return 0}},{timeout:20000,message:logs}).toBe(200)
|
|
|
+ }
|
|
|
+ const stop=async()=>{if(!child||child.exitCode!==null)return;await page.goto('about:blank').catch(()=>{});const current=child,exited=new Promise<void>(r=>current.once('exit',()=>r()));current.kill('SIGTERM');const timeout=setTimeout(()=>current.kill('SIGKILL'),3000);await exited;clearTimeout(timeout);child=undefined}
|
|
|
+ const login=async()=>{
|
|
|
+ await page.goto(base+'/conversations')
|
|
|
+ if(await page.getByRole('textbox',{name:'账号',exact:true}).isVisible()){
|
|
|
+ await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture');await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass');await page.getByRole('button',{name:'登录',exact:true}).click()
|
|
|
+ }
|
|
|
+ await expect(page.locator('.desktop-sidebar .sidebar-account-switcher__main')).toBeVisible()
|
|
|
+ }
|
|
|
+ const settings=async()=>{await page.locator('.desktop-sidebar .sidebar-account-switcher__main').click();await page.getByRole('menuitem',{name:'我的设置',exact:true}).click();await page.getByRole('button',{name:'密码管理',exact:true}).click()}
|
|
|
+ const headers=async()=>({Origin:base,'X-CSRF-Token':(await(await page.request.get(base+'/api/realtime/capabilities')).json()).csrfToken})
|
|
|
+ try{
|
|
|
+ await start();await login()
|
|
|
+ const agentResponse=await page.request.post(base+'/api/app/agents',{headers:await headers(),data:{name:'竹儿测试',profile:'default'}});expect(agentResponse.ok()).toBe(true)
|
|
|
+ const agentId=(await agentResponse.json()).agent.id
|
|
|
+ await settings();const panel=page.locator('.credential-vault')
|
|
|
+ await panel.getByLabel('主密码',{exact:true}).fill(master);await panel.getByLabel('确认主密码',{exact:true}).fill(master);await panel.getByRole('button',{name:'创建密码库',exact:true}).click()
|
|
|
+ await panel.getByLabel('主密码',{exact:true}).fill(master);await panel.getByRole('button',{name:'手动解锁 5 分钟',exact:true}).click();await panel.getByRole('button',{name:'添加凭据',exact:true}).click()
|
|
|
+ await panel.getByRole('combobox',{name:/^用途/}).selectOption('ssh')
|
|
|
+ await panel.getByLabel('名称',{exact:true}).fill('TrueNAS 流程测试');await panel.getByLabel('用户名',{exact:true}).fill(first.username);await panel.getByLabel('SSH 主机',{exact:true}).fill('127.0.0.1');await panel.getByLabel(/^SSH 登录密码/).fill(first.secret)
|
|
|
+ await panel.getByText('高级连接设置(可选)',{exact:true}).click();await panel.getByLabel('端口',{exact:true}).fill(String(first.target.port))
|
|
|
+ await panel.getByLabel('配置允许 Bot 执行的固定操作',{exact:true}).check();await panel.getByRole('combobox',{name:/^授权给 Bot/}).selectOption(agentId)
|
|
|
+ await panel.getByRole('button',{name:'保存并授权 Bot',exact:true}).click();await expect(panel.getByText('TrueNAS 流程测试 → 竹儿测试 · 已持续授权')).toBeVisible()
|
|
|
+ expect(first.state.authMethods).toEqual([])
|
|
|
+ const state=await(await page.request.get(base+'/api/app/vault')).json(),credentialRef=state.entries[0].id
|
|
|
+ const use=async(discoverOnly=false)=>{const response=await page.request.post(base+'/__test/vault-use',{headers:await headers(),data:{agentId,credentialRef,discoverOnly}});expect(response.ok(),await response.text()).toBe(true);return response.json()}
|
|
|
+ await panel.getByRole('button',{name:'锁定管理界面',exact:true}).click()
|
|
|
+ expect((await use()).result).toEqual({status:'complete',operation:'ssh.exec',exitCode:0});expect(first.state.commands).toEqual(['/usr/bin/uptime'])
|
|
|
+ await panel.getByLabel('主密码',{exact:true}).fill(master);await panel.getByRole('button',{name:'手动解锁 5 分钟',exact:true}).click()
|
|
|
+ await panel.locator('li').filter({has:page.locator('strong', {hasText:'TrueNAS 流程测试'})}).getByRole('button',{name:'编辑',exact:true}).click()
|
|
|
+ await expect(panel.getByText('已持续授权:竹儿测试。修改后继续可用,无需重新授权。')).toBeVisible()
|
|
|
+ await panel.getByText('高级连接设置(可选)',{exact:true}).click();await panel.getByLabel('端口',{exact:true}).fill(String(second.target.port));await panel.getByRole('button',{name:'加密保存',exact:true}).click()
|
|
|
+ await page.getByRole('button',{name:'关闭我的设置',exact:true}).filter({visible:true}).first().click();await expect(panel).toHaveCount(0);await expect.poll(async()=>(await(await page.request.get(base+'/api/app/vault')).json()).unlocked).toBe(false)
|
|
|
+ const edited=await use();expect(edited.refs.references[0]).toMatchObject({authorized:true,allowedTarget:{port:second.target.port}});expect(edited.result.exitCode).toBe(0)
|
|
|
+ expect(first.state.commands).toHaveLength(1);expect(second.state.commands).toHaveLength(1)
|
|
|
+ await stop();await start();await login()
|
|
|
+ const restarted=await(await page.request.get(base+'/api/app/vault')).json();expect(restarted).toMatchObject({unlocked:false,botGrants:[{credentialRef,agentId}]})
|
|
|
+ expect((await use()).result).toEqual({status:'complete',operation:'ssh.exec',exitCode:0});expect(second.state.commands).toHaveLength(2)
|
|
|
+ await settings();await expect(panel.getByText('TrueNAS 流程测试 → 竹儿测试 · 已持续授权')).toBeVisible()
|
|
|
+ await page.setViewportSize({width:375,height:812});expect(await panel.evaluate(el=>el.scrollWidth<=el.clientWidth)).toBe(true)
|
|
|
+ mkdirSync('test-results/credential-vault-persistent',{recursive:true});await page.screenshot({path:'test-results/credential-vault-persistent/mobile-after-restart.png'})
|
|
|
+ await panel.getByLabel('主密码',{exact:true}).fill(master);await panel.getByRole('button',{name:'手动解锁 5 分钟',exact:true}).click();await panel.getByRole('button',{name:'撤销 Bot 授权',exact:true}).click();await panel.getByRole('button',{name:'锁定管理界面',exact:true}).click()
|
|
|
+ expect(await use(true)).toMatchObject({references:[],reason:'bot_not_authorized'});expect(second.state.commands).toHaveLength(2)
|
|
|
+ }finally{await stop();await first.close();await second.close();rmSync(home,{recursive:true,force:true})}
|
|
|
+})
|