diff --git a/tests/test_qa_gaps.py b/tests/test_qa_gaps.py index 2fbe968..8922325 100644 --- a/tests/test_qa_gaps.py +++ b/tests/test_qa_gaps.py @@ -140,17 +140,10 @@ class TestBlockedReasonPropagation: task = models.get_task(conn, "PROJ-001") assert task["status"] == "blocked" - # BUG DOCUMENTED: The specific worker error is NOT in task.blocked_reason. - # Current behavior: task.blocked_reason == 'Department backend_head sub-pipeline failed' - # Desired behavior: specific_worker_error should appear in task.blocked_reason - assert specific_worker_error not in (task["blocked_reason"] or ""), ( - f"Unexpected: specific worker error was propagated to task.blocked_reason. " - f"This means Issue 1 has been fixed — update this test!" - ) - - # Verify: generic message IS what gets stored (documents current behavior) - assert "backend_head" in (task["blocked_reason"] or ""), ( - "Expected task.blocked_reason to contain 'backend_head' (the generic message)" + # FIXED (KIN-ARCH-014): specific worker error IS now in task.blocked_reason + assert specific_worker_error in (task["blocked_reason"] or ""), ( + f"Expected specific worker error in task.blocked_reason, " + f"got: {task['blocked_reason']!r}" ) @patch("agents.runner._run_autocommit") diff --git a/web/frontend/src/__tests__/upload-warning.test.ts b/web/frontend/src/__tests__/upload-warning.test.ts new file mode 100644 index 0000000..9382bdc --- /dev/null +++ b/web/frontend/src/__tests__/upload-warning.test.ts @@ -0,0 +1,304 @@ +/** + * KIN-UI-009: Предупреждение при частичной загрузке вложений + * + * Проверяет: + * 1. Если все файлы загружены успешно — баннер uploadWarning не показывается + * 2. Если один файл упал — показывается предупреждение с его именем + * 3. Если несколько файлов упали — все имена перечислены в предупреждении + * 4. Если часть файлов прошла успешно — в предупреждении только упавшие + * 5. Кнопка ✕ скрывает баннер + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import ProjectView from '../views/ProjectView.vue' + +vi.mock('../api', () => ({ + api: { + project: vi.fn(), + taskFull: vi.fn(), + runTask: vi.fn(), + auditProject: vi.fn(), + createTask: vi.fn(), + patchTask: vi.fn(), + patchProject: vi.fn(), + deployProject: vi.fn(), + getPhases: vi.fn(), + uploadAttachment: vi.fn(), + environments: vi.fn(), + }, +})) + +import { api } from '../api' + +const Stub = { template: '
' } + +function makeTask(id: string, status: string = 'pending') { + return { + id, + project_id: 'KIN', + title: `Task ${id}`, + status, + priority: 5, + assigned_role: null, + parent_task_id: null, + brief: null, + spec: null, + execution_mode: null, + blocked_reason: null, + dangerously_skipped: null, + category: null, + acceptance_criteria: null, + created_at: '2024-01-01', + updated_at: '2024-01-01', + } +} + +const MOCK_PROJECT = { + id: 'KIN', + name: 'Kin', + path: '/projects/kin', + status: 'active', + priority: 5, + tech_stack: ['python', 'vue'], + execution_mode: 'review', + autocommit_enabled: 0, + obsidian_vault_path: null, + deploy_command: null, + created_at: '2024-01-01', + total_tasks: 0, + done_tasks: 0, + active_tasks: 0, + blocked_tasks: 0, + review_tasks: 0, + project_type: 'development', + ssh_host: null, + ssh_user: null, + ssh_key_path: null, + ssh_proxy_jump: null, + description: null, + tasks: [], + decisions: [], + modules: [], +} + +const localStorageMock = (() => { + let store: Record = {} + return { + getItem: (k: string) => store[k] ?? null, + setItem: (k: string, v: string) => { store[k] = v }, + removeItem: (k: string) => { delete store[k] }, + clear: () => { store = {} }, + } +})() +Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true }) + +function makeRouter() { + return createRouter({ + history: createMemoryHistory(), + routes: [ + { path: '/', component: Stub }, + { path: '/project/:id', component: ProjectView, props: true }, + ], + }) +} + +async function mountAndOpenAddTaskModal() { + const router = makeRouter() + await router.push('/project/KIN') + const wrapper = mount(ProjectView, { + props: { id: 'KIN' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // Открываем модал добавления задачи + const tasBtn = wrapper.findAll('button').find(b => b.text() === '+ Тас')! + await tasBtn.trigger('click') + await flushPromises() + + return wrapper +} + +/** Добавляет файлы в pendingFiles через внутреннее состояние