Compare commits

...

3 commits

Author SHA1 Message Date
Jeffrey (Dongkyu) Kim
97410cc7a2 Tighten KTX train-type regression tests
The Issue #171 follow-up already locked the train-type CLI behavior. This pass addresses the remaining review cleanup in the modified test file by narrowing the normalized train_id before reuse and tidying formatting without changing behavior.

Constraint: Keep the PR scoped to ktx-booking regression coverage and documentation

Rejected: Encode train_type into train_id in this follow-up | broader selector migration is outside the approved regression scope

Confidence: high

Scope-risk: narrow

Tested: PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking

Tested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py

Tested: pyright scripts/test_ktx_booking.py

Tested: npm run lint

Tested: npm run typecheck

Tested: npm test

Not-tested: live Korail search/reserve requiring credentials and external service availability
2026-04-30 01:54:08 +09:00
Jeffrey (Dongkyu) Kim
87cd3ae34f Guard KTX search default during train-type regression
Issue #171 locked explicit train-type choices for Korail search and reserve flows. The follow-up review found reserve default coverage but no direct search default assertion, so this adds the narrow parser regression without changing runtime behavior.\n\nConstraint: Issue #171 requires search and reserve train-type behavior to stay regression-covered.\nRejected: Broaden command-level tests for every train type | parser choices already loop over TRAIN_TYPE_MAP and command forwarding is covered for the non-KTX regression route.\nConfidence: high\nScope-risk: narrow\nTested: PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking\nTested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py\nTested: npm run lint\nTested: npm run typecheck\nTested: npm test\nNot-tested: live Korail search/reserve requiring credentials and external service availability
2026-04-30 01:42:59 +09:00
Jeffrey (Dongkyu) Kim
f90ddf9e23 Protect explicit Korail train-type replay
Issue #171 added explicit train-type selection for non-KTX Korail routes. The merged implementation already wires search and reserve through TRAIN_TYPE_MAP; this follow-up locks that behavior with command-level and parser regression coverage, removes stale test import noise, and aligns the skill shortlist wording with the broader train_type output.

Constraint: The ktx-booking skill keeps ktx as the default train type for backward compatibility.

Rejected: Encode train type into train_id in this follow-up | larger selector schema change already marked as non-blocking UX follow-up.

Confidence: high

Scope-risk: narrow

Directive: Do not hardcode command_search or command_reserve back to TrainType.KTX; keep parser choices derived from TRAIN_TYPE_MAP.

Tested: python3 scripts/ktx_booking.py search --help

Tested: python3 scripts/ktx_booking.py reserve --help

Tested: PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking

Tested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py

Tested: PYTHONNOUSERSITE=1 PYTHONPATH=scripts python3 -S -c 'import ktx_booking; print(ktx_booking._KORAIL_IMPORT_ERROR); print(ktx_booking.TRAIN_TYPE_MAP["itx-cheongchun"])'

Tested: PYTHONNOUSERSITE=1 PYTHONPATH=scripts python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'

Tested: python3 -S scripts/ktx_booking.py search --help; python3 -S scripts/ktx_booking.py reserve --help

Tested: npm run lint

Tested: npm run typecheck

Tested: npm test

Not-tested: Live Korail search/reserve requiring credentials and external availability
2026-04-30 01:34:02 +09:00
2 changed files with 76 additions and 4 deletions

View file

@ -101,7 +101,7 @@ python3 scripts/ktx_booking.py search 남춘천 용산 20260503 150000 --train-t
- `index`
- `train_id`
- 출발/도착 시각
- KTX 여부
- 열차 종류 (`train_type`)
- 일반실/특실 가능 여부
- 예약 대기 가능 여부

View file

@ -1,5 +1,4 @@
import argparse
import importlib
import io
import subprocess
import sys
@ -131,7 +130,10 @@ class KtxBookingTests(unittest.TestCase):
normalized = ktx_booking.normalize_train(train, index=2)
self.assertIn("train_id", normalized)
resolved = ktx_booking.find_train_by_id([train], normalized["train_id"])
train_id = normalized["train_id"]
if not isinstance(train_id, str):
self.fail("train_id should be emitted as a string")
resolved = ktx_booking.find_train_by_id([train], train_id)
self.assertIs(resolved, train)
def test_build_parser_requires_train_id_for_reserve(self):
@ -148,6 +150,77 @@ class KtxBookingTests(unittest.TestCase):
self.assertEqual(args.train_id, "ktx:v1:test")
self.assertEqual(args.train_type, "ktx")
def test_build_parser_defaults_search_train_type_to_ktx(self):
args = ktx_booking.build_parser().parse_args([
"search",
"서울",
"부산",
"20260328",
"090000",
])
self.assertEqual(args.train_type, "ktx")
def test_parser_train_type_choices_match_supported_train_types(self):
parser = ktx_booking.build_parser()
for train_type in sorted(ktx_booking.TRAIN_TYPE_MAP):
search_args = parser.parse_args([
"search",
"서울",
"부산",
"20260328",
"090000",
"--train-type",
train_type,
])
reserve_args = parser.parse_args([
"reserve",
"서울",
"부산",
"20260328",
"090000",
"--train-id",
"ktx:v1:test",
"--train-type",
train_type,
])
self.assertEqual(search_args.train_type, train_type)
self.assertEqual(reserve_args.train_type, train_type)
def test_command_search_replays_selected_train_type(self):
selected = FakeTrain(
train_no="2080",
dep_time="155300",
arr_time="170000",
dep_name="남춘천",
arr_name="용산",
train_type_name="ITX-청춘",
)
client = FakeClient([selected])
args = argparse.Namespace(
dep="남춘천",
arr="용산",
date="20260503",
time="150000",
adults=1,
children=0,
toddlers=0,
seniors=0,
limit=5,
train_type="itx-cheongchun",
include_no_seats=False,
include_waiting_list=False,
)
with patch.object(ktx_booking, "build_client", return_value=client):
with redirect_stdout(io.StringIO()):
ktx_booking.command_search(args)
self.assertEqual(
client.search_calls[-1]["train_type"],
ktx_booking.TRAIN_TYPE_MAP["itx-cheongchun"],
)
def test_command_reserve_targets_exact_train_id_even_if_order_changes(self):
sold_out_first = FakeTrain(
train_no="001",
@ -180,7 +253,6 @@ class KtxBookingTests(unittest.TestCase):
self.assertIn("train_id", str(exc.exception))
def test_command_reserve_replays_selected_train_type(self):
selected = FakeTrain(train_no="009", dep_time="090000", arr_time="113000", label="selected")
train_id = ktx_booking.normalize_train(selected, index=1)["train_id"]