add integrated tests

This commit is contained in:
Raykov-MS 2026-05-18 16:12:16 +03:00
parent a718827c12
commit 5cd7232eb2
2 changed files with 312 additions and 2 deletions

View File

@ -0,0 +1,310 @@
import uuid
import pytest
def _auth_headers(tokens: dict) -> dict:
return {"Authorization": f"Bearer {tokens['access_token']}"}
def _pick_form_and_sheet(client, admin_tokens) -> tuple[int, str, str | None]:
response = client.get("/api/v1/form/", headers=_auth_headers(admin_tokens))
assert response.status_code == 200
forms = response.json().get("result") or []
for form in forms:
form_id = form.get("id")
form_type = form.get("form_type_code")
if not form_id or not form_type:
continue
sheets_response = client.get(
f"/api/v1/form/{form_id}/sheets",
headers=_auth_headers(admin_tokens),
)
if sheets_response.status_code != 200:
continue
sheets = (sheets_response.json().get("result") or {}).get("sheets") or []
if "AHR" not in sheets:
continue
direction = "Support" if form_type == "FORM_1" else None
return int(form_id), "AHR", direction
pytest.skip("В тестовой БД не найдена форма с листом AHR")
def _pick_form_with_sheet(
client, admin_tokens, target_sheet: str, form_type_code: str | None = None
) -> tuple[int, str]:
response = client.get("/api/v1/form/", headers=_auth_headers(admin_tokens))
assert response.status_code == 200
forms = response.json().get("result") or []
for form in forms:
form_id = form.get("id")
if not form_id:
continue
if form_type_code and form.get("form_type_code") != form_type_code:
continue
sheets_response = client.get(
f"/api/v1/form/{form_id}/sheets",
headers=_auth_headers(admin_tokens),
)
if sheets_response.status_code != 200:
continue
sheets = (sheets_response.json().get("result") or {}).get("sheets") or []
if target_sheet in sheets:
return int(form_id), target_sheet
pytest.skip(f"Не найдена форма с листом {target_sheet}")
def _pick_input_line_id(client, admin_tokens, form_id: int, sheet: str, direction: str | None) -> int:
url = f"/api/v1/form/{form_id}/sheet/{sheet}"
params = []
if direction:
params.append(f"direction={direction}")
if params:
url = f"{url}?{'&'.join(params)}"
response = client.get(url, headers=_auth_headers(admin_tokens))
assert response.status_code == 200
rows = response.json().get("result") or []
for row in rows:
data = row.get("data") or {}
if row.get("row_type") == "INPUT" and data.get("line_id"):
return int(data["line_id"])
pytest.skip("Не найден INPUT line_id для проверки upd_form_cell")
def test_backend_calls_v_form_view_via_sheet_endpoint(client, admin_tokens):
form_id, sheet, direction = _pick_form_and_sheet(client, admin_tokens)
url = f"/api/v1/form/{form_id}/sheet/{sheet}"
if direction:
url = f"{url}?direction={direction}"
response = client.get(url, headers=_auth_headers(admin_tokens))
assert response.status_code == 200
payload = response.json()
assert isinstance(payload.get("result"), list)
if payload["result"]:
first_row = payload["result"][0]
assert "row_type" in first_row
assert "data" in first_row
def test_backend_calls_upd_form_cell_and_maps_sql_validation_error(client, admin_tokens):
form_id, sheet, direction = _pick_form_and_sheet(client, admin_tokens)
line_id = _pick_input_line_id(client, admin_tokens, form_id, sheet, direction)
url = f"/api/v1/form/{form_id}/sheet/{sheet}/cell"
if direction:
url = f"{url}?direction={direction}"
response = client.patch(
url,
json={"line_id": line_id, "column": "bad.scope", "value": 1},
headers=_auth_headers(admin_tokens),
)
# SQL-функция отдает prefixed validation error.
assert response.status_code in (400, 422)
payload = response.json()
assert isinstance(payload, dict)
assert "message" in payload
assert isinstance(payload["message"], str)
def test_backend_calls_form3_add_project_and_add_line_functions(client, admin_tokens):
create = client.post(
"/api/v1/projects",
json={"name": f"ITEST_DB_FUNC_{uuid.uuid4().hex[:6]}", "year": 2026, "branch_id": 1},
headers=_auth_headers(admin_tokens),
)
assert create.status_code == 200
project_payload = create.json()
assert "project_id" in project_payload
assert "limit_report_id" in project_payload
assert "current_expenses_report_id" in project_payload
project_id = int(project_payload["project_id"])
add_line = client.post(
f"/api/v1/projects/{project_id}/report/2026/LIMIT/line",
json={"expense_item_id": 1},
headers=_auth_headers(admin_tokens),
)
assert add_line.status_code == 200
rows = add_line.json()
assert isinstance(rows, list)
def test_forms_validation_invalid_sections_returns_400(client, admin_tokens):
form_id, sheet = _pick_form_with_sheet(client, admin_tokens, "AHR")
response = client.get(
f"/api/v1/form/{form_id}/sheet/{sheet}?sections=bad_section",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 400
payload = response.json()
assert isinstance(payload, dict)
assert "message" in payload
def test_forms_validation_empty_sections_csv_is_ignored(client, admin_tokens):
form_id, sheet, direction = _pick_form_and_sheet(client, admin_tokens)
url = f"/api/v1/form/{form_id}/sheet/{sheet}?sections= , , "
if direction:
url += f"&direction={direction}"
response = client.get(url, headers=_auth_headers(admin_tokens))
assert response.status_code == 200
payload = response.json()
assert isinstance(payload.get("result"), list)
def test_forms_validation_direction_required_for_form1_ahr(client, admin_tokens):
form_id, sheet = _pick_form_with_sheet(client, admin_tokens, "AHR", form_type_code="FORM_1")
response = client.get(
f"/api/v1/form/{form_id}/sheet/{sheet}",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 400
payload = response.json()
assert isinstance(payload, dict)
assert "message" in payload
assert "direction" in payload["message"].lower()
def test_forms_validation_direction_case_sensitive(client, admin_tokens):
form_id, sheet = _pick_form_with_sheet(client, admin_tokens, "AHR", form_type_code="FORM_1")
response = client.get(
f"/api/v1/form/{form_id}/sheet/{sheet}?direction=support",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 422
def test_forms_validation_direction_forbidden_on_non_form1(client, admin_tokens):
form_id, sheet = _pick_form_with_sheet(client, admin_tokens, "AHR", form_type_code="FORM_2")
response = client.get(
f"/api/v1/form/{form_id}/sheet/{sheet}?direction=Support",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 400
payload = response.json()
assert isinstance(payload, dict)
assert "message" in payload
assert "direction" in payload["message"].lower()
def test_form3_validation_invalid_sections_returns_400(client, admin_tokens):
create = client.post(
"/api/v1/projects",
json={"name": f"ITEST_F3_SECT_{uuid.uuid4().hex[:6]}", "year": 2026, "branch_id": 1},
headers=_auth_headers(admin_tokens),
)
assert create.status_code == 200
project_id = int(create.json()["project_id"])
response = client.get(
f"/api/v1/projects/{project_id}/report/2026/LIMIT?sections=q1,q5",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 400
payload = response.json()
assert isinstance(payload, dict)
assert "message" in payload
def test_form3_validation_empty_sections_csv_is_ignored(client, admin_tokens):
create = client.post(
"/api/v1/projects",
json={"name": f"ITEST_F3_EMPTY_{uuid.uuid4().hex[:6]}", "year": 2026, "branch_id": 1},
headers=_auth_headers(admin_tokens),
)
assert create.status_code == 200
project_id = int(create.json()["project_id"])
response = client.get(
f"/api/v1/projects/{project_id}/report/2026/LIMIT?sections= , , ",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 200
assert isinstance(response.json().get("result"), list)
def test_form3_validation_duplicate_sections_is_allowed(client, admin_tokens):
create = client.post(
"/api/v1/projects",
json={"name": f"ITEST_F3_DUP_{uuid.uuid4().hex[:6]}", "year": 2026, "branch_id": 1},
headers=_auth_headers(admin_tokens),
)
assert create.status_code == 200
project_id = int(create.json()["project_id"])
response = client.get(
f"/api/v1/projects/{project_id}/report/2026/LIMIT?sections=q1,q1",
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 200
assert isinstance(response.json().get("result"), list)
def test_form3_add_project_invalid_name_returns_422(client, admin_tokens):
# Пробел запрещён regex-правилом AddProjectBody.
response = client.post(
"/api/v1/projects",
json={"name": "INVALID NAME", "year": 2026, "branch_id": 1},
headers=_auth_headers(admin_tokens),
)
assert response.status_code == 422
def test_health_and_ready_endpoints(client):
healthz = client.get("/healthz")
assert healthz.status_code == 200
health_payload = healthz.json()
assert health_payload.get("status") == "ok"
readyz = client.get("/readyz")
assert readyz.status_code in (200, 503)
ready_payload = readyz.json()
assert "status" in ready_payload
assert "db_status" in ready_payload
assert "mv_expense_item_tree" in ready_payload
def test_admin_refresh_tree_requires_admin_role(client, admin_tokens):
admin_resp = client.post(
"/api/v1/admin/refresh-tree",
headers=_auth_headers(admin_tokens),
)
assert admin_resp.status_code in (200, 503)
uname = f"itest_non_admin_{uuid.uuid4().hex[:6]}"
create_user = client.put(
"/api/v1/users/",
json={
"email": f"{uname}@example.com",
"username": uname,
"password": "pass123",
"full_name": "Integration User",
"role_id": 2,
},
headers=_auth_headers(admin_tokens),
)
if create_user.status_code != 200:
pytest.skip("Не удалось создать non-admin пользователя в текущей БД")
login_resp = client.post("/api/v1/auth/login", json={"username": uname, "password": "pass123"})
if login_resp.status_code != 200:
pytest.skip("Не удалось залогинить non-admin пользователя в текущей БД")
user_tokens = login_resp.json()
non_admin_resp = client.post(
"/api/v1/admin/refresh-tree",
headers=_auth_headers(user_tokens),
)
assert non_admin_resp.status_code == 403
def test_admin_refresh_tree_requires_auth(client):
response = client.post("/api/v1/admin/refresh-tree")
assert response.status_code in (401, 403)

View File

@ -21,8 +21,8 @@ def test_forms_sheets_not_found_error_shape(client, admin_tokens):
assert response.status_code == 404 assert response.status_code == 404
payload = response.json() payload = response.json()
assert isinstance(payload, dict) assert isinstance(payload, dict)
assert "detail" in payload assert "message" in payload
assert isinstance(payload["detail"], str) assert isinstance(payload["message"], str)
def test_forms_sheet_get_not_found_error_shape(client, admin_tokens): def test_forms_sheet_get_not_found_error_shape(client, admin_tokens):