fix(tests): реальный прогон интеграционных тестов СПОД против Postgres
Прогнал api/tests/integration/test_form2_ahr_satellite_spod_integration.py против живого Postgres (perf/perf/perf на localhost:55432, БД dfip_1906, alembic upgrade head + фикстура) — нашёл и поправил два бага самого теста (фичу/миграцию не трогал): - _find_row не учитывал служебную META-строку (editable/role/user_id без id/header), которую v3.v_form_view добавляет первой в любой лист — KeyError: 'id' на первой же выборке. Теперь ищет только среди INPUT-строк. - test_satellite_spod_phase_gates_non_admin_user делал один тест из "запрет без fact_q4.spod → добавили ключ → разрешено": неадминский 403/400 от isp1 внутри теста вызывает db_ref.rollback() в tests/integration/conftest.py (per-request откат на ошибке), который стирает все несохранённые изменения этого же теста — включая только что созданные строку сателлита и form_phase. Разбил на два независимых теста (test_satellite_spod_rejected_before_phase_includes_spod_column / test_satellite_spod_allowed_once_phase_includes_spod_column), в каждом ровно один запрос, которому разрешено провалиться. pytest tests/integration — 157 passed, 1 failed (pre-existing, не связан: test_backend_calls_form3_add_project_and_add_line_functions, FORM_3/add_project, 400 на /api/v1/projects/.../report/.../line), 4 skipped. Новый файл — 15/15. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
d41668dc27
commit
a5ffd476a6
@ -62,7 +62,12 @@ def _get_sheet_rows(client, admin_tokens, form2_id, sheet) -> list[dict]:
|
|||||||
|
|
||||||
|
|
||||||
def _find_row(rows: list[dict], predicate) -> dict:
|
def _find_row(rows: list[dict], predicate) -> dict:
|
||||||
|
# v_form_view prepends a META row ({"editable": [...], "role": ..., ...},
|
||||||
|
# row_type="META", depth=-1) with no "id"/"header" — only INPUT rows carry
|
||||||
|
# satellite/AHR line data.
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
if row.get("row_type") != "INPUT":
|
||||||
|
continue
|
||||||
if predicate(row["data"]):
|
if predicate(row["data"]):
|
||||||
return row
|
return row
|
||||||
raise AssertionError("строка не найдена")
|
raise AssertionError("строка не найдена")
|
||||||
@ -141,14 +146,22 @@ def test_satellite_spod_rejects_other_quarters(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("sheet", ["AHR_RENT", "AHR_UTILITY", "AHR_SECURITY"])
|
@pytest.mark.parametrize("sheet", ["AHR_RENT", "AHR_UTILITY", "AHR_SECURITY"])
|
||||||
def test_satellite_spod_phase_gates_non_admin_user(
|
def test_satellite_spod_rejected_before_phase_includes_spod_column(
|
||||||
client, admin_tokens, isp_tokens, form2_id, sheet
|
client, admin_tokens, isp_tokens, form2_id, sheet
|
||||||
):
|
):
|
||||||
"""DFIP-пользователь (isp1): без 'fact_q4.spod' в этапе — отказ,
|
"""DFIP-пользователь (isp1) с этапом, где column_keys ещё не включает
|
||||||
после появления ключа (как после data-миграции) — успех."""
|
'fact_q4.spod' (как до data-миграции): запись отклоняется.
|
||||||
|
|
||||||
|
Note: a failed request (403/400) makes the shared test-session's
|
||||||
|
`_get_db` override call `db_ref.rollback()`, which would wipe out any
|
||||||
|
uncommitted setup done earlier in the *same* test (the satellite line,
|
||||||
|
the phase row) — see conftest.py's `client` fixture. So the "before"
|
||||||
|
and "after" migration scenarios must be two separate tests, each
|
||||||
|
ending in exactly one request that is allowed to fail.
|
||||||
|
"""
|
||||||
line_id = _add_satellite_line(client, admin_tokens, form2_id, sheet)
|
line_id = _add_satellite_line(client, admin_tokens, form2_id, sheet)
|
||||||
|
|
||||||
phase_code = f"spod_test_{sheet.lower()}"
|
phase_code = f"spod_test_before_{sheet.lower()}"
|
||||||
create_body = {
|
create_body = {
|
||||||
"budget_form_id": form2_id,
|
"budget_form_id": form2_id,
|
||||||
"sheet": sheet,
|
"sheet": sheet,
|
||||||
@ -173,9 +186,28 @@ def test_satellite_spod_phase_gates_non_admin_user(
|
|||||||
)
|
)
|
||||||
assert response.status_code in (400, 403, 409), response.text
|
assert response.status_code in (400, 403, 409), response.text
|
||||||
|
|
||||||
response = client.patch(
|
|
||||||
f"/api/v1/stages/form/{form2_id}/{sheet}/{phase_code}",
|
@pytest.mark.parametrize("sheet", ["AHR_RENT", "AHR_UTILITY", "AHR_SECURITY"])
|
||||||
json={**create_body, "column_keys": ["fact_q4.dec", "fact_q4.spod"]},
|
def test_satellite_spod_allowed_once_phase_includes_spod_column(
|
||||||
|
client, admin_tokens, isp_tokens, form2_id, sheet
|
||||||
|
):
|
||||||
|
"""DFIP-пользователь (isp1) с этапом, где column_keys уже включает
|
||||||
|
'fact_q4.spod' (как после data-миграции из этого PR): запись проходит."""
|
||||||
|
line_id = _add_satellite_line(client, admin_tokens, form2_id, sheet)
|
||||||
|
|
||||||
|
phase_code = f"spod_test_after_{sheet.lower()}"
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1/stages/form/{form2_id}",
|
||||||
|
json={
|
||||||
|
"budget_form_id": form2_id,
|
||||||
|
"sheet": sheet,
|
||||||
|
"phase_code": phase_code,
|
||||||
|
"role": "DFIP",
|
||||||
|
"column_keys": ["fact_q4.dec", "fact_q4.spod"],
|
||||||
|
"opens_at": "2020-01-01T00:00:00",
|
||||||
|
"closes_at": "2099-01-01T00:00:00",
|
||||||
|
"direction": None,
|
||||||
|
},
|
||||||
headers=_auth_headers(admin_tokens),
|
headers=_auth_headers(admin_tokens),
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
@ -186,9 +218,3 @@ def test_satellite_spod_phase_gates_non_admin_user(
|
|||||||
headers=_auth_headers(isp_tokens),
|
headers=_auth_headers(isp_tokens),
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
|
|
||||||
response = client.delete(
|
|
||||||
f"/api/v1/stages/form/{form2_id}/{sheet}/{phase_code}",
|
|
||||||
headers=_auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, response.text
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user