k-skill/scripts/test_zipcode_search.py
Jeffrey (Dongkyu) Kim 5c95e9e742 Enable official English address lookup for Korean postcode searches
The zipcode-search feature now uses the official ePost integrated search surface
for postcode plus English-address lookups, ships a runnable helper, and
locks the behavior with regression coverage plus aligned docs.

A narrow compatibility fallback was also added to the KIPRIS XML parser so
repository CI stays green on the current Python 3.14 environment where
pyexpat is unavailable.

Constraint: Must use official public ePost output instead of custom romanization rules
Constraint: Repository verification must pass under the current local Python 3.14 toolchain
Rejected: Implement our own Hangul-to-English address formatter | would diverge from the official postal rendering
Rejected: Leave the KIPRIS parser untouched | npm run ci currently fails in this environment without the XML fallback
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep zipcode-search tied to the official ePost integrated surface unless a new approved source is added
Tested: python3 -m unittest scripts.test_zipcode_search
Tested: node --test scripts/skill-docs.test.js
Tested: python3 scripts/zipcode_search.py '서울특별시 강남구 테헤란로 123'
Tested: npm run build
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search
Tested: npm run ci
Not-tested: Live no-result and multi-result zipcode queries beyond the verified Teheran-ro example
2026-04-10 10:34:25 +09:00

87 lines
3.2 KiB
Python

import json
import unittest
from unittest import mock
from scripts.zipcode_search import (
SEARCH_URL,
AddressSearchResult,
fetch_search_page,
lookup_korean_address,
parse_search_results,
)
SAMPLE_HTML = """
<table>
<tbody>
<tr class="title2">
<th scope="row">06133</th>
<td class="t_a_l l_h_18">
서울특별시 강남구 테헤란로 123 (역삼동, 여삼빌딩)<br />
서울특별시 강남구 역삼동 648-23 (여삼빌딩)
</td>
<td><a class="btn_s gray" href="#" onclick="javascript:viewDetail('06133','서울특별시 강남구 테헤란로 123 (역삼동, 여삼빌딩)','123, Teheran-ro, Gangnam-gu, Seoul, 06133, Rep. of KOREA','서울특별시 강남구 역삼동 648-23 (여삼빌딩)', '0');" title="보기">더보기</a></td>
</tr>
<tr class="view">
<td class="p_l_86px" colspan="3">
123, Teheran-ro, Gangnam-gu, Seoul, 06133, Rep. of KOREA
</td>
</tr>
</tbody>
</table>
"""
class ZipcodeSearchParsingTest(unittest.TestCase):
def test_parse_search_results_extracts_official_korean_and_english_addresses(self):
items = parse_search_results(SAMPLE_HTML)
self.assertEqual(
items,
[
AddressSearchResult(
zip_code="06133",
road_address="서울특별시 강남구 테헤란로 123 (역삼동, 여삼빌딩)",
english_address="123, Teheran-ro, Gangnam-gu, Seoul, 06133, Rep. of KOREA",
jibun_address="서울특별시 강남구 역삼동 648-23 (여삼빌딩)",
)
],
)
def test_lookup_korean_address_rejects_blank_query(self):
with self.assertRaisesRegex(ValueError, "query"):
lookup_korean_address(" ")
class ZipcodeSearchTransportTest(unittest.TestCase):
def test_fetch_search_page_uses_official_https_endpoint_and_curl_safety_flags(self):
runner = mock.Mock(return_value=mock.Mock(stdout="<html></html>"))
page = fetch_search_page("서울특별시 강남구 테헤란로 123", runner=runner)
self.assertEqual(page, "<html></html>")
command = runner.call_args.args[0]
self.assertEqual(command[0], "curl")
self.assertIn("--http1.1", command)
self.assertEqual(command[command.index("--tls-max") + 1], "1.2")
self.assertEqual(command[command.index("--retry") + 1], "3")
self.assertIn("--retry-all-errors", command)
self.assertEqual(command[command.index("--retry-delay") + 1], "1")
self.assertEqual(command[command.index("--max-time") + 1], "20")
self.assertEqual(command[-1], SEARCH_URL)
class ZipcodeSearchCliShapeTest(unittest.TestCase):
def test_lookup_response_is_json_serializable(self):
response = lookup_korean_address(
"서울특별시 강남구 테헤란로 123",
fetcher=lambda _query: SAMPLE_HTML,
)
payload = json.loads(response.to_json())
self.assertEqual(payload["query"], "서울특별시 강남구 테헤란로 123")
self.assertEqual(payload["results"][0]["zip_code"], "06133")
self.assertIn("Teheran-ro", payload["results"][0]["english_address"])
if __name__ == "__main__":
unittest.main()