unformatted: File would be reformatted --> /tmp/st_01a00a96/followup/copy.before.py:1:1 76 | if status.state.value == "collecting": 77 | return PROMPTS[status.next_field], None, "customer" 78 | if status.state.value == "customer_attestation": - return "입력 요약이 준비됐습니다. 본인이 입력한 내용임을 확인해 주세요.", "attest", "customer" 79 + return ( 80 + "입력 요약이 준비됐습니다. 본인이 입력한 내용임을 확인해 주세요.", 81 + "attest", 82 + "customer", 83 + ) 84 | if status.state.value == "owner_review": 85 | return "고객 영양 기초정보의 운영자 검토가 필요합니다.", "owner_ok", "owner" 86 | if status.state.value == "safety_hold": unformatted: File would be reformatted --> /tmp/st_01a00a96/followup/runtime.before.py:1:1 63 | self.profile_root = Path(profile_root).resolve() 64 | self.bootstrap = bootstrap_transport 65 | self.reconciler = reconciler or NutritionOnboardingReconciler() - self._publication_outbox = GatewayOnboardingPublicationOutbox( - self.profile_root - ) - self.domain = importlib.import_module( - "checkin_cli.nutrition_onboarding" - ) 66 + self._publication_outbox = GatewayOnboardingPublicationOutbox(self.profile_root) 67 + self.domain = importlib.import_module("checkin_cli.nutrition_onboarding") 68 | source_path = getattr(self.domain, "__file__", None) 69 | if not isinstance(source_path, str): 70 | raise NutritionOnboardingRuntimeError( -------------------------------------------------------------------------------- 108 | self.domain.record_current_registry_consent( 109 | self.profile_root, 110 | current.customer_key, - recorded_on=recorded_at.astimezone( - ZoneInfo("Asia/Seoul") - ).date(), 111 + recorded_on=recorded_at.astimezone(ZoneInfo("Asia/Seoul")).date(), 112 | ) 113 | except (OSError, TypeError, ValueError): - await query.answer( - text="현재 동의를 안전하게 저장하지 못했습니다." - ) 114 + await query.answer(text="현재 동의를 안전하게 저장하지 못했습니다.") 115 | return 116 | try: 117 | authority = self._current_authority(current) 118 | except (OSError, ValueError): - await query.answer( - text="온보딩 권한 또는 동의가 변경됐습니다." - ) 119 + await query.answer(text="온보딩 권한 또는 동의가 변경됐습니다.") 120 | return 121 | evidence = self._evidence( 122 | actor_id=getattr( -------------------------------------------------------------------------------- 255 | """Authenticate and receipt one owner commit whose UI recovered later.""" 256 | outbox = getattr(self, "_publication_outbox", None) 257 | ready_path = getattr(service, "ready_path", None) - baseline_path = ready_path.parent / "baseline-v1.json" if isinstance(ready_path, Path) else None 258 + baseline_path = ( 259 + ready_path.parent / "baseline-v1.json" 260 + if isinstance(ready_path, Path) 261 + else None 262 + ) 263 | callback = getattr( 264 | getattr(self, "adapter", None), 265 | "_reconcile_telegram_business_commit", -------------------------------------------------------------------------------- 345 | or event.action != "Approve" 346 | or event.actor_user_id != authority.owner_user_id 347 | or event.authority != authority_values - or event.route != tuple(str(item) for item in self._route(session, "owner")) 348 + or event.route 349 + != tuple(str(item) for item in self._route(session, "owner")) 350 | or event.callback_data != expected_callback_data 351 | ): 352 | return False -------------------------------------------------------------------------------- 431 | ) 432 | raw_answers = document.get("answers") 433 | if not isinstance(raw_answers, dict): - raise NutritionOnboardingRuntimeError( - "invalid onboarding answers" - ) 434 + raise NutritionOnboardingRuntimeError("invalid onboarding answers") 435 | answers: dict[str, object] = {} 436 | for key, value in raw_answers.items(): 437 | if not isinstance(key, str): - raise NutritionOnboardingRuntimeError( - "invalid onboarding answers" - ) 438 + raise NutritionOnboardingRuntimeError("invalid onboarding answers") 439 | answers[key] = value 440 | for field in fields[target_cursor:]: 441 | if not isinstance(field, str): unformatted: File would be reformatted --> /tmp/st_01a00a96/followup/test_runtime.before.py:1:1 156 | customer_key="customer_001", 157 | owner_id="103", 158 | chat_id="-100200", - role_claim=lambda role: ( - customer if role.value == "customer" else trainer - ), 159 + role_claim=lambda role: customer if role.value == "customer" else trainer, 160 | topic_id=lambda _slot: None, 161 | ) 162 | -------------------------------------------------------------------------------- 208 | {"evidence_id": "checkin.current", "text": "현재 확정 체크인"} 209 | ], 210 | "decision_guardrails": { - "action_options": [ - {"id": "maintain_plan", "text": "현재 계획 유지"} - ] 211 + "action_options": [{"id": "maintain_plan", "text": "현재 계획 유지"}] 212 | }, 213 | }, 214 | } -------------------------------------------------------------------------------- 355 | ) 356 | 357 | adapter._request_physique_coaching_stage = MagicMock(side_effect=coach_response) 358 + 359 | async def polish_copy(*_args, artifact_sink=None, **_kwargs): 360 | assert artifact_sink is not None - artifact_sink( - { - "raw_polish_output": '{"slots":[]}', - "polish_valid": True, - } - ) 361 + artifact_sink({ 362 + "raw_polish_output": '{"slots":[]}', 363 + "polish_valid": True, 364 + }) 365 | return ( 366 | "현재 자료를 함께 살펴보면 하루 목표를 2000kcal로 조정하는 안을 " 367 | "운영자와 먼저 확인하겠습니다." -------------------------------------------------------------------------------- 465 | adapter._physique_chat_id = lambda *_args: "100" 466 | adapter._physique_thread_id = lambda *_args: "59" 467 | message = SimpleNamespace( - text=( - "목표 2100 170 205 65\n" - "하루 목표 2100kcal 조정안을 확인하겠습니다." - ) 468 + text=("목표 2100 170 205 65\n하루 목표 2100kcal 조정안을 확인하겠습니다.") 469 | ) 470 | 471 | handled = await adapter._handle_nutrition_draft_edit_text( -------------------------------------------------------------------------------- 526 | payload={"body_digest": "owner-card", "state": "owner_review"}, 527 | ) 528 | - assert publication_generation( - ready, - current=current, - payload={"body_digest": "ready-card", "state": "ready"}, - ) == 24 529 + assert ( 530 + publication_generation( 531 + ready, 532 + current=current, 533 + payload={"body_digest": "ready-card", "state": "ready"}, 534 + ) 535 + == 24 536 + ) 537 | 538 | 539 | def test_ready_card_announces_activation_readiness_to_owner() -> None: -------------------------------------------------------------------------------- 555 | 556 | 557 | @pytest.mark.asyncio - async def test_consent_withdrawal_text_short_circuits_handle_text_without_bootstrap_lookup() -> None: 558 + async def test_consent_withdrawal_text_short_circuits_handle_text_without_bootstrap_lookup() -> ( 559 + None 560 + ): 561 | runtime = object.__new__(TelegramNutritionOnboardingRuntime) 562 | runtime.bootstrap = SimpleNamespace( 563 | store=SimpleNamespace( -------------------------------------------------------------------------------- 705 | state_dir = tmp_path / "data/onboarding/telegram-publication-outbox-v1" 706 | state_dir.mkdir(parents=True, mode=0o700) 707 | legacy = state_dir / "ledger.json" - legacy_document = json.dumps( - { - "schema": "telegram-nutrition-onboarding-publication-outbox-v1", - "records": [ - { - "session_id": "session-1", - "generation": 0, - "payload": {"state": "collecting"}, - "state": "RECEIPTED", - "message_id": 901, - } - ], - } - ) 708 + legacy_document = json.dumps({ 709 + "schema": "telegram-nutrition-onboarding-publication-outbox-v1", 710 + "records": [ 711 + { 712 + "session_id": "session-1", 713 + "generation": 0, 714 + "payload": {"state": "collecting"}, 715 + "state": "RECEIPTED", 716 + "message_id": 901, 717 + } 718 + ], 719 + }) 720 | legacy.write_text(legacy_document, encoding="utf-8") 721 | legacy.chmod(0o600) 722 | -------------------------------------------------------------------------------- 808 | monkeypatch: pytest.MonkeyPatch, 809 | ) -> None: 810 | outbox = GatewayOnboardingPublicationOutbox(tmp_path) - monkeypatch.setattr(outbox, "record_receipt", Mock(side_effect=OSError("primary down"))) 811 + monkeypatch.setattr( 812 + outbox, "record_receipt", Mock(side_effect=OSError("primary down")) 813 + ) 814 | runtime = object.__new__(TelegramNutritionOnboardingRuntime) 815 | runtime._publication_outbox = outbox 816 | runtime.adapter = SimpleNamespace( -------------------------------------------------------------------------------- 827 | await runtime._send_publication( 828 | session=SimpleNamespace(session_id="session-1", sid_hash="a" * 64), 829 | service=SimpleNamespace(store=store), - status=SimpleNamespace(state=SimpleNamespace(value="collecting"), answer_count=0), 830 + status=SimpleNamespace( 831 + state=SimpleNamespace(value="collecting"), answer_count=0 832 + ), 833 | text="current onboarding card", 834 | role="customer", 835 | payload={"body_digest": "b" * 64, "state": "collecting"}, -------------------------------------------------------------------------------- 850 | monkeypatch: pytest.MonkeyPatch, 851 | ) -> None: 852 | outbox = GatewayOnboardingPublicationOutbox(tmp_path) - monkeypatch.setattr(outbox, "record_receipt", Mock(side_effect=OSError("primary down"))) 853 + monkeypatch.setattr( 854 + outbox, "record_receipt", Mock(side_effect=OSError("primary down")) 855 + ) 856 | first = object.__new__(TelegramNutritionOnboardingRuntime) 857 | first._publication_outbox = outbox 858 | first.adapter = SimpleNamespace( -------------------------------------------------------------------------------- 871 | await first._send_publication( 872 | session=SimpleNamespace(session_id="session-1", sid_hash="a" * 64), 873 | service=SimpleNamespace(store=first_store), - status=SimpleNamespace(state=SimpleNamespace(value="collecting"), answer_count=0), 874 + status=SimpleNamespace( 875 + state=SimpleNamespace(value="collecting"), answer_count=0 876 + ), 877 | text="current onboarding card", 878 | role="customer", 879 | payload=payload, -------------------------------------------------------------------------------- 896 | mark_committed=Mock(), 897 | ) 898 | - assert await restarted._recover_publication_receipts(SimpleNamespace(store=restarted_store)) == 1 899 + assert ( 900 + await restarted._recover_publication_receipts( 901 + SimpleNamespace(store=restarted_store) 902 + ) 903 + == 1 904 + ) 905 | restarted_store.mark_committed.assert_called_once_with( 906 | session_id="session-1", generation=0, message_id=901 907 | ) - assert GatewayOnboardingPublicationOutbox(tmp_path).records()[0].state == "COMMITTED" 908 + assert ( 909 + GatewayOnboardingPublicationOutbox(tmp_path).records()[0].state == "COMMITTED" 910 + ) 911 | 912 | 913 | @pytest.mark.asyncio -------------------------------------------------------------------------------- 916 | monkeypatch: pytest.MonkeyPatch, 917 | ) -> None: 918 | outbox = GatewayOnboardingPublicationOutbox(tmp_path) - monkeypatch.setattr(outbox, "record_receipt", Mock(side_effect=OSError("primary down"))) 919 | monkeypatch.setattr( 920 + outbox, "record_receipt", Mock(side_effect=OSError("primary down")) 921 + ) 922 + monkeypatch.setattr( 923 | outbox, 924 | "record_emergency_receipt", 925 | Mock(side_effect=OSError("emergency down")), -------------------------------------------------------------------------------- 938 | mark_committed=Mock(), 939 | ) 940 | - with pytest.raises(PublicationReceiptPersistenceError, match="primary and emergency"): 941 + with pytest.raises( 942 + PublicationReceiptPersistenceError, match="primary and emergency" 943 + ): 944 | await runtime._send_publication( 945 | session=SimpleNamespace(session_id="session-1", sid_hash="a" * 64), 946 | service=SimpleNamespace(store=store), - status=SimpleNamespace(state=SimpleNamespace(value="collecting"), answer_count=0), 947 + status=SimpleNamespace( 948 + state=SimpleNamespace(value="collecting"), answer_count=0 949 + ), 950 | text="current onboarding card", 951 | role="customer", 952 | payload={"body_digest": "b" * 64, "state": "collecting"}, -------------------------------------------------------------------------------- 993 | mark_committed=Mock(), 994 | ) 995 | - assert await runtime._recover_publication_receipts(SimpleNamespace(store=store)) == 0 996 + assert ( 997 + await runtime._recover_publication_receipts(SimpleNamespace(store=store)) == 0 998 + ) 999 | store.mark_committed.assert_not_called() 1000 | 1001 | -------------------------------------------------------------------------------- 1034 | mark_committed=Mock(), 1035 | ) 1036 | - assert await runtime._recover_publication_receipts( - SimpleNamespace(store=store), - session=SimpleNamespace(session_id="session-1"), - ) == 0 1037 + assert ( 1038 + await runtime._recover_publication_receipts( 1039 + SimpleNamespace(store=store), 1040 + session=SimpleNamespace(session_id="session-1"), 1041 + ) 1042 + == 0 1043 + ) 1044 | store.mark_committed.assert_not_called() 1045 | assert runtime._publication_outbox.records()[0].state == "RECEIPTED" 1046 | -------------------------------------------------------------------------------- 1066 | await runtime._send_publication( 1067 | session=SimpleNamespace(session_id="session-1", sid_hash="a" * 64), 1068 | service=SimpleNamespace(store=store), - status=SimpleNamespace(state=SimpleNamespace(value="collecting"), answer_count=0), 1069 + status=SimpleNamespace( 1070 + state=SimpleNamespace(value="collecting"), answer_count=0 1071 + ), 1072 | text="current onboarding card", 1073 | role="customer", 1074 | payload={"body_digest": "b" * 64, "state": "collecting"}, -------------------------------------------------------------------------------- 1138 | first = object.__new__(TelegramNutritionOnboardingRuntime) 1139 | first._publication_outbox = GatewayOnboardingPublicationOutbox(tmp_path) 1140 | first.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=901) - ) 1141 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=901)) 1142 | ) 1143 | first._route = Mock(return_value=("-100", "20")) 1144 | first_store = SimpleNamespace( -------------------------------------------------------------------------------- 1188 | generation=0, 1189 | message_id=901, 1190 | ) - assert GatewayOnboardingPublicationOutbox(tmp_path).records()[0].state == "COMMITTED" 1191 + assert ( 1192 + GatewayOnboardingPublicationOutbox(tmp_path).records()[0].state == "COMMITTED" 1193 + ) 1194 | restarted.adapter = SimpleNamespace( 1195 | _send_nutrition_topic=AsyncMock( 1196 | side_effect=AssertionError("committed card was sent twice") -------------------------------------------------------------------------------- 1234 | ) 1235 | runtime._publication_outbox = GatewayOnboardingPublicationOutbox(tmp_path) 1236 | service = SimpleNamespace( - status=Mock(return_value=SimpleNamespace(state=SimpleNamespace(value="collecting"))), 1237 + status=Mock( 1238 + return_value=SimpleNamespace(state=SimpleNamespace(value="collecting")) 1239 + ), 1240 | store=SimpleNamespace(mark_committed=Mock()), 1241 | ) 1242 | runtime._service = Mock(return_value=service) -------------------------------------------------------------------------------- 1256 | async def test_optional_question_force_reply_preserves_skip_affordance() -> None: 1257 | runtime = object.__new__(TelegramNutritionOnboardingRuntime) 1258 | runtime.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=901) - ) 1259 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=901)) 1260 | ) 1261 | runtime._route = Mock(return_value=("-100", "20")) 1262 | store = SimpleNamespace( -------------------------------------------------------------------------------- 1293 | async def test_required_question_never_offers_skip() -> None: 1294 | runtime = object.__new__(TelegramNutritionOnboardingRuntime) 1295 | runtime.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=902) - ) 1296 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=902)) 1297 | ) 1298 | runtime._route = Mock(return_value=("-100", "20")) 1299 | service = SimpleNamespace( -------------------------------------------------------------------------------- 1339 | ) 1340 | ) 1341 | runtime.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=903) - ) 1342 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=903)) 1343 | ) 1344 | runtime._current_authority = Mock( 1345 | return_value=SimpleNamespace(consent_granted=True) -------------------------------------------------------------------------------- 1380 | 1381 | await runtime._publish(session, service, status) 1382 | - keyboard = runtime.adapter._send_nutrition_topic.await_args.kwargs[ - "reply_markup" - ] 1383 + keyboard = runtime.adapter._send_nutrition_topic.await_args.kwargs["reply_markup"] 1384 | buttons = [button for row in keyboard.inline_keyboard for button in row] 1385 | assert [button.text for button in buttons] == [ 1386 | "입력 내용이 맞습니다", 1387 | "수정", 1388 | ] - assert [_load().decode_callback(button.callback_data).action for button in buttons] == [ 1389 + assert [ 1390 + _load().decode_callback(button.callback_data).action for button in buttons 1391 + ] == [ 1392 | "attest", 1393 | "revise", 1394 | ] -------------------------------------------------------------------------------- 1409 | reconcile=AsyncMock(return_value=reconciliation) 1410 | ) 1411 | runtime.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=904) - ) 1412 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=904)) 1413 | ) 1414 | runtime._current_authority = Mock( 1415 | return_value=SimpleNamespace(consent_granted=True) -------------------------------------------------------------------------------- 1561 | reconcile=AsyncMock(return_value=reconciliation) 1562 | ) 1563 | runtime.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=905) - ) 1564 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=905)) 1565 | ) 1566 | runtime._current_authority = Mock(return_value=authority) 1567 | runtime._route = Mock(return_value=("-100", "20")) -------------------------------------------------------------------------------- 1577 | assert reconciliation.summary_ko in sent["text"] 1578 | assert persisted is not None 1579 | assert persisted["answers_digest"] == current_digest - assert ( - persisted["advisory"]["summary_ko"] - == reconciliation.summary_ko - ) 1580 + assert persisted["advisory"]["summary_ko"] == reconciliation.summary_ko 1581 | 1582 | 1583 | @pytest.mark.asyncio 1584 | @pytest.mark.parametrize( 1585 | "state,labels,actions", - (( - "owner_review", - ["Approve", "Revise", "Reject", "Safety Hold"], - ["owner_ok", "op_rev", "op_rej", "op_hold"], - ),), 1586 + ( 1587 + ( 1588 + "owner_review", 1589 + ["Approve", "Revise", "Reject", "Safety Hold"], 1590 + ["owner_ok", "op_rev", "op_rej", "op_hold"], 1591 + ), 1592 + ), 1593 | ) 1594 | async def test_review_cards_offer_all_canonical_actions( 1595 | state: str, -------------------------------------------------------------------------------- 1598 | ) -> None: 1599 | runtime = object.__new__(TelegramNutritionOnboardingRuntime) 1600 | runtime.adapter = SimpleNamespace( - _send_nutrition_topic=AsyncMock( - return_value=SimpleNamespace(message_id=904) - ) 1601 + _send_nutrition_topic=AsyncMock(return_value=SimpleNamespace(message_id=904)) 1602 | ) 1603 | runtime._route = Mock(return_value=("-100", "20")) 1604 | service = SimpleNamespace( -------------------------------------------------------------------------------- 1618 | 1619 | await runtime._publish(session, service, status) 1620 | - keyboard = runtime.adapter._send_nutrition_topic.await_args.kwargs[ - "reply_markup" - ] 1621 + keyboard = runtime.adapter._send_nutrition_topic.await_args.kwargs["reply_markup"] 1622 | buttons = [button for row in keyboard.inline_keyboard for button in row] 1623 | assert [button.text for button in buttons] == labels 1624 | assert [ - _load().decode_callback(button.callback_data).action - for button in buttons 1625 + _load().decode_callback(button.callback_data).action for button in buttons 1626 | ] == actions - assert all( - len(button.callback_data.encode("utf-8")) <= 64 - for button in buttons - ) 1627 + assert all(len(button.callback_data.encode("utf-8")) <= 64 for button in buttons) 1628 | 1629 | 1630 | @dataclass -------------------------------------------------------------------------------- 1649 | *, 1650 | reply_markup: object | None = None, 1651 | ) -> object: - return await self.edit_message_reply_markup_mock( - reply_markup=reply_markup - ) 1652 + return await self.edit_message_reply_markup_mock(reply_markup=reply_markup) 1653 | 1654 | 1655 | @dataclass -------------------------------------------------------------------------------- 1691 | return_value=SimpleNamespace(generation=24, message_id=905) 1692 | ) 1693 | ), - status=Mock( - return_value=SimpleNamespace(state=SimpleNamespace(value=state)) - ), 1694 + status=Mock(return_value=SimpleNamespace(state=SimpleNamespace(value=state))), 1695 | review_as_trainer=Mock(return_value=result), 1696 | review_as_owner=Mock(return_value=result), 1697 | ) -------------------------------------------------------------------------------- 1802 | 1803 | 1804 | @pytest.mark.asyncio - async def test_historical_trainer_callback_is_rejected_before_route_or_service_mutation() -> None: 1805 + async def test_historical_trainer_callback_is_rejected_before_route_or_service_mutation() -> ( 1806 + None 1807 + ): 1808 | runtime, service, query, message = _review_callback_runtime( 1809 | state="trainer_review", 1810 | result_state="owner_review", 1811 | ) - data = _load().encode_callback_hash( - action="train_ok", - generation=23, - sid_hash="a" * 64, - ).replace("non2:", "non1:", 1) 1812 + data = ( 1813 + _load() 1814 + .encode_callback_hash( 1815 + action="train_ok", 1816 + generation=23, 1817 + sid_hash="a" * 64, 1818 + ) 1819 + .replace("non2:", "non1:", 1) 1820 + ) 1821 | 1822 | await runtime.handle_callback(query, data, message) 1823 | -------------------------------------------------------------------------------- 1910 | service = SimpleNamespace( 1911 | status=Mock(return_value=status), 1912 | store=SimpleNamespace( - load_session=Mock( - return_value=SimpleNamespace(message_id=901, payload={}) - ) 1913 + load_session=Mock(return_value=SimpleNamespace(message_id=901, payload={})) 1914 | ), 1915 | submit_answer=Mock(return_value=next_status), 1916 | ) 3 files would be reformatted, 1 file already formatted