import {flushPromises,mount,type VueWrapper} from '@vue/test-utils'
import {afterEach,beforeEach,expect,it,vi} from 'vitest'
import Dialog from '@/components/app/ExecutionResourceDialog.vue'
import {apiRequest} from '@/api/client'
vi.mock('@/api/client',()=>({apiRequest:vi.fn()}))
const request=vi.mocked(apiRequest),root='/api/app/admin/execution'
let wrapper:VueWrapper
const mountDialog=(resource?:any)=>mount(Dialog,{props:{agents:[{id:'bot-1',name:'夭夭',owner:'admin'}],resource},global:{stubs:{StandaloneDialog:{template:'
'}}}})
const button=(text:string)=>wrapper.findAll('button').find(b=>b.text()===text)!
beforeEach(()=>{request.mockReset();vi.spyOn(window,'open').mockReturnValue(null);request.mockResolvedValue({})})
afterEach(()=>{wrapper?.unmount();vi.restoreAllMocks();vi.useRealTimers()})
it('defaults to browser login, opens on the click, polls completion and saves only the attempt reference',async()=>{
vi.useFakeTimers();let resolveStart!:(value:any)=>void
request.mockImplementation(async(path,options)=>{
if(path===root+'/codex-auth')return new Promise(resolve=>{resolveStart=resolve})
if(path.startsWith(root+'/codex-auth/')&&!options?.method)return {id:'attempt',status:'authorized',expiresAt:Date.now()+60000}
return {}
})
wrapper=mountDialog();expect(button('浏览器授权(推荐)').attributes('aria-pressed')).toBe('true')
expect(wrapper.find('input[type=file]').exists()).toBe(false);expect(button('保存授权').attributes('disabled')).toBeDefined()
await button('打开浏览器授权').trigger('click')
expect(window.open).toHaveBeenCalledWith('https://auth.openai.com/codex/device','_blank',expect.stringContaining('noopener'))
expect(wrapper.text()).toContain('正在获取授权码')
const id=(request.mock.calls[0]![1]!.body as any).requestId
resolveStart({id,status:'pending',loginUrl:'https://auth.openai.com/codex/device',userCode:'ABCD-12345',expiresAt:Date.now()+60000});await flushPromises()
expect(wrapper.get('a').attributes('href')).toBe('https://auth.openai.com/codex/device')
expect(wrapper.text()).toContain('ABCD-12345')
await vi.advanceTimersByTimeAsync(1500);await flushPromises()
expect(wrapper.text()).toContain('浏览器授权成功')
await wrapper.get('input[maxlength="80"]').setValue('工作账号');await wrapper.get('input[type=checkbox]').setValue(true)
await wrapper.get('form').trigger('submit');await flushPromises()
expect(request).toHaveBeenCalledWith(root+'/resources',{method:'POST',body:{name:'工作账号',kind:'codex',agentIds:['bot-1'],revision:0,authAttemptId:'attempt'}})
expect(wrapper.emitted('saved')).toHaveLength(1)
})
it('cancels a late login response when switching to file import and keeps file grants explicit',async()=>{
let finish!:(v:any)=>void
request.mockImplementation(async path=>path===root+'/codex-auth'?new Promise(resolve=>{finish=resolve}):{})
wrapper=mountDialog();await button('打开浏览器授权').trigger('click')
const id=(request.mock.calls[0]![1]!.body as any).requestId
await button('导入授权文件').trigger('click')
finish({id,status:'authorized',expiresAt:Date.now()+60000});await flushPromises()
expect(request).toHaveBeenCalledWith(root+'/codex-auth/'+id,{method:'DELETE'})
expect(wrapper.text()).not.toContain('浏览器授权成功')
const input=wrapper.get('input[type=file]'),content=JSON.stringify({tokens:{access_token:'fixture-secret'}})
Object.defineProperty(input.element,'files',{value:[{name:'auth.json',size:content.length,text:async()=>content}]})
await input.trigger('change');await flushPromises();expect(wrapper.text()).toContain('已读取 auth.json')
expect((wrapper.get('input[type=checkbox]').element as HTMLInputElement).checked).toBe(false)
await wrapper.get('input[maxlength="80"]').setValue('导入账号');await wrapper.get('form').trigger('submit');await flushPromises()
expect(request).toHaveBeenCalledWith(root+'/resources',{method:'POST',body:{name:'导入账号',kind:'codex',agentIds:[],revision:0,authJson:content}})
})
it('validates pasted credentials without echoing secret content in the error',async()=>{
wrapper=mountDialog();await button('导入授权文件').trigger('click')
await wrapper.get('details textarea').setValue('invalid fixture-secret');await wrapper.get('form').trigger('submit');await flushPromises()
expect(wrapper.get('[role=alert]').text()).toBe('请选择有效的 Codex auth.json 授权文件')
expect(request).not.toHaveBeenCalled()
})
it('retains an existing login when only changing its name or grants',async()=>{
wrapper=mountDialog({id:'resource',kind:'codex',name:'旧账号',agentIds:['bot-1'],revision:2})
await wrapper.get('input[maxlength="80"]').setValue('新名称');await wrapper.get('form').trigger('submit');await flushPromises()
expect(request).toHaveBeenCalledWith(root+'/resources/resource',{method:'PUT',body:{name:'新名称',kind:'codex',agentIds:['bot-1'],revision:2}})
})
it('shows failed and expired login states and permits a fresh attempt',async()=>{
request.mockResolvedValue({id:'attempt',status:'expired',expiresAt:0})
wrapper=mountDialog();await button('打开浏览器授权').trigger('click');await flushPromises()
expect(wrapper.text()).toContain('授权已过期');expect(button('保存授权').attributes('disabled')).toBeDefined()
request.mockResolvedValue({id:'next',status:'failed',expiresAt:0,error:'服务器未找到 Codex CLI'})
await button('打开浏览器授权').trigger('click');await flushPromises();expect(wrapper.text()).toContain('未找到 Codex CLI')
})