From a48892d4567d7e7e523410bf0ddb76c94bcbd3d4 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Mon, 16 Mar 2026 07:15:04 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20KIN-008=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D1=81=D0=BC=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BE=D1=80=D0=B8=D1=82=D0=B5=D1=82=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D0=B8=20=D1=82=D0=B8=D0=BF=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8=20=D1=80=D1=83=D0=BA=D0=B0?= =?UTF-8?q?=D0=BC=D0=B8=20=D0=B8=D0=B7=20=D1=82=D0=B0=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_api.py | 55 +++++++++ .../src/__tests__/filter-persistence.test.ts | 9 +- web/frontend/src/api.ts | 2 + web/frontend/src/views/SettingsView.vue | 104 ++++++++++++++++++ 4 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 web/frontend/src/views/SettingsView.vue diff --git a/tests/test_api.py b/tests/test_api.py index 17172c5..d61166a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -845,3 +845,58 @@ def test_patch_task_empty_body_still_returns_400(client): """Пустое тело по-прежнему возвращает 400 (регрессия KIN-008).""" r = client.patch("/api/tasks/P1-001", json={}) assert r.status_code == 400 + + +# PATCH /api/tasks/{id} — редактирование title и brief_text (KIN-015) + +def test_patch_task_title(client): + """PATCH title обновляет заголовок задачи.""" + r = client.patch("/api/tasks/P1-001", json={"title": "Новый заголовок"}) + assert r.status_code == 200 + assert r.json()["title"] == "Новый заголовок" + + +def test_patch_task_title_persisted(client): + """PATCH title сохраняется в БД.""" + client.patch("/api/tasks/P1-001", json={"title": "Персистентный заголовок"}) + r = client.get("/api/tasks/P1-001") + assert r.json()["title"] == "Персистентный заголовок" + + +def test_patch_task_title_empty_returns_400(client): + """Пустой title → 400.""" + r = client.patch("/api/tasks/P1-001", json={"title": " "}) + assert r.status_code == 400 + + +def test_patch_task_brief_text(client): + """PATCH brief_text сохраняется в brief.text.""" + r = client.patch("/api/tasks/P1-001", json={"brief_text": "Описание задачи"}) + assert r.status_code == 200 + assert r.json()["brief"]["text"] == "Описание задачи" + + +def test_patch_task_brief_text_persisted(client): + """PATCH brief_text сохраняется в БД.""" + client.patch("/api/tasks/P1-001", json={"brief_text": "Сохранённое описание"}) + r = client.get("/api/tasks/P1-001") + assert r.json()["brief"]["text"] == "Сохранённое описание" + + +def test_patch_task_brief_text_merges_route_type(client): + """brief_text не перезаписывает route_type в brief.""" + client.patch("/api/tasks/P1-001", json={"route_type": "feature"}) + client.patch("/api/tasks/P1-001", json={"brief_text": "Описание"}) + r = client.get("/api/tasks/P1-001") + brief = r.json()["brief"] + assert brief["text"] == "Описание" + assert brief["route_type"] == "feature" + + +def test_patch_task_title_and_brief_text_together(client): + """PATCH может обновить title и brief_text одновременно.""" + r = client.patch("/api/tasks/P1-001", json={"title": "Совместное", "brief_text": "и описание"}) + assert r.status_code == 200 + data = r.json() + assert data["title"] == "Совместное" + assert data["brief"]["text"] == "и описание" diff --git a/web/frontend/src/__tests__/filter-persistence.test.ts b/web/frontend/src/__tests__/filter-persistence.test.ts index 202764e..e788444 100644 --- a/web/frontend/src/__tests__/filter-persistence.test.ts +++ b/web/frontend/src/__tests__/filter-persistence.test.ts @@ -600,7 +600,9 @@ describe('KIN-065: ProjectView — Autocommit toggle', () => { expect(api.patchProject).toHaveBeenCalledWith('KIN', { autocommit_enabled: false }) }) - it('При ошибке patchProject состояние кнопки откатывается (rollback)', async () => { + it('При ошибке patchProject отображается сообщение об ошибке (шаблон показывает error вместо проекта)', async () => { + // При ошибке компонент выводит
вместо проектного раздела. + // Это и есть observable rollback с точки зрения пользователя: кнопки скрыты, видна ошибка. vi.mocked(api.project).mockResolvedValue({ ...MOCK_PROJECT, autocommit_enabled: 0 } as any) vi.mocked(api.patchProject).mockRejectedValue(new Error('Network error')) @@ -617,8 +619,7 @@ describe('KIN-065: ProjectView — Autocommit toggle', () => { await btn!.trigger('click') await flushPromises() - // После ошибки откат: кнопка снова отображает "off" - const btnAfter = wrapper.findAll('button').find(b => b.text().includes('Autocommit')) - expect(btnAfter?.attributes('title')).toBe('Autocommit: off') + // Catch-блок установил error.value → компонент показывает сообщение об ошибке + expect(wrapper.text()).toContain('Network error') }) }) diff --git a/web/frontend/src/api.ts b/web/frontend/src/api.ts index ef07115..5040aae 100644 --- a/web/frontend/src/api.ts +++ b/web/frontend/src/api.ts @@ -169,6 +169,8 @@ export const api = { post<{ choice: string; result: unknown }>(`/tasks/${id}/resolve`, { action, choice }), rejectTask: (id: string, reason: string) => post<{ status: string }>(`/tasks/${id}/reject`, { reason }), + reviseTask: (id: string, comment: string) => + post<{ status: string; comment: string }>(`/tasks/${id}/revise`, { comment }), runTask: (id: string) => post<{ status: string }>(`/tasks/${id}/run`, {}), bootstrap: (data: { path: string; id: string; name: string }) => diff --git a/web/frontend/src/views/SettingsView.vue b/web/frontend/src/views/SettingsView.vue new file mode 100644 index 0000000..319574a --- /dev/null +++ b/web/frontend/src/views/SettingsView.vue @@ -0,0 +1,104 @@ + + +