import contextlib
import io
import json
import unittest
from scripts.korean_spell_check import (
SpellCheckIssue,
apply_page_corrections,
check_text,
extract_result_payload,
parse_args,
split_text_into_chunks,
)
SAMPLE_RESULTS_HTML = """
"""
SAMPLE_NO_ISSUES_HTML = """
"""
class SplitTextIntoChunksTest(unittest.TestCase):
def test_prefers_paragraph_boundaries_before_falling_back(self):
text = "첫 문단입니다.\n\n둘째 문단입니다.\n\n셋째 문단입니다."
chunks = split_text_into_chunks(text, max_chars=15)
self.assertEqual(chunks, ["첫 문단입니다.\n\n", "둘째 문단입니다.\n\n", "셋째 문단입니다."])
self.assertEqual("".join(chunks), text)
def test_preserves_exact_blank_runs_and_indentation_when_rejoined(self):
text = "아버지가방에들어가신다.\n\n\n 왠지 않되요."
chunks = split_text_into_chunks(text, max_chars=15)
self.assertEqual("".join(chunks), text)
def test_handles_overlong_unit_with_trailing_separator(self):
text = "첫문장은조금길게씁니다. 둘째문장도길어요.\n\n다음 문단입니다."
chunks = split_text_into_chunks(text, max_chars=18)
self.assertEqual("".join(chunks), text)
def test_avoids_separator_only_chunks_when_a_paragraph_exactly_hits_the_limit(self):
text = "123456789012345\n\nabc"
chunks = split_text_into_chunks(text, max_chars=15)
self.assertEqual(chunks, ["123456789012345", "\n\nabc"])
class ExtractResultPayloadTest(unittest.TestCase):
def test_extracts_issue_rows_from_official_results_html(self):
pages = extract_result_payload(SAMPLE_RESULTS_HTML)
self.assertEqual(len(pages), 1)
self.assertEqual(pages[0]["str"], "아버지가방에들어가신다.")
self.assertEqual(pages[0]["errInfo"][0]["candWord"], "아버지가 방에 들어가신다")
def test_apply_page_corrections_uses_the_first_candidate(self):
pages = extract_result_payload(SAMPLE_RESULTS_HTML)
corrected = apply_page_corrections(pages[0])
self.assertEqual(corrected, "아버지가 방에 들어가신다.")
def test_returns_empty_pages_when_service_reports_no_issues(self):
self.assertEqual(extract_result_payload(SAMPLE_NO_ISSUES_HTML), [])
class CheckTextTest(unittest.TestCase):
def test_check_text_builds_chunked_issue_reports(self):
requested_texts = []
def fake_requester(chunk, *, strong_rules, timeout):
requested_texts.append((chunk, strong_rules, timeout))
payload = json.dumps(
[
{
"str": chunk,
"errInfo": [
{
"help": "철자 검사를 해 보니 이 어절은 분석할 수 없으므로 틀린 말로 판단하였습니다.
후보 어절은 이 철자 검사/교정기에서 띄어쓰기, 붙여쓰기, 음절 대치와 같은 교정 방법에 따라 수정한 결과입니다.",
"errorIdx": 0,
"correctMethod": 3,
"start": 0,
"errMsg": "",
"end": 11,
"orgStr": "아버지가방에들어가신다",
"candWord": "아버지가 방에 들어가신다",
}
],
"idx": 0,
}
],
ensure_ascii=False,
)
return f"""
"""
report = check_text(
"아버지가방에들어가신다.\n\n아버지가방에들어가신다.",
max_chars=15,
requester=fake_requester,
throttle_seconds=0,
)
self.assertEqual(len(report["chunks"]), 2)
self.assertEqual(report["corrected_text"], "아버지가 방에 들어가신다.\n\n아버지가 방에 들어가신다.")
self.assertEqual(len(report["issues"]), 2)
self.assertIsInstance(report["issues"][0], SpellCheckIssue)
self.assertEqual(report["issues"][0].original, "아버지가방에들어가신다")
self.assertEqual(report["issues"][0].suggestions[0], "아버지가 방에 들어가신다")
self.assertEqual(requested_texts[0][0], "아버지가방에들어가신다.\n\n")
self.assertTrue(all(call[1] for call in requested_texts))
def test_check_text_preserves_blank_lines_when_payload_collapses_them(self):
html = """
"""
report = check_text(
"아버지가방에들어가신다.\n\n왠지 않되요.",
max_chars=50,
requester=lambda chunk, *, strong_rules, timeout: html,
throttle_seconds=0,
)
self.assertEqual(report["corrected_text"], "아버지가 방에 들어가신다.\n\n왠지 안 돼요.")
self.assertEqual(report["chunks"][0]["corrected_text"], "아버지가 방에 들어가신다.\n\n왠지 안 돼요.")
def test_check_text_preserves_blank_lines_when_service_suggests_sentence_spacing(self):
html = """
"""
report = check_text(
"아버지가방에들어가신다.\n\n왠지 않되요.",
max_chars=50,
requester=lambda chunk, *, strong_rules, timeout: html,
throttle_seconds=0,
)
self.assertEqual(report["corrected_text"], "아버지가 방에 들어가신다.\n\n왠지 안 돼요.")
def test_check_text_preserves_indent_and_triple_blank_lines_for_file_style_input(self):
html = """
"""
report = check_text(
"아버지가방에들어가신다.\n\n\n 왠지 않되요.",
max_chars=50,
requester=lambda chunk, *, strong_rules, timeout: html,
throttle_seconds=0,
)
self.assertEqual(report["corrected_text"], "아버지가 방에 들어가신다.\n\n\n 왠지 안 돼요.")
def test_check_text_keeps_separator_layout_when_service_merges_spacing_across_boundary(self):
html = """
"""
report = check_text(
"아버지가방에들어가신다.\n\n\n 왠지 않되요.",
max_chars=50,
requester=lambda chunk, *, strong_rules, timeout: html,
throttle_seconds=0,
)
self.assertEqual(report["corrected_text"], "아버지가 방에 들어가신다.\n\n\n 왠지 안 돼요.")
def test_check_text_accepts_no_issue_chunks_before_a_later_corrected_chunk(self):
error_html = """
"""
chunks = iter([SAMPLE_NO_ISSUES_HTML, SAMPLE_NO_ISSUES_HTML, error_html])
text = "첫 문장은 조금 길겠습니다. 둘째 문장도 길어요.\n\n\n 아버지가방에들어가신다.\n"
report = check_text(
text,
max_chars=18,
requester=lambda chunk, *, strong_rules, timeout: next(chunks),
throttle_seconds=0,
)
self.assertEqual(report["meta"]["chunk_count"], 3)
self.assertEqual(
report["corrected_text"],
"첫 문장은 조금 길겠습니다. 둘째 문장도 길어요.\n\n\n 아버지가 방에 들어가신다.\n",
)
self.assertEqual(len(report["issues"]), 1)
class ParseArgsTest(unittest.TestCase):
def test_rejects_non_positive_max_chars(self):
for value in ("0", "-1"):
with self.subTest(value=value):
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit) as ctx:
parse_args(["--text", "테스트", "--max-chars", value])
self.assertNotEqual(ctx.exception.code, 0)
if __name__ == "__main__":
unittest.main()