from datetime import datetime import uuid import pytest def test_projects_list_smoke(client, admin_tokens, auth_headers): response = client.get("/api/v1/projects", headers=auth_headers(admin_tokens)) assert response.status_code == 200 payload = response.json() assert "result" in payload assert "count" in payload assert isinstance(payload["result"], list) def _create_project(client, admin_tokens, auth_headers, year=2026, branch_id=1) -> tuple[int, str]: name = f"PT_{uuid.uuid4().hex[:10]}" response = client.post( "/api/v1/projects", json={"name": name, "year": year, "branch_id": branch_id}, headers=auth_headers(admin_tokens), ) assert response.status_code == 200 payload = response.json() return payload["project_id"], name def _add_line(client, admin_tokens, auth_headers, project_id: int, year: int = 2026) -> int: for expense_item_id in range(1, 80): response = client.post( f"/api/v1/projects/{project_id}/report/{year}/LIMIT/line", json={"expense_item_id": expense_item_id}, headers=auth_headers(admin_tokens), ) if response.status_code != 200: continue rows = response.json() input_row = next( ( row for row in rows if row.get("row_type") == "INPUT" and row.get("data", {}).get("line_id") ), None, ) if input_row: return int(input_row["data"]["line_id"]) pytest.skip("Не удалось подобрать expense_item_id для add_form3_line в текущей БД") def test_create_project_smoke(client, admin_tokens, auth_headers): name = f"PT_{uuid.uuid4().hex[:10]}" response = client.post( "/api/v1/projects", json={"name": name, "year": None, "branch_id": 1}, headers=auth_headers(admin_tokens), ) assert response.status_code == 200 payload = response.json() assert "project_id" in payload assert (project_id := payload["project_id"]) is not None response = client.get( f"/api/v1/projects/{project_id}", headers=auth_headers(admin_tokens), ) assert response.status_code == 200 payload = response.json() assert "result" in payload assert "id" in payload["result"] assert payload["result"]["id"] == project_id assert "years" in payload["result"] assert isinstance(payload["result"]["years"], list) assert len(payload["result"]["years"]) == 1 def test_projects_write_smoke(client, admin_tokens, auth_headers): project_id, name = _create_project(client, admin_tokens, auth_headers) upd_project_response = client.patch( f"/api/v1/project/{project_id}", json={"column": "name", "value": f"{name}_U"}, headers=auth_headers(admin_tokens), ) assert upd_project_response.status_code == 200 line_id = _add_line(client, admin_tokens, auth_headers, project_id) upd_cell_response = client.patch( f"/api/v1/projects/{project_id}/report/2026/LIMIT/cell", json={"line_id": line_id, "column": "q1.m1", "value": 100}, headers=auth_headers(admin_tokens), ) assert upd_cell_response.status_code == 200 assert isinstance(upd_cell_response.json(), list) upd_cells_response = client.patch( f"/api/v1/projects/{project_id}/report/2026/LIMIT/cells", json={ "changes": [ {"line_id": line_id, "column": "q1.m2", "value": 200}, {"line_id": line_id, "column": "q1.m3", "value": 300}, ] }, headers=auth_headers(admin_tokens), ) assert upd_cells_response.status_code == 200 assert isinstance(upd_cells_response.json(), list) del_line_response = client.delete( f"/api/v1/projects/{project_id}/report/2026/LIMIT/line/{line_id}", headers=auth_headers(admin_tokens), ) assert del_line_response.status_code == 200 def test_projects_write_invalid_report_type_error_shape(client, admin_tokens, auth_headers): project_id, _ = _create_project(client, admin_tokens, auth_headers) line_id = _add_line(client, admin_tokens, auth_headers, project_id) response = client.patch( f"/api/v1/projects/{project_id}/report/2026/BAD_TYPE/cell", json={"line_id": line_id, "column": "q1.m1", "value": 100}, headers=auth_headers(admin_tokens), ) assert response.status_code == 400 payload = response.json() assert isinstance(payload, dict) assert "message" in payload assert isinstance(payload["message"], str) def test_projects_write_report_not_found_error_shape(client, admin_tokens, auth_headers): project_id, _ = _create_project(client, admin_tokens, auth_headers) line_id = _add_line(client, admin_tokens, auth_headers, project_id) response = client.patch( f"/api/v1/projects/{project_id}/report/2099/LIMIT/cell", json={"line_id": line_id, "column": "q1.m1", "value": 100}, headers=auth_headers(admin_tokens), ) assert response.status_code == 400 payload = response.json() assert isinstance(payload, dict) assert payload.get("message") == "Отчёт не найден" def test_delete_project_smoke(client, admin_tokens, auth_headers): project_id, _ = _create_project(client, admin_tokens, auth_headers, year=datetime.now().year) response = client.delete( f"/api/v1/projects/{project_id}", headers=auth_headers(admin_tokens), ) assert response.status_code == 200 payload = response.json() assert payload["success"] is True assert payload["message"] == "Проект удалён" def test_delete_project_verify_gone(client, admin_tokens, auth_headers): project_id, _ = _create_project(client, admin_tokens, auth_headers, year=datetime.now().year) del_response = client.delete( f"/api/v1/projects/{project_id}", headers=auth_headers(admin_tokens), ) assert del_response.status_code == 200 get_response = client.get( f"/api/v1/projects/{project_id}", headers=auth_headers(admin_tokens), ) assert get_response.status_code == 404 def test_delete_project_not_found(client, admin_tokens, auth_headers): response = client.delete( "/api/v1/projects/99999", headers=auth_headers(admin_tokens), ) assert response.status_code == 400 payload = response.json() assert isinstance(payload, dict) assert "message" in payload def test_delete_project_unauthorized(client): response = client.delete("/api/v1/projects/1") assert response.status_code == 403 #ToDo: вернуть, когда пофиксим смещение фаз в зависимости от года # def test_delete_project_with_lines_blocked(client, admin_tokens, auth_headers): # """Проект со строками отчёта нельзя удалить: can_delete_project → false → RAISE EXCEPTION.""" # year = datetime.now().year - 1 # project_id, _ = _create_project(client, admin_tokens, auth_headers, year=year) # _add_line(client, admin_tokens, auth_headers, project_id, year=year) # response = client.delete( # f"/api/v1/projects/{project_id}", # headers=auth_headers(admin_tokens), # ) # assert response.status_code == 400 # payload = response.json() # assert isinstance(payload, dict) # assert "code" in payload # assert "message" in payload # assert "невозможно удалить" in payload["message"] def test_delete_project_executor_from_other_org(client, admin_tokens, auth_headers, isp_tokens): """ISP1 (привязан к РФ id=2) не может удалить проект на ССП id=1 — ACL.""" project_id, _ = _create_project(client, admin_tokens, auth_headers, branch_id=1, year=datetime.now().year) response = client.delete( f"/api/v1/projects/{project_id}", headers=auth_headers(isp_tokens), ) assert response.status_code == 400 payload = response.json() assert "message" in payload assert payload["message"] == "Проект не найден" #ToDo: вернуть, когда пофиксим смещение фаз в зависимости от года # def test_delete_project_executor_from_same_org(client, isp_tokens, auth_headers): # """ISP1 может удалить проект на своём РФ (id=2), если нет строк отчёта.""" # project_id = _create_project(client, isp_tokens, auth_headers, branch_id=2, year=datetime.now().year) # response = client.delete( # f"/api/v1/projects/{project_id}", # headers=auth_headers(isp_tokens), # ) # assert response.status_code == 200 # payload = response.json() # assert payload["success"] is True # assert payload["message"] == "Проект удалён" def test_delete_seed_project_smoke(client, admin_tokens, auth_headers): """Проект из фикстуры (id=2) удаляется: у него нет строк отчёта, can_delete_project → true.""" response = client.delete( "/api/v1/projects/2", headers=auth_headers(admin_tokens), ) assert response.status_code == 200 payload = response.json() assert payload["success"] is True assert payload["message"] == "Проект удалён"