Files
Aislo/resources/tester/test_b06_cut_slope_user_input.py
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

151 lines
7.1 KiB
Python

"""절토 경사를 **사용자가 직접 넣는** 길이 끊기지 않는지 (2026-09-07 사용자 지시).
사용자 원문 — 「대신 경고부분은 삭제해주고 대신 각도를 사용자가 넣을수 있게 반영. 전체
공통으로 변경하는 경우에는 기본값 지정으로 하면 되지만 횡단도 하나만 변경하는 폼은 가져야함」
및 「개별 횡단도에는 암 절토 각도의 개별 수정 가능해야함 / 전체 변경을 위해서는 좌측 패널의
표준 횡단면 설정을 이용. (표준 횡단면 설정으로 변경시 사용자가 기본값을 사용하지 않는 값들은
변경되면 안됨.)」
⚠ **경사는 「나르는 값」이 아니라 「기하 입력」이다.** 계산이 끝난 결과에 키만 베껴 붙이면
설계선은 옛 경사로 그려지고 숫자만 새것이 되어 어긋난다 — 소단에서 겪은 자리와 같다.
그래서 여기서는 ① 값이 실제로 **계산에 들어가** 절토 면적이 달라지는지 ② 부르는 자리마다
계산 **전에** 넘어가는지를 함께 지킨다.
"""
import re
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
from B06_Section.B06_Section_Router_Design import ( # noqa: E402
USER_TOUCHED_KEYS,
stored_cut_slope,
)
from config.config_system import STANDARD_CROSS_SECTION # noqa: E402
_B06 = PROJECT_ROOT / "B06_Section"
_REFRESH = _B06 / "B06_Section_Cross_Refresh.ts"
_CARD = _B06 / "B06_Section_UI_Cross_CutSlope.ts"
_CHROME = _B06 / "B06_Section_UI_Cross_Card_Chrome.ts"
_ROUTER_DESIGN = _B06 / "B06_Section_Router_Design.py"
def _ground(slope: float) -> list[dict]:
"""가로 -12~+12m 를 0.5m 간격으로 훑은 비탈 지반선."""
samples = []
offset = -12.0
while offset <= 12.0001:
samples.append(
{
"offset_m": round(offset, 3),
"elevation_m": round(100.0 + slope * offset, 4),
"valid": True,
}
)
offset += 0.5
return samples
def _design(**options):
return compute_cross_design(
_ground(-0.5),
99.8,
ground_type="ripping_rock",
section_mode="left_cut",
ditch_side="left",
standard=STANDARD_CROSS_SECTION,
rock_boundary_offset_m=-1.0,
**options,
)
def test_넣은_경사가_실제로_그려진다():
"""표준 1:0.4 → 사용자 1:1.0. 경사비도 절토 면적도 함께 달라져야 한다."""
base = _design()
changed = _design(cut_slope_ratio=1.0)
assert base["cut_slope_ratio"] == 0.4, "표준 절토 경사가 바뀐 듯 — 전제 확인 필요"
assert changed["cut_slope_ratio"] == 1.0, "넣은 경사가 설계에 안 들어감"
# 사면이 눕는 만큼 더 파므로 절토 면적이 커진다 — 숫자가 그대로면 그림만 바뀐 것이다.
assert changed["cut_area_m2"] > base["cut_area_m2"], "경사를 눕혔는데 절토량이 그대로임"
def test_무릎_위_토사_경사는_그대로다():
"""사용자가 넣는 것은 **암 절토각**이다 — 무릎 위(토사)는 표준값 그대로 간다."""
changed = _design(cut_slope_ratio=1.0, two_stage_slope=True)
assert changed["soil_cut_slope_ratio"] == STANDARD_CROSS_SECTION["soil"]["cut_slope_ratio"]
def test_0_은_표준값으로_되돌림이다():
"""되돌리기(↺)는 세션에서 지우는 게 아니라 **0 을 남긴다** — 지우면 정본의 옛 값이
되살아나 표준으로 못 돌아간다."""
assert stored_cut_slope({"cut_slope_ratio_user": 0}) is None
assert stored_cut_slope({"cut_slope_ratio_user": 0.8}) == 0.8
assert stored_cut_slope({}) is None
def test_다시_계산해도_사용자_값이_남는다():
"""표준 횡단면 설정을 바꿔도 개별로 고친 측점은 그대로 (사용자 원문 끝줄)."""
assert "cut_slope_ratio_user" in USER_TOUCHED_KEYS
assert '"cut_slope_ratio_user"' in _REFRESH.read_text(encoding="utf-8")
def test_계산_전에_넘어간다():
"""⚠ 이 시험이 이 건의 요지다 — 값을 계산 **인자**로 넘기는지.
재계산 경로(포장 강제·세월교 하강·선형 재계산)가 모두 인자로 받아야 한다. 한 곳이라도
빠지면 그 경로에서만 옛 경사로 그려지고 값만 새것이 된다.
"""
router = _ROUTER_DESIGN.read_text(encoding="utf-8")
assert router.count("cut_slope_ratio=") >= 3, "재계산 경로 중 값을 안 넘기는 곳이 있음"
refresh = _REFRESH.read_text(encoding="utf-8")
assert "cutSlopeRatio: cutSlopeAt(" in refresh, "브라우저 재계산이 값을 안 넘김"
# 계산이 끝난 뒤 베껴 붙이는 꼴이면 안 된다.
assert not re.search(r"design\.cut_slope_ratio\s*=", refresh), "계산 뒤에 값만 덮어쓰고 있음"
def test_각도와_경사비가_서로_바뀐다():
"""화면은 각도(°), 속은 경사비(1:n). 1:0.4 = 68.2° · 1:1.0 = 45°."""
source = _CARD.read_text(encoding="utf-8")
assert "ratioToDegrees" in source and "degreesToRatio" in source
# 45°는 1:1 — 두 함수가 서로의 역이어야 한다(코드에 그 식이 있는지).
assert "Math.atan(1 / Math.max(ratio, 1e-6))" in source
assert "1 / Math.tan((clamped * Math.PI) / 180)" in source
def test_칸은_암_측점에만_선다():
"""토사 측점은 암반이 없어 암 절토각이 쓰이지 않는다(2026-09-07 사용자 확정)."""
chrome = _CHROME.read_text(encoding="utf-8")
assert 'cutSlope && section.design?.geometry_preset === "rock"' in chrome
# ── 지반유형 버튼 정리 (2026-09-07 사용자 확정) ────────────────────────────────
# 「리핑암과 발파암 버튼을 삭제하면서 구분의 의미가 없어졌어. … 토사버튼만 존재하고 이값은
# 활성화/비활성화로 반영(기본값은 비활성화)」 — 켜면 토사, 끄면 암이다.
_DESIGN_UI = _B06 / "B06_Section_UI_Cross_Design.ts"
def test_지반유형은_토사_토글_하나다():
source = _DESIGN_UI.read_text(encoding="utf-8")
assert "GROUND_OPTIONS" not in source, "지반유형 버튼 셋이 남아 있음"
assert "groundToggle" in source, "토사 토글이 없음"
# 저장값은 종전 그대로 — 끔 = ripping_rock, 켬 = soil.
assert 'GROUND_ROCK_DEFAULT: GroundType = "ripping_rock"' in source
def test_기본은_암이다():
"""사용자 원문 「기본값은 비활성화」 — 설계가 없는 측점도 암으로 선다."""
source = _DESIGN_UI.read_text(encoding="utf-8")
assert "design?.ground_type ?? GROUND_ROCK_DEFAULT" in source
def test_옛_발파암_자료도_암으로_읽힌다():
"""버튼은 없앴지만 저장분에 남은 `blasting_rock` 은 그대로 암 기하로 계산된다."""
from config.config_system_design import SECTION_GROUND_TYPE_PRESET
assert SECTION_GROUND_TYPE_PRESET["blasting_rock"] == "rock"
assert SECTION_GROUND_TYPE_PRESET["ripping_rock"] == "rock"