kin: auto-commit after pipeline
This commit is contained in:
parent
66dc5f2111
commit
b75269fa6c
4 changed files with 108 additions and 3 deletions
|
|
@ -235,6 +235,61 @@ describe('SettingsView — Deploy Config', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// 1b. SettingsView — Project Links
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
describe('SettingsView — Project Links', () => {
|
||||||
|
it('link.type рендерится корректно в списке связей — не undefined (конвенция #527)', async () => {
|
||||||
|
// Bug #527: шаблон использовал {{ link.link_type }} вместо {{ link.type }} → runtime undefined
|
||||||
|
vi.mocked(api.projectLinks).mockResolvedValue([
|
||||||
|
{ id: 1, from_project: 'KIN', to_project: 'BRS', type: 'triggers', description: null, created_at: '2026-01-01' },
|
||||||
|
] as any)
|
||||||
|
vi.mocked(api.projects).mockResolvedValue([BASE_PROJECT as any])
|
||||||
|
const wrapper = mount(SettingsView)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// Тип связи должен отображаться как строка, а не undefined
|
||||||
|
expect(wrapper.text()).toContain('triggers')
|
||||||
|
expect(wrapper.text()).not.toContain('undefined')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('addLink вызывает createProjectLink с полем type (не link_type) из формы', async () => {
|
||||||
|
// Bug #527: addLink() использовал link_type вместо type при вызове api
|
||||||
|
vi.mocked(api.projects).mockResolvedValue([
|
||||||
|
{ ...BASE_PROJECT, id: 'BRS', name: 'Barsik' } as any,
|
||||||
|
BASE_PROJECT as any,
|
||||||
|
])
|
||||||
|
vi.mocked(api.projectLinks).mockResolvedValue([])
|
||||||
|
const wrapper = mount(SettingsView)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// Открываем форму добавления связи для первого проекта
|
||||||
|
const addBtns = wrapper.findAll('button').filter(b => b.text().includes('+ Add Link'))
|
||||||
|
if (addBtns.length > 0) {
|
||||||
|
await addBtns[0].trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// Выбираем to_project (в SettingsView это select без api.projects — используем allProjectList)
|
||||||
|
const selects = wrapper.findAll('select')
|
||||||
|
const toProjectSelect = selects.find(s => s.findAll('option').some(o => o.element.value !== '' && o.element.value !== 'depends_on' && o.element.value !== 'triggers' && o.element.value !== 'related_to'))
|
||||||
|
if (toProjectSelect) {
|
||||||
|
const opts = toProjectSelect.findAll('option').filter(o => o.element.value !== '')
|
||||||
|
if (opts.length > 0) await toProjectSelect.setValue(opts[0].element.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = wrapper.find('form')
|
||||||
|
await form.trigger('submit')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
if (vi.mocked(api.createProjectLink).mock.calls.length > 0) {
|
||||||
|
const callArg = vi.mocked(api.createProjectLink).mock.calls[0][0]
|
||||||
|
expect(callArg).toHaveProperty('type')
|
||||||
|
expect(callArg).not.toHaveProperty('link_type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// 2. ProjectView — Deploy кнопка
|
// 2. ProjectView — Deploy кнопка
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
@ -603,6 +658,32 @@ describe('ProjectView — Links таб', () => {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('createProjectLink передаёт поле type (не link_type) — конвенция #527', async () => {
|
||||||
|
// Bug #527: addLink() передавал link_type вместо type → поле type отсутствовало в запросе
|
||||||
|
vi.mocked(api.projects).mockResolvedValue([
|
||||||
|
{ ...BASE_PROJECT, id: 'BRS', name: 'Barsik' } as any,
|
||||||
|
])
|
||||||
|
const wrapper = await mountProjectView()
|
||||||
|
await switchToLinksTab(wrapper)
|
||||||
|
|
||||||
|
const plusBtn = wrapper.findAll('button').find(b => b.text().includes('+') && b.text().includes('Link'))
|
||||||
|
await plusBtn!.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const selects = wrapper.findAll('select')
|
||||||
|
const toProjectSelect = selects.find(s => s.findAll('option').some(o => o.text().includes('BRS')))
|
||||||
|
if (toProjectSelect) await toProjectSelect.setValue('BRS')
|
||||||
|
|
||||||
|
const form = wrapper.find('form')
|
||||||
|
await form.trigger('submit')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const callArg = vi.mocked(api.createProjectLink).mock.calls[0]?.[0]
|
||||||
|
expect(callArg).toBeDefined()
|
||||||
|
expect(callArg).toHaveProperty('type')
|
||||||
|
expect(callArg).not.toHaveProperty('link_type')
|
||||||
|
})
|
||||||
|
|
||||||
it('Delete вызывает api.deleteProjectLink с id связи', async () => {
|
it('Delete вызывает api.deleteProjectLink с id связи', async () => {
|
||||||
const links = [
|
const links = [
|
||||||
{ id: 7, from_project: 'KIN', to_project: 'BRS', type: 'depends_on', description: null, created_at: '2026-01-01' },
|
{ id: 7, from_project: 'KIN', to_project: 'BRS', type: 'depends_on', description: null, created_at: '2026-01-01' },
|
||||||
|
|
|
||||||
|
|
@ -905,6 +905,28 @@ describe('KIN-049: TaskDetail — кнопка Deploy', () => {
|
||||||
expect(hasDeployBtn, 'Deploy не должна быть видна при статусе review').toBe(false)
|
expect(hasDeployBtn, 'Deploy не должна быть видна при статусе review').toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Кнопка Deploy видна при status=done с обоими полями одновременно (сосуществование #520)', async () => {
|
||||||
|
// Конвенция #520: legacy deploy_command и новый deploy_runtime могут быть заданы одновременно
|
||||||
|
vi.mocked(api.taskFull).mockResolvedValue(makeDeployTask('done', 'echo deploy.sh', 'node') as any)
|
||||||
|
const router = makeRouter()
|
||||||
|
await router.push('/task/KIN-049')
|
||||||
|
|
||||||
|
const wrapper = mount(TaskDetail, {
|
||||||
|
props: { id: 'KIN-049' },
|
||||||
|
global: { plugins: [router] },
|
||||||
|
})
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const deployBtn = wrapper.findAll('button').find(b => b.text().includes('Deploy'))
|
||||||
|
expect(deployBtn?.exists(), 'Deploy должна быть видна при одновременном наличии deploy_command и deploy_runtime').toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('makeDeployTask factory использует null по умолчанию для deployRuntime (конвенция #521)', () => {
|
||||||
|
// Конвенция #521: project_deploy_runtime: null — явное значение по умолчанию
|
||||||
|
const task = makeDeployTask('done', 'echo deploy.sh')
|
||||||
|
expect(task.project_deploy_runtime).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
it('Клик по Deploy вызывает api.deployProject с project_id задачи', async () => {
|
it('Клик по Deploy вызывает api.deployProject с project_id задачи', async () => {
|
||||||
vi.mocked(api.taskFull).mockResolvedValue(makeDeployTask('done', 'echo ok') as any)
|
vi.mocked(api.taskFull).mockResolvedValue(makeDeployTask('done', 'echo ok') as any)
|
||||||
vi.mocked(api.deployProject).mockResolvedValue({
|
vi.mocked(api.deployProject).mockResolvedValue({
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,8 @@ export interface ProjectLink {
|
||||||
id: number
|
id: number
|
||||||
from_project: string
|
from_project: string
|
||||||
to_project: string
|
to_project: string
|
||||||
|
// WARNING (decision #527): поле называется `type` — так отдаёт бэкенд.
|
||||||
|
// НЕ переименовывать в link_type — это вызовет runtime undefined во всех компонентах.
|
||||||
type: string
|
type: string
|
||||||
description: string | null
|
description: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ async function saveDeployConfig(projectId: string) {
|
||||||
deploy_path: deployPaths.value[projectId],
|
deploy_path: deployPaths.value[projectId],
|
||||||
deploy_runtime: deployRuntimes.value[projectId],
|
deploy_runtime: deployRuntimes.value[projectId],
|
||||||
deploy_restart_cmd: deployRestartCmds.value[projectId],
|
deploy_restart_cmd: deployRestartCmds.value[projectId],
|
||||||
deploy_command: deployCommands.value[projectId] || undefined,
|
deploy_command: deployCommands.value[projectId],
|
||||||
})
|
})
|
||||||
saveDeployConfigStatus.value[projectId] = 'Saved'
|
saveDeployConfigStatus.value[projectId] = 'Saved'
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|
@ -150,7 +150,7 @@ async function addLink(projectId: string) {
|
||||||
await api.createProjectLink({
|
await api.createProjectLink({
|
||||||
from_project: projectId,
|
from_project: projectId,
|
||||||
to_project: form.to_project,
|
to_project: form.to_project,
|
||||||
link_type: form.link_type,
|
type: form.link_type,
|
||||||
description: form.description || undefined,
|
description: form.description || undefined,
|
||||||
})
|
})
|
||||||
showAddLinkForm.value[projectId] = false
|
showAddLinkForm.value[projectId] = false
|
||||||
|
|
@ -306,7 +306,7 @@ async function deleteLink(projectId: string, linkId: number) {
|
||||||
<span class="text-gray-500 font-mono">{{ link.from_project }}</span>
|
<span class="text-gray-500 font-mono">{{ link.from_project }}</span>
|
||||||
<span class="text-gray-600">→</span>
|
<span class="text-gray-600">→</span>
|
||||||
<span class="text-gray-500 font-mono">{{ link.to_project }}</span>
|
<span class="text-gray-500 font-mono">{{ link.to_project }}</span>
|
||||||
<span class="px-1 bg-indigo-900/30 text-indigo-400 border border-indigo-800 rounded">{{ link.link_type }}</span>
|
<span class="px-1 bg-indigo-900/30 text-indigo-400 border border-indigo-800 rounded">{{ link.type }}</span>
|
||||||
<span v-if="link.description" class="text-gray-600">{{ link.description }}</span>
|
<span v-if="link.description" class="text-gray-600">{{ link.description }}</span>
|
||||||
<button @click="deleteLink(project.id, link.id)"
|
<button @click="deleteLink(project.id, link.id)"
|
||||||
class="ml-auto text-red-500 hover:text-red-400 bg-transparent border-none cursor-pointer text-xs shrink-0">
|
class="ml-auto text-red-500 hover:text-red-400 bg-transparent border-none cursor-pointer text-xs shrink-0">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue