kin: auto-commit after pipeline
This commit is contained in:
parent
40e1001cea
commit
62f0ccc292
3 changed files with 193 additions and 0 deletions
|
|
@ -329,6 +329,72 @@ describe('отображение логов', () => {
|
|||
expect(wrapper.text()).toContain('Network fail')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
// KIN-OBS-026: catch (e: unknown) — type narrowing
|
||||
it('не-Error строка отображается через String(e) (catch unknown narrowing)', async () => {
|
||||
vi.mocked(api.getPipelineLogs).mockRejectedValueOnce('строковая ошибка')
|
||||
|
||||
const wrapper = mountConsole('done')
|
||||
await wrapper.find('button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('Ошибка')
|
||||
expect(wrapper.text()).toContain('строковая ошибка')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('не-Error число отображается через String(e) (catch unknown narrowing)', async () => {
|
||||
vi.mocked(api.getPipelineLogs).mockRejectedValueOnce(500)
|
||||
|
||||
const wrapper = mountConsole('done')
|
||||
await wrapper.find('button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('Ошибка')
|
||||
expect(wrapper.text()).toContain('500')
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// KIN-OBS-026: scrollTimer cleanup
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
describe('scrollTimer cleanup (KIN-OBS-026)', () => {
|
||||
it('onUnmounted очищает scrollTimer если он был активен', async () => {
|
||||
// fetchLogs с логами создаёт scrollTimer = setTimeout(scrollToBottom, 0)
|
||||
vi.mocked(api.getPipelineLogs).mockResolvedValueOnce([makeLog(1)] as any)
|
||||
|
||||
const wrapper = mountConsole('done')
|
||||
await wrapper.find('button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
// С fake timers setTimeout(scrollToBottom, 0) ещё не выполнен — таймер висит
|
||||
expect(vi.getTimerCount()).toBeGreaterThan(0)
|
||||
|
||||
wrapper.unmount()
|
||||
// stopPolling() очищает scrollTimer через clearTimeout
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
})
|
||||
|
||||
it('закрытие панели очищает scrollTimer вместе с setInterval', async () => {
|
||||
vi.mocked(api.getPipelineLogs)
|
||||
.mockResolvedValueOnce([makeLog(1)] as any)
|
||||
.mockResolvedValue([])
|
||||
|
||||
const wrapper = mountConsole('running')
|
||||
await wrapper.find('button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
// Есть как минимум setInterval + scrollTimer
|
||||
expect(vi.getTimerCount()).toBeGreaterThanOrEqual(2)
|
||||
|
||||
// Закрываем панель → stopPolling() вызывает clearInterval + clearTimeout
|
||||
await wrapper.find('button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ const rejectReason = ref('')
|
|||
const showRevise = ref(false)
|
||||
const reviseComment = ref('')
|
||||
|
||||
const parsedSelectedOutput = computed<ParsedAgentOutput | null>(() => {
|
||||
if (!selectedStep.value) return null
|
||||
return parseAgentOutput(selectedStep.value.output_summary)
|
||||
})
|
||||
|
||||
// Auto/Review mode (per-task, persisted in DB; falls back to localStorage per project)
|
||||
const autoMode = ref(false)
|
||||
|
||||
|
|
@ -135,6 +140,28 @@ function formatOutput(text: string | null): string {
|
|||
}
|
||||
}
|
||||
|
||||
interface ParsedAgentOutput {
|
||||
verdict: string | null
|
||||
details: string | null
|
||||
raw: string
|
||||
}
|
||||
|
||||
function parseAgentOutput(text: string | null): ParsedAgentOutput {
|
||||
if (!text) return { verdict: null, details: null, raw: '' }
|
||||
const verdictMatch = text.match(/##\s*Verdict\s*\n([\s\S]*?)(?=##\s*Details|$)/m)
|
||||
const detailsJsonMatch = text.match(/##\s*Details[\s\S]*?```json\n([\s\S]*?)```/)
|
||||
const verdict = verdictMatch ? verdictMatch[1].trim() : null
|
||||
let details: string | null = null
|
||||
if (detailsJsonMatch) {
|
||||
try {
|
||||
details = JSON.stringify(JSON.parse(detailsJsonMatch[1].trim()), null, 2)
|
||||
} catch {
|
||||
details = detailsJsonMatch[1].trim()
|
||||
}
|
||||
}
|
||||
return { verdict, details, raw: text }
|
||||
}
|
||||
|
||||
async function approve() {
|
||||
if (!task.value) return
|
||||
approveLoading.value = true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue