98 lines
4.5 KiB
TypeScript
98 lines
4.5 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { beforeEach, expect, it, vi } from 'vitest'
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import UserSkillEditor from './UserSkillEditor.vue'
|
|
import { useWorkspaceStore } from '@/stores/workspace'
|
|
import { useSkillStore } from '@/stores/skill'
|
|
import * as service from '@/services/skillService'
|
|
import type { UserSkill } from '@/contracts'
|
|
|
|
vi.mock('@/services/skillService', () => ({
|
|
listSkills: vi.fn(), listUserSkills: vi.fn(), createUserSkill: vi.fn(), updateUserSkill: vi.fn(), deleteUserSkill: vi.fn(),
|
|
}))
|
|
|
|
const saved: UserSkill = {
|
|
skill_id: 'user_skill_' + '1'.repeat(32), revision: 'a'.repeat(64), status: 'ready',
|
|
missing_dependencies: [], undeclared_permissions: [],
|
|
data: { version: 1, name: 'Review', description: '', prompt: 'Review carefully', tools: ['notes.read'], permissions: ['notes.read'], retrieval: { top_k: 10, rerank: true, citation: true }, required_capabilities: ['chat'], created_at_ms: 1, updated_at_ms: 1 },
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
setActivePinia(createPinia())
|
|
useWorkspaceStore().vaultId = 'vault-one'
|
|
vi.mocked(service.listSkills).mockResolvedValue([])
|
|
vi.mocked(service.listUserSkills).mockResolvedValue([])
|
|
vi.mocked(service.createUserSkill).mockResolvedValue(saved)
|
|
vi.mocked(service.updateUserSkill).mockResolvedValue({ ...saved, revision: 'b'.repeat(64), data: { ...saved.data, version: 2 } })
|
|
vi.mocked(service.deleteUserSkill).mockResolvedValue({ status: 'completed' })
|
|
})
|
|
|
|
it('creates a complete declarative record and labels declarations as non-grants', async () => {
|
|
const wrapper = mount(UserSkillEditor)
|
|
await wrapper.findAll('input.input')[0]!.setValue('Review')
|
|
await wrapper.findAll('input.input')[1]!.setValue('notes.read')
|
|
await wrapper.get('textarea').setValue('Review carefully')
|
|
await wrapper.get('input[value="notes.read"]').setValue(true)
|
|
await wrapper.get('input[value="chat"]').setValue(true)
|
|
await wrapper.get('form').trigger('submit')
|
|
await flushPromises()
|
|
expect(service.createUserSkill).toHaveBeenCalledWith(expect.objectContaining({
|
|
revision: '', name: 'Review', prompt: 'Review carefully', tools: ['notes.read'],
|
|
permissions: ['notes.read'], required_capabilities: ['chat'],
|
|
retrieval: { top_k: 10, rerank: true, citation: true },
|
|
}), expect.stringMatching(/^[0-9a-f-]{36}$/))
|
|
expect(wrapper.text()).toContain('不是设备授权')
|
|
expect(useSkillStore().userSkills).toHaveLength(1)
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('does not publish a late save response into a different Vault', async () => {
|
|
let finish!: (value: UserSkill) => void
|
|
vi.mocked(service.createUserSkill).mockImplementation(() => new Promise(resolve => { finish = resolve }))
|
|
const wrapper = mount(UserSkillEditor)
|
|
await wrapper.findAll('input.input')[0]!.setValue('Review')
|
|
await wrapper.get('form').trigger('submit')
|
|
useWorkspaceStore().vaultId = 'vault-two'
|
|
await wrapper.vm.$nextTick()
|
|
finish(saved)
|
|
await flushPromises()
|
|
expect(useSkillStore().userSkills).toEqual([])
|
|
expect(wrapper.text()).toContain('WORKSPACE_CHANGED')
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('does not publish a late list response into a different Vault', async () => {
|
|
let finish!: (value: UserSkill[]) => void
|
|
vi.mocked(service.listUserSkills).mockImplementation(() => new Promise(resolve => { finish = resolve }))
|
|
const store = useSkillStore()
|
|
const loading = store.loadSkills()
|
|
useWorkspaceStore().vaultId = 'vault-two'
|
|
finish([saved])
|
|
await loading
|
|
expect(store.userSkills).toEqual([])
|
|
})
|
|
|
|
it('reuses the operation UUID after an ambiguous save failure and changes it with the payload', async () => {
|
|
vi.mocked(service.createUserSkill)
|
|
.mockRejectedValueOnce(new Error('HOST_TIMEOUT'))
|
|
.mockResolvedValueOnce(saved)
|
|
.mockRejectedValueOnce(new Error('HOST_TIMEOUT'))
|
|
const wrapper = mount(UserSkillEditor)
|
|
const name = wrapper.findAll('input.input')[0]!
|
|
await name.setValue('Review')
|
|
await wrapper.get('form').trigger('submit')
|
|
await flushPromises()
|
|
const firstOperation = vi.mocked(service.createUserSkill).mock.calls[0]![1]
|
|
await wrapper.get('form').trigger('submit')
|
|
await flushPromises()
|
|
expect(vi.mocked(service.createUserSkill).mock.calls[1]![1]).toBe(firstOperation)
|
|
await wrapper.findAll('button').find(button => button.text().includes('清空表单'))!.trigger('click')
|
|
await name.setValue('Changed')
|
|
await wrapper.get('form').trigger('submit')
|
|
await flushPromises()
|
|
expect(vi.mocked(service.createUserSkill).mock.calls[2]![1]).not.toBe(firstOperation)
|
|
wrapper.unmount()
|
|
})
|