"""B09 사용자가 고친 값 — 프로젝트 단위 저장·얹기·되돌리기 (PLAN 12장 2차 · 2026-09-14 브레인 판정). 겨누는 것 ① 고친 값은 **복사본에** 얹힘 — 기본 벌(캐시)은 그대로(골든셋·다른 프로젝트가 안 흔들림) ② 자재단가대비표 채택 슬롯 — 값 있는 슬롯만 받음 · 채택하면 금액이 그 슬롯 값으로 섬 ③ 지우면(↺) 계산값으로 돌아감 · 고친 값이 없으면 캐시 키가 빈 글 """ from __future__ import annotations from decimal import Decimal import pytest from B09_Estimation.B09_Estimation_Edits import ( EditError, apply_edits, edits_key, merge_changes, ) from B09_Estimation.B09_Estimation_PriceBook import PriceBook, PriceDetail, PriceKind, PriceTitle from B09_Estimation.B09_Estimation_UnitPrice import UnitPriceBuild def _build() -> UnitPriceBuild: book = PriceBook() slots = [Decimal(100), None, Decimal(90), None, None, Decimal(120)] book.add_title(PriceTitle("M-1", PriceKind.MATERIAL, "시멘트", slots=slots)) book.add_title(PriceTitle("B-1", PriceKind.UNIT_PRICE, "타설")) book.add_detail(PriceDetail("B-1", "M-1", Decimal(2))) return UnitPriceBuild(book=book) def test_채택_슬롯을_바꾸면_복사본에서만_금액이_바뀐다() -> None: base = _build() edits = merge_changes({}, base, [{"section": "adopted_slots", "key": "M-1", "value": "3"}]) assert edits == {"adopted_slots": {"M-1": 3}} edited = apply_edits(base, edits) assert edited.book.resolve("B-1").material == Decimal(180) # 90 × 2 assert base.book.resolve("B-1").material == Decimal(240) # 기본 벌은 6번 120 그대로 assert edited.edit_skipped == [] def test_값_없는_슬롯은_받지_않는다() -> None: with pytest.raises(EditError): merge_changes({}, _build(), [{"section": "adopted_slots", "key": "M-1", "value": 2}]) with pytest.raises(EditError): merge_changes({}, _build(), [{"section": "adopted_slots", "key": "B-1", "value": 1}]) def test_지우면_계산값으로_돌아가고_캐시_키가_빈다() -> None: base = _build() edits = merge_changes( {"adopted_slots": {"M-1": 1}}, base, [{"section": "adopted_slots", "key": "M-1", "value": None}], ) assert edits == {} and edits_key(edits) == "" assert edits_key({"adopted_slots": {"M-1": 1}}) != "" def test_단가표에서_사라진_코드는_버리지_않고_남긴다() -> None: edited = apply_edits(_build(), {"adopted_slots": {"M-없음": 1, "M-1": 2}}) assert len(edited.edit_skipped) == 2 assert edited.book.titles["M-1"].adopted_slot == 6