Files
Aislo/resources/tester/test_384_s_area_keys.py
T
eomsangdonandClaude Opus 5.5 23c6645839 fix(388-2 보안): [확정] 면적 · 관 길이 = 서버 셈만 · 재계산 실패 경고
- 화면 [저장] · [확정]이 면적 · 관 길이 9칸을 안 보냄
- 서버가 받아도 _AREA_KEYS 걷음(옛 화면 · 위조 값 정본에 안 남음)
- 서버 재계산 실패(상세 못 받음 · Node 실패)를 삼키지 않음 → 응답 recomputed:false
- 화면 경고 한 줄(저장은 됨 · 옛 서버 값 남음 · 다시 [저장])
- 새 test_384_s_area_keys · 옛 시험 셋 새 동작에 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
2026-10-17 17:21:38 +09:00

126 lines
4.9 KiB
Python

"""388-2 [저장] · [확정] 면적 · 관 길이 = 서버 셈만(요구 242 · 분석 1장 S3).
- 브라우저가 면적 · 관 길이 9칸(`_AREA_KEYS`)을 보내도 정본에 안 적음 · 다른 칸은 그대로
- 서버 재계산 실패를 삼키지 않음 — 상세 못 받음 · Node 실패 = 던짐 → 응답 `recomputed: false`
- 화면은 면적 · 관 길이를 안 보냄 · `recomputed: false` 면 경고 한 줄
"""
from __future__ import annotations
import asyncio
from pathlib import Path
from types import SimpleNamespace
from uuid import uuid4
import pytest
import B06_Section.B06_Section_Router_Confirm as confirm
import B06_Section.B06_Section_Server_Calc_Prebuild as prebuild
from B06_Section.B06_Section_Schema import SectionConfirmRequest, SectionConfirmResponse
ROOT = Path(__file__).resolve().parents[2]
def test_면적_보내도_정본에_안_적음(monkeypatch):
written: list = []
async def merge(connection, *, route_id, entries, replace, project_id=None):
written.append((replace, entries))
return 0
monkeypatch.setattr(confirm, "merge_cross_section_designs", merge)
forged = {key: 999.0 for key in prebuild._AREA_KEYS}
request = SectionConfirmRequest(
cross_patches=[{"chainage_m": 20.0, "ground_type": "rock", **forged}]
)
asyncio.run(confirm._apply_section_edits(None, 3, request, []))
patches = written[-1][1]
assert patches == [(20.0, {"ground_type": "rock"})]
def test_면적만_보낸_측점은_patch_없음(monkeypatch):
written: list = []
async def merge(connection, *, route_id, entries, replace, project_id=None):
written.append(entries)
return 0
monkeypatch.setattr(confirm, "merge_cross_section_designs", merge)
request = SectionConfirmRequest(cross_patches=[{"chainage_m": 40.0, "cut_area_m2": 12.5}])
asyncio.run(confirm._apply_section_edits(None, 3, request, []))
assert written[-1] == []
def test_재계산_실패는_False(monkeypatch):
async def boom(project_id, route_id):
raise RuntimeError("Node 실패")
async def ok(project_id, route_id):
return 1
monkeypatch.setattr(prebuild, "recompute_server_side", boom)
assert asyncio.run(confirm._recompute_stored_designs(uuid4(), 3)) is False
monkeypatch.setattr(prebuild, "recompute_server_side", ok)
assert asyncio.run(confirm._recompute_stored_designs(uuid4(), 3)) is True
def test_상세_못_받으면_던짐(monkeypatch):
import B06_Section.B06_Section_Router as b06_router
async def no_detail(project_id, route_id):
return SimpleNamespace() # model_dump 없음 = JSONResponse 실패
async def run_with(fn, *args):
return None
async def nothing(project_id):
return {}
monkeypatch.setattr(b06_router, "get_section_detail", no_detail)
monkeypatch.setattr(prebuild, "run_with_connection", run_with)
monkeypatch.setattr(prebuild, "haul_inputs_for", nothing)
with pytest.raises(RuntimeError):
asyncio.run(prebuild.recompute_server_side(uuid4(), 3))
def test_Node_실패면_던짐(monkeypatch, tmp_path):
import B06_Section.B06_Section_Router as b06_router
import B06_Section.B06_Section_Router_Design as b06_design
detail = {"cross_sections": [{"chainage_m": 0.0, "design": {}}], "longitudinal": {}}
async def got(project_id, route_id):
return SimpleNamespace(model_dump=lambda mode: detail)
async def run_with(fn, *args):
return "p" if fn is prebuild.get_project_storage_relative_path else {}
async def nothing(project_id):
return {}
monkeypatch.setattr(b06_router, "get_section_detail", got)
monkeypatch.setattr(prebuild, "run_with_connection", run_with)
monkeypatch.setattr(prebuild, "haul_inputs_for", nothing)
monkeypatch.setattr(prebuild, "resolve_stored_project_path", lambda path: str(tmp_path))
monkeypatch.setattr(b06_design, "enforce_design_lines", lambda *a: None)
monkeypatch.setattr(prebuild, "_enforce_stored_designs", lambda *a: None)
monkeypatch.setattr(prebuild, "quantity_settings", lambda root: {})
monkeypatch.setattr(prebuild, "mixed_conversion_factors", lambda settings: {})
monkeypatch.setattr(prebuild, "haul_equipment_limits", lambda settings: [])
monkeypatch.setattr(prebuild, "_node_output", lambda node_input: None)
with pytest.raises(RuntimeError):
asyncio.run(prebuild.recompute_server_side(uuid4(), 3))
def test_응답에_recomputed():
assert SectionConfirmResponse(project_id="p", route_id=1).recomputed is True
assert SectionConfirmResponse(project_id="p", route_id=1, recomputed=False).recomputed is False
def test_화면은_면적_안_보내고_경고():
persist = (ROOT / "B06_Section/B06_Section_UI_Page_Persist.ts").read_text(encoding="utf-8")
assert "STRUCTURE_ROW_KEYS" not in persist
api = (ROOT / "B06_Section/B06_Section_Api_Fetch.ts").read_text(encoding="utf-8")
assert api.count("return warnUnrecomputed(") == 2
assert "result.recomputed === false" in api