From e80e50ba0c8f5b4c957ea69b6e9231d34fe8cb98 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Mon, 16 Mar 2026 20:17:39 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20KIN-UI-005=20=D0=9D=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D1=82=D1=8C=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20chat=20endpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/db.py | 13 ++++++++++ tests/test_api_chat.py | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/test_api_chat.py diff --git a/core/db.py b/core/db.py index ddb3280..b190e97 100644 --- a/core/db.py +++ b/core/db.py @@ -263,6 +263,19 @@ CREATE TABLE IF NOT EXISTS chat_messages ( ); CREATE INDEX IF NOT EXISTS idx_chat_messages_project ON chat_messages(project_id, created_at); + +-- Вложения задач (KIN-090) +CREATE TABLE IF NOT EXISTS task_attachments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, + filename TEXT NOT NULL, + path TEXT NOT NULL, + mime_type TEXT NOT NULL, + size INTEGER NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX IF NOT EXISTS idx_task_attachments_task ON task_attachments(task_id); """ diff --git a/tests/test_api_chat.py b/tests/test_api_chat.py new file mode 100644 index 0000000..162c961 --- /dev/null +++ b/tests/test_api_chat.py @@ -0,0 +1,56 @@ +"""Tests for chat endpoints: GET/POST /api/projects/{project_id}/chat (KIN-UI-005).""" + +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient + +import web.api as api_module + + +@pytest.fixture +def client(tmp_path): + db_path = tmp_path / "test.db" + api_module.DB_PATH = db_path + from web.api import app + c = TestClient(app) + c.post("/api/projects", json={"id": "p1", "name": "P1", "path": "/p1"}) + return c + + +def test_get_chat_history_empty_for_new_project(client): + r = client.get("/api/projects/p1/chat") + assert r.status_code == 200 + assert r.json() == [] + + +def test_post_chat_task_request_creates_task_stub(client): + with patch("core.chat_intent.classify_intent", return_value="task_request"), \ + patch("web.api.subprocess.Popen") as mock_popen: + mock_popen.return_value = MagicMock() + r = client.post("/api/projects/p1/chat", json={"content": "Добавь кнопку выхода"}) + + assert r.status_code == 200 + data = r.json() + assert data["user_message"]["role"] == "user" + assert data["assistant_message"]["message_type"] == "task_created" + assert "task_stub" in data["assistant_message"] + assert data["assistant_message"]["task_stub"]["status"] == "pending" + assert data["task"] is not None + assert mock_popen.called + + +def test_post_chat_status_query_returns_text_response(client): + with patch("core.chat_intent.classify_intent", return_value="status_query"): + r = client.post("/api/projects/p1/chat", json={"content": "что сейчас в работе?"}) + + assert r.status_code == 200 + data = r.json() + assert data["user_message"]["role"] == "user" + assert data["assistant_message"]["role"] == "assistant" + assert data["task"] is None + assert "Нет активных задач" in data["assistant_message"]["content"] + + +def test_post_chat_empty_content_returns_400(client): + r = client.post("/api/projects/p1/chat", json={"content": " "}) + assert r.status_code == 400