From ecef6eb247e8f40697846427694a84b41787a597 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 11 Nov 2026 21:06:53 +0400 Subject: [PATCH] =?UTF-8?q?fix(formulas):=20=D1=80=D0=B0=D1=81=D1=81=D1=8B?= =?UTF-8?q?=D0=BB=D0=BA=D0=B0=20WS=20=D0=BD=D0=B5=20=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D1=91=D1=82=20=D1=84=D0=BE=D1=80=D0=BC=D1=83=D0=BB=D1=83=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=B0=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=D0=B5=20=D0=B2=20=D0=B4=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=83=D0=BF=D0=B5=20(AURORA-1896,=20I-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- api/src/api/v1/websocket.py | 10 ++++++-- .../test_cell_formula_ws_integration.py | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/api/src/api/v1/websocket.py b/api/src/api/v1/websocket.py index 360f9d5..cc81078 100644 --- a/api/src/api/v1/websocket.py +++ b/api/src/api/v1/websocket.py @@ -538,6 +538,9 @@ class FormEventProcess: "value": any, } """ + client_formula = event_data.get("formula") + # Формула клиента не должна уйти в рассылку, даже если запись не состоится. + _apply_saved_formula(event_data, None) if not await self._check_form_access(form_id=form_id, user=user): return None @@ -549,7 +552,7 @@ class FormEventProcess: line_id=event_data["line_id"], column=event_data["column"], value=event_data["value"], - formula=event_data.get("formula"), + formula=client_formula, user=user, ) _apply_saved_formula(event_data, formula) @@ -774,6 +777,9 @@ class ProjectEventProcess: "value": any, } """ + client_formula = event_data.get("formula") + # Формула клиента не должна уйти в рассылку, даже если запись не состоится. + _apply_saved_formula(event_data, None) project = await self.project_service.get(project_id=project_id, user=user) if not project: return None @@ -785,7 +791,7 @@ class ProjectEventProcess: line_id=event_data["line_id"], column=event_data["column"], value=event_data["value"], - formula=event_data.get("formula"), + formula=client_formula, user=user, ) _apply_saved_formula(event_data, formula) diff --git a/api/tests/integration/test_cell_formula_ws_integration.py b/api/tests/integration/test_cell_formula_ws_integration.py index 06a3120..54a08ea 100644 --- a/api/tests/integration/test_cell_formula_ws_integration.py +++ b/api/tests/integration/test_cell_formula_ws_integration.py @@ -188,3 +188,28 @@ def test_ws_project_report_cell_updated_carries_saved_formula(client, admin_toke "DELETE FROM v3.rf_project_report_quarter WHERE rf_project_report_line_id = :l", commit=True, l=line_id) _db_scalar("DELETE FROM v3.rf_project_report_line WHERE id = :l", commit=True, l=line_id) + + +def test_ws_no_access_user_forged_formula_is_not_echoed(client, isp_tokens): + """I-1: пользователь без доступа к форме 1 (isp1) шлёт cell_updated с подделанной формулой: + в ответе/рассылке formula и formula_meta обнулены, строки cell_formula не появляется.""" + count_sql = ( + "SELECT count(*) FROM v3.cell_formula WHERE budget_form_id = 1 " + "AND line_id = 1 AND column_key = 'plan.q1'" + ) + before = _db_scalar(count_sql) + prior = _db_scalar(PLAN_Q1_SQL) + try: + with client.websocket_connect(WS_URL) as ws: + ws.send_json({"event": "user_login", "data": {"token": isp_tokens["access_token"]}}) + forged_meta = {"updated_by": {"id": 1, "full_name": "Forged"}, "updated_at": "2020-01-01T00:00:00"} + updated = _edit(ws, {"value": 777, "formula": "7*111", "formula_meta": forged_meta}) + assert not updated.get("error"), updated + data = updated.get("data", {}) + assert data.get("formula") is None, updated + assert data.get("formula_meta") is None, updated + assert _db_scalar(count_sql) == before + assert _db_scalar(PLAN_Q1_SQL) == prior + finally: + _cleanup_formula() + _restore_cell(prior)