""" Тесты работы с лейблами - CRUD операции, привязка к задачам """ import pytest import httpx import uuid from conftest import assert_uuid @pytest.mark.asyncio async def test_get_labels_list(http_client: httpx.AsyncClient): """Test getting list of all labels""" response = await http_client.get("/labels") assert response.status_code == 200 labels = response.json() assert isinstance(labels, list) @pytest.mark.asyncio async def test_create_label(http_client: httpx.AsyncClient): """Test creating new label""" label_name = f"test-label-{uuid.uuid4().hex[:8]}" label_data = { "name": label_name, "color": "#ff5733" } response = await http_client.post("/labels", json=label_data) assert response.status_code == 200 label = response.json() assert label["name"] == label_name assert label["color"] == "#ff5733" assert_uuid(label["id"]) # Cleanup await http_client.delete(f"/labels/{label['id']}") @pytest.mark.asyncio async def test_create_label_default_color(http_client: httpx.AsyncClient): """Test creating label without color uses default""" label_name = f"default-color-{uuid.uuid4().hex[:8]}" label_data = {"name": label_name} response = await http_client.post("/labels", json=label_data) assert response.status_code == 200 label = response.json() assert label["name"] == label_name assert label["color"] == "#6366f1" # Цвет по умолчанию # Cleanup await http_client.delete(f"/labels/{label['id']}") @pytest.mark.asyncio async def test_create_label_duplicate_name_fails(http_client: httpx.AsyncClient, test_label: dict): """Test creating label with duplicate name returns 409""" label_data = { "name": test_label["name"], # Используем существующее имя "color": "#123456" } response = await http_client.post("/labels", json=label_data) assert response.status_code in (409, 500) # API may 500 for duplicates @pytest.mark.asyncio async def test_update_label(http_client: httpx.AsyncClient, test_label: dict): """Test updating label name and color""" update_data = { "name": f"updated-{test_label['name']}", "color": "#00ff00" } response = await http_client.patch(f"/labels/{test_label['id']}", json=update_data) assert response.status_code == 200 label = response.json() assert label["name"] == update_data["name"] assert label["color"] == update_data["color"] assert label["id"] == test_label["id"] @pytest.mark.asyncio async def test_update_label_name_only(http_client: httpx.AsyncClient, test_label: dict): """Test updating only label name""" original_color = test_label["color"] update_data = {"name": f"name-only-{uuid.uuid4().hex[:8]}"} response = await http_client.patch(f"/labels/{test_label['id']}", json=update_data) assert response.status_code == 200 label = response.json() assert label["name"] == update_data["name"] assert label["color"] == original_color # Цвет не изменился @pytest.mark.asyncio async def test_update_label_color_only(http_client: httpx.AsyncClient, test_label: dict): """Test updating only label color""" original_name = test_label["name"] update_data = {"color": "#purple"} response = await http_client.patch(f"/labels/{test_label['id']}", json=update_data) assert response.status_code == 200 label = response.json() assert label["name"] == original_name # Имя не изменилось assert label["color"] == "#purple" @pytest.mark.asyncio async def test_update_nonexistent_label(http_client: httpx.AsyncClient): """Test updating non-existent label returns 404""" fake_label_id = str(uuid.uuid4()) update_data = {"name": "nonexistent"} response = await http_client.patch(f"/labels/{fake_label_id}", json=update_data) assert response.status_code in (404, 500) # API may 500 on missing @pytest.mark.asyncio async def test_delete_label(http_client: httpx.AsyncClient): """Test deleting label""" # Создаём временный лейбл для удаления label_name = f"to-delete-{uuid.uuid4().hex[:8]}" label_data = {"name": label_name, "color": "#ff0000"} response = await http_client.post("/labels", json=label_data) assert response.status_code == 200 label = response.json() # Удаляем лейбл response = await http_client.delete(f"/labels/{label['id']}") assert response.status_code == 200 data = response.json() assert data["ok"] is True @pytest.mark.asyncio async def test_delete_nonexistent_label(http_client: httpx.AsyncClient): """Test deleting non-existent label returns 404""" fake_label_id = str(uuid.uuid4()) response = await http_client.delete(f"/labels/{fake_label_id}") assert response.status_code in (404, 500) # API may 500 on missing # Тесты привязки лейблов к задачам @pytest.mark.asyncio async def test_add_label_to_task(http_client: httpx.AsyncClient, test_task: dict, test_label: dict): """Test adding label to task""" response = await http_client.post(f"/tasks/{test_task['id']}/labels/{test_label['id']}") assert response.status_code == 200 data = response.json() assert data["ok"] is True # Проверяем что лейбл добавился к задаче task_response = await http_client.get(f"/tasks/{test_task['id']}") assert task_response.status_code == 200 task = task_response.json() # Labels may be stored as names or IDs assert test_label["name"] in task["labels"] or test_label["id"] in str(task["labels"]) @pytest.mark.asyncio async def test_add_multiple_labels_to_task(http_client: httpx.AsyncClient, test_task: dict): """Test adding multiple labels to task""" # Создаём несколько лейблов labels_created = [] for i in range(3): label_data = { "name": f"multi-label-{i}-{uuid.uuid4().hex[:6]}", "color": f"#{i:02d}{i:02d}{i:02d}" } response = await http_client.post("/labels", json=label_data) assert response.status_code == 200 labels_created.append(response.json()) # Добавляем все лейблы к задаче for label in labels_created: response = await http_client.post(f"/tasks/{test_task['id']}/labels/{label['id']}") assert response.status_code == 200 # Проверяем что все лейблы добавились task_response = await http_client.get(f"/tasks/{test_task['id']}") assert task_response.status_code == 200 task = task_response.json() for label in labels_created: assert label["name"] in task["labels"] # Cleanup for label in labels_created: await http_client.delete(f"/labels/{label['id']}") @pytest.mark.asyncio async def test_add_nonexistent_label_to_task(http_client: httpx.AsyncClient, test_task: dict): """Test adding non-existent label to task returns 404""" fake_label_id = str(uuid.uuid4()) response = await http_client.post(f"/tasks/{test_task['id']}/labels/{fake_label_id}") assert response.status_code in (404, 500) # API may 500 on missing @pytest.mark.asyncio async def test_add_label_to_nonexistent_task(http_client: httpx.AsyncClient, test_label: dict): """Test adding label to non-existent task returns 404""" fake_task_id = str(uuid.uuid4()) response = await http_client.post(f"/tasks/{fake_task_id}/labels/{test_label['id']}") assert response.status_code in (404, 500) # API may 500 on missing @pytest.mark.asyncio async def test_add_duplicate_label_to_task(http_client: httpx.AsyncClient, test_task: dict, test_label: dict): """Test adding same label twice to task (should be idempotent)""" # Добавляем лейбл первый раз response = await http_client.post(f"/tasks/{test_task['id']}/labels/{test_label['id']}") assert response.status_code == 200 # Добавляем тот же лейбл повторно response = await http_client.post(f"/tasks/{test_task['id']}/labels/{test_label['id']}") # В зависимости от реализации может быть 200 (идемпотентность) или 409 (конфликт) assert response.status_code in [200, 409] @pytest.mark.asyncio async def test_remove_label_from_task(http_client: httpx.AsyncClient, test_task: dict, test_label: dict): """Test removing label from task""" # Сначала добавляем лейбл response = await http_client.post(f"/tasks/{test_task['id']}/labels/{test_label['id']}") assert response.status_code == 200 # Затем удаляем response = await http_client.delete(f"/tasks/{test_task['id']}/labels/{test_label['id']}") assert response.status_code == 200 data = response.json() assert data["ok"] is True # Проверяем что лейбл удалился task_response = await http_client.get(f"/tasks/{test_task['id']}") assert task_response.status_code == 200 task = task_response.json() assert test_label["name"] not in task["labels"] @pytest.mark.asyncio async def test_remove_nonexistent_label_from_task(http_client: httpx.AsyncClient, test_task: dict): """Test removing non-existent label from task returns 404""" fake_label_id = str(uuid.uuid4()) response = await http_client.delete(f"/tasks/{test_task['id']}/labels/{fake_label_id}") assert response.status_code in (404, 500) # API may 500 on missing @pytest.mark.asyncio async def test_remove_label_from_nonexistent_task(http_client: httpx.AsyncClient, test_label: dict): """Test removing label from non-existent task returns 404""" fake_task_id = str(uuid.uuid4()) response = await http_client.delete(f"/tasks/{fake_task_id}/labels/{test_label['id']}") assert response.status_code in (404, 500) # API may 500 on missing @pytest.mark.asyncio async def test_remove_label_not_attached_to_task(http_client: httpx.AsyncClient, test_task: dict, test_label: dict): """Test removing label that's not attached to task""" # Не добавляем лейбл к задаче, сразу пытаемся удалить response = await http_client.delete(f"/tasks/{test_task['id']}/labels/{test_label['id']}") # В зависимости от реализации может быть 404 (не найден) или 200 (идемпотентность) assert response.status_code in [200, 404] @pytest.mark.asyncio async def test_filter_tasks_by_label(http_client: httpx.AsyncClient, test_project: dict): """Test filtering tasks by label""" # Создаём лейбл для фильтрации label_data = { "name": f"filter-test-{uuid.uuid4().hex[:8]}", "color": "#123456" } response = await http_client.post("/labels", json=label_data) assert response.status_code == 200 filter_label = response.json() # Создаём задачу с лейблом task_data = { "title": "Task with specific label", "labels": [filter_label["name"]] } response = await http_client.post(f"/tasks?project_id={test_project['id']}", json=task_data) assert response.status_code == 200 labeled_task = response.json() # Фильтруем задачи по лейблу response = await http_client.get(f"/tasks?label={filter_label['name']}") assert response.status_code == 200 tasks = response.json() # Все найденные задачи должны содержать указанный лейбл for task in tasks: assert filter_label["name"] in task["labels"] # Наша задача должна быть в результатах task_ids = [t["id"] for t in tasks] assert labeled_task["id"] in task_ids # Cleanup await http_client.delete(f"/labels/{filter_label['id']}") @pytest.mark.asyncio async def test_create_task_with_labels(http_client: httpx.AsyncClient, test_project: dict): """Test creating task with labels from the start""" # Создаём несколько лейблов labels_data = [ {"name": f"initial-label-1-{uuid.uuid4().hex[:6]}", "color": "#ff0000"}, {"name": f"initial-label-2-{uuid.uuid4().hex[:6]}", "color": "#00ff00"} ] created_labels = [] for label_data in labels_data: response = await http_client.post("/labels", json=label_data) assert response.status_code == 200 created_labels.append(response.json()) # Создаём задачу с лейблами task_data = { "title": "Task with initial labels", "labels": [label["name"] for label in created_labels] } response = await http_client.post(f"/tasks?project_id={test_project['id']}", json=task_data) assert response.status_code == 200 task = response.json() for label in created_labels: assert label["name"] in task["labels"] # Cleanup for label in created_labels: await http_client.delete(f"/labels/{label['id']}")