feat(sync): 添加 Vault 所有的用户 Skill 记录
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// @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()
|
||||
})
|
||||
Reference in New Issue
Block a user