From 2fb1a54f33321062cd2d4249e0b1cff24a705a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=ED=83=9C=EC=A0=95?= Date: Thu, 20 Aug 2026 14:23:19 +0900 Subject: [PATCH] fix: route canonical HWPX values through record rules --- app/api/routes/documents/generate.py | 38 ++++++++--- tests/api/test_document_editing_endpoint.py | 70 +++++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/app/api/routes/documents/generate.py b/app/api/routes/documents/generate.py index 501059f..e02fd98 100644 --- a/app/api/routes/documents/generate.py +++ b/app/api/routes/documents/generate.py @@ -61,6 +61,10 @@ def generate_document( editing_service: Annotated[ DocumentEditingService, Depends(get_document_editing_service) ], + record_service: Annotated[ + DocumentRecordGenerationService, + Depends(get_document_record_generation_service), + ], settings: Annotated[Settings, Depends(get_settings)], assets: Annotated[ list[UploadFile] | None, @@ -80,14 +84,32 @@ def generate_document( temporary_directory / f"output.{command.format.value}" ) try: - result = editing_service.generate( - command.template_id, - command.format, - destination_path, - values=command.values, - application_options=command.application_options, - assets=asset_paths, - ) + record_keys = { + key + for rule in record_service.rules.get(command.template_id, ()) + for key in (rule.source_key, *rule.record_keys) + } + if ( + command.format is DocumentFormat.HWPX + and command.values + and set(command.values) <= record_keys + and not command.application_options + and not asset_paths + ): + result = record_service.generate( + command.values, + destination_path, + template_id=command.template_id, + ) + else: + result = editing_service.generate( + command.template_id, + command.format, + destination_path, + values=command.values, + application_options=command.application_options, + assets=asset_paths, + ) except DocumentEditingError as exc: raise_editing_http_error(exc) return mutation_file_response( diff --git a/tests/api/test_document_editing_endpoint.py b/tests/api/test_document_editing_endpoint.py index 8e1bd8f..784e7b9 100644 --- a/tests/api/test_document_editing_endpoint.py +++ b/tests/api/test_document_editing_endpoint.py @@ -1,5 +1,6 @@ import io import json +import xml.etree.ElementTree as ET import zipfile from pathlib import Path @@ -160,6 +161,75 @@ async def test_generate_hwpx_with_values_and_application_options() -> None: assert "[v]" in section +@pytest.mark.asyncio +async def test_generate_hwpx_with_canonical_values() -> None: + payload = { + "template_id": "immigration_integrated_application_v34", + "format": "hwpx", + "values": { + "family_name": "NGUYEN", + "given_names": "VAN A", + "birth_year": "1995", + "birth_month": "03", + "birth_day": "15", + "passport_number": "P1234567", + }, + } + + async with AsyncClient( + transport=ASGITransport(app=app), + base_url="http://test", + ) as client: + response = await client.post( + "/api/v1/documents/generate", + data={"payload": json.dumps(payload)}, + ) + + assert response.status_code == 200, response.text + with zipfile.ZipFile(io.BytesIO(response.content)) as package: + root = ET.fromstring(package.read("Contents/section0.xml")) + hp = "{http://www.hancom.co.kr/hwpml/2011/paragraph}" + table = next(root.iter(f"{hp}tbl")) + cells = {} + for cell in table.iter(f"{hp}tc"): + address = cell.find(f"{hp}cellAddr") + if address is not None: + coordinates = ( + int(address.attrib["rowAddr"]), + int(address.attrib["colAddr"]), + ) + cells[coordinates] = "".join( + text.text or "" for text in cell.iter(f"{hp}t") + ) + assert cells[(14, 3)] == "NGUYEN" + assert cells[(14, 19)] == "VAN A" + assert cells[(16, 5)] == "1995" + assert cells[(16, 15)] == "03" + assert cells[(16, 19)] == "15" + assert cells[(18, 3)] == "P1234567" + + +@pytest.mark.asyncio +async def test_generate_hwpx_rejects_unknown_value_key() -> None: + payload = { + "template_id": "immigration_integrated_application_v34", + "format": "hwpx", + "values": {"definitely_unknown_field": "x"}, + } + + async with AsyncClient( + transport=ASGITransport(app=app), + base_url="http://test", + ) as client: + response = await client.post( + "/api/v1/documents/generate", + data={"payload": json.dumps(payload)}, + ) + + assert response.status_code == 422 + assert "fields were not found in the HWPX template" in response.json()["detail"] + + @pytest.mark.asyncio async def test_generate_hwp_with_signature(tmp_path: Path) -> None: payload = {