From 349398f769996ab0e6e4977f0b7e2d39bee3ef56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nelson=20Guam=C3=A1n?= Date: Sun, 15 Dec 2024 09:48:48 +0000 Subject: [PATCH] fix: Additional tests, integration with isort and black, and testing triggers on the publish GitHub Action --- .github/workflows/publish.yml | 6 +- notebooks/tender_by_code.ipynb | 3 +- poetry.lock | 212 +++++++++++++++++++++- pyproject.toml | 2 +- src/licitpy/client/purchase_orders.py | 4 +- src/licitpy/client/tenders.py | 2 +- src/licitpy/downloader/purchase_order.py | 4 +- src/licitpy/downloader/tender.py | 6 +- src/licitpy/parsers/purchase_order.py | 12 +- tests/unit/test_parsers_purchase_order.py | 184 +++++++++++++++++++ 10 files changed, 414 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6a5055d..0715361 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,10 +1,8 @@ name: Publish on: - workflow_run: - workflows: ["Release"] - types: - - completed + release: + types: [published] jobs: pypi-publish: diff --git a/notebooks/tender_by_code.ipynb b/notebooks/tender_by_code.ipynb index 1544dff..115ab34 100644 --- a/notebooks/tender_by_code.ipynb +++ b/notebooks/tender_by_code.ipynb @@ -58,7 +58,8 @@ ], "source": [ "import licitpy\n", - "print(licitpy.__version__)\n" + "\n", + "print(licitpy.__version__)" ] }, { diff --git a/poetry.lock b/poetry.lock index 0733d75..396c0ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -25,6 +25,21 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] +[[package]] +name = "asttokens" +version = "3.0.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, +] + +[package.extras] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "attrs" version = "24.2.0" @@ -102,10 +117,12 @@ files = [ [package.dependencies] click = ">=8.0.0" +ipython = {version = ">=7.8.0", optional = true, markers = "extra == \"jupyter\""} mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" +tokenize-rt = {version = ">=3.2.0", optional = true, markers = "extra == \"jupyter\""} tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} @@ -465,6 +482,17 @@ files = [ {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"}, ] +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + [[package]] name = "distlib" version = "0.3.9" @@ -501,6 +529,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "2.1.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + [[package]] name = "filelock" version = "3.16.1" @@ -621,6 +663,42 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "ipython" +version = "8.18.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.18.0-py3-none-any.whl", hash = "sha256:d538a7a98ad9b7e018926447a5f35856113a85d08fd68a165d7871ab5175f6e0"}, + {file = "ipython-8.18.0.tar.gz", hash = "sha256:4feb61210160f75e229ce932dbf8b719bff37af123c0b985fd038b14233daa16"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] + [[package]] name = "isort" version = "5.13.2" @@ -635,6 +713,25 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + [[package]] name = "jinja2" version = "3.1.4" @@ -900,6 +997,20 @@ files = [ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mdurl" version = "0.1.2" @@ -1161,6 +1272,21 @@ files = [ numpy = ">=1.23.5" types-pytz = ">=2022.1.1" +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + [[package]] name = "pathspec" version = "0.12.1" @@ -1183,6 +1309,20 @@ files = [ {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, ] +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + [[package]] name = "platformdirs" version = "4.3.6" @@ -1246,6 +1386,31 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pydantic" version = "2.10.3" @@ -1749,6 +1914,25 @@ files = [ {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, ] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "stevedore" version = "5.4.0" @@ -1792,6 +1976,17 @@ files = [ [package.extras] tests = ["pytest", "pytest-cov"] +[[package]] +name = "tokenize-rt" +version = "6.1.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +optional = false +python-versions = ">=3.9" +files = [ + {file = "tokenize_rt-6.1.0-py2.py3-none-any.whl", hash = "sha256:d706141cdec4aa5f358945abe36b911b8cbdc844545da99e811250c0cee9b6fc"}, + {file = "tokenize_rt-6.1.0.tar.gz", hash = "sha256:e8ee836616c0877ab7c7b54776d2fefcc3bde714449a206762425ae114b53c86"}, +] + [[package]] name = "tomli" version = "2.2.1" @@ -1892,6 +2087,21 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + [[package]] name = "types-beautifulsoup4" version = "4.12.0.20241020" @@ -2074,4 +2284,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.12" -content-hash = "f800cdf275333a125d90d04af9331a799c4ad6604600e679a4ba8a5f50919c2c" +content-hash = "eee7e636ba4b5510ab6495d8595bad3ac176a75c386cb7b9a05d1404ff7a70ef" diff --git a/pyproject.toml b/pyproject.toml index 8b380d8..231a3b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ requests-cache = "^1.2.1" [tool.poetry.group.dev.dependencies] -black = "^24.10.0" +black = {extras = ["jupyter"], version = "^24.10.0"} isort = "^5.13.2" pytest = "^8.3.3" pytest-mock = "^3.14.0" diff --git a/src/licitpy/client/purchase_orders.py b/src/licitpy/client/purchase_orders.py index 442f855..60e696f 100644 --- a/src/licitpy/client/purchase_orders.py +++ b/src/licitpy/client/purchase_orders.py @@ -21,7 +21,7 @@ def from_date( """ Retrieves purchase orders from a specific date range. """ - + start_date, end_date = determine_date_range(start_date, end_date, time_range) return self.source.get_monthly_purchase_orders(start_date, end_date) @@ -30,5 +30,5 @@ def from_code(self, code: str) -> PurchaseOrder: """ Retrieves a purchase order by its code. """ - + return self.source.get_purchase_order(code) diff --git a/src/licitpy/client/tenders.py b/src/licitpy/client/tenders.py index 450c220..c5b27f4 100644 --- a/src/licitpy/client/tenders.py +++ b/src/licitpy/client/tenders.py @@ -21,7 +21,7 @@ def from_date( """ Retrieves tenders from a specific date range. """ - + start_date, end_date = determine_date_range(start_date, end_date, time_range) return self.source.get_monthly_tenders(start_date, end_date) diff --git a/src/licitpy/downloader/purchase_order.py b/src/licitpy/downloader/purchase_order.py index a1498af..05520f1 100644 --- a/src/licitpy/downloader/purchase_order.py +++ b/src/licitpy/downloader/purchase_order.py @@ -16,7 +16,7 @@ def get_purchase_orders_from_csv( """ Gets a list of purchase orders from a given year and month. """ - + # Note about the commune: # In the CSV file, the commune is linked to the column "CiudadUnidadCompra" (AO). # Example in CSV format: @@ -116,5 +116,5 @@ def get_purchase_orders(self, year: int, month: int) -> List[PurchaseOrderFromCS Gets a list of purchase orders from a given year and month. """ purchase_orders = self.get_purchase_orders_from_csv(year, month) - + return sorted(purchase_orders, key=lambda po: po.FechaEnvio, reverse=True) diff --git a/src/licitpy/downloader/tender.py b/src/licitpy/downloader/tender.py index a772481..705bbce 100644 --- a/src/licitpy/downloader/tender.py +++ b/src/licitpy/downloader/tender.py @@ -146,7 +146,7 @@ def get_tender_ocds_data_from_api(self, code: str) -> OpenContract: """ Retrieves OCDS data for a given tender code from the API. """ - + url = f"https://apis.mercadopublico.cl/OCDS/data/record/{code}" response = self.session.get(url) @@ -173,7 +173,7 @@ def get_tender_ocds_data_from_codes( """ Retrieves OCDS data for a list of tenders from the API. """ - + data_tenders: Dict[str, OpenContract] = {} with ThreadPoolExecutor(max_workers=32) as executor: @@ -279,7 +279,7 @@ def download_attachment(self, url: HttpUrl, attachment: Attachment) -> str: """ Downloads an attachment from a URL using a POST request with the attachment ID. """ - + return self.download_attachment_from_url(url, attachment) def get_tender_questions(self, code: str) -> List[Question]: diff --git a/src/licitpy/parsers/purchase_order.py b/src/licitpy/parsers/purchase_order.py index 46cc97d..a4821bd 100644 --- a/src/licitpy/parsers/purchase_order.py +++ b/src/licitpy/parsers/purchase_order.py @@ -13,14 +13,14 @@ class PurchaseOrderParser(BaseParser): def get_url_from_code(self, code: str) -> HttpUrl: """Get the URL of a purchase order from the code.""" - + url = f"https://www.mercadopublico.cl/PurchaseOrder/Modules/PO/DetailsPurchaseOrder.aspx?codigoOC={code}" - + return HttpUrl(url) def get_purchase_order_status(self, html: str) -> Status: """Get the status of a purchase order from the HTML content.""" - + status = self.get_text_by_element_id(html, "lblStatusPOValue") # Generates the same process as performed in @@ -37,19 +37,19 @@ def get_purchase_order_status(self, html: str) -> Status: def get_purchase_order_title_from_html(self, html: str) -> str: """Get the title of a purchase order from the HTML content.""" - + return self.get_text_by_element_id(html, "lblNamePOValue") def get_purchase_order_issue_date_from_html(self, html: str) -> date: """Get the issue date of a purchase order from the HTML content.""" - + # 06-12-2024 - DD-MM-YYYY issue_date = self.get_text_by_element_id(html, "lblCreationDatePOValue") return convert_to_date(issue_date) def get_purchase_order_tender_code_from_html(self, html: str) -> str | None: """Get the tender code of a purchase order from the HTML content.""" - + if not self.has_element_id(html, "lblProvenience"): return None diff --git a/tests/unit/test_parsers_purchase_order.py b/tests/unit/test_parsers_purchase_order.py index 8487cae..1174b14 100644 --- a/tests/unit/test_parsers_purchase_order.py +++ b/tests/unit/test_parsers_purchase_order.py @@ -1,9 +1,12 @@ +import re +from datetime import date from unittest.mock import patch import pytest from pydantic import HttpUrl from licitpy.parsers.purchase_order import PurchaseOrderParser +from licitpy.types.geography import Commune, GeographyChile from licitpy.types.purchase_order import Status @@ -13,7 +16,12 @@ def purchase_order_parser() -> PurchaseOrderParser: def test_get_url_from_code(purchase_order_parser: PurchaseOrderParser) -> None: + """ + Test that the method get_url_from_code returns the expected url + """ + code = "12345678" + expected_url = HttpUrl( "https://www.mercadopublico.cl/PurchaseOrder/Modules/PO/DetailsPurchaseOrder.aspx?codigoOC=12345678" ) @@ -24,6 +32,10 @@ def test_get_url_from_code(purchase_order_parser: PurchaseOrderParser) -> None: def test_get_purchase_order_status(purchase_order_parser: PurchaseOrderParser) -> None: + """ + Test that the method get_purchase_order_status returns the expected status + """ + html = """ @@ -31,6 +43,7 @@ def test_get_purchase_order_status(purchase_order_parser: PurchaseOrderParser) - """ + expected_status = Status("Aceptada") with patch.object( @@ -44,6 +57,10 @@ def test_get_purchase_order_status(purchase_order_parser: PurchaseOrderParser) - def test_get_purchase_order_title_from_html( purchase_order_parser: PurchaseOrderParser, ) -> None: + """ + Test that the method get_purchase_order_title_from_html returns the expected title + """ + html = """ @@ -61,3 +78,170 @@ def test_get_purchase_order_title_from_html( result = purchase_order_parser.get_purchase_order_title_from_html(html) assert result == expected_title, f"Expected {expected_title}, got {result}" + + +def test_get_purchase_order_issue_date_from_html( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_issue_date_from_html returns the expected issue date + """ + + html = """ + + + 06-12-2024 + + + """ + expected_issue_date = date(2024, 12, 6) + + with patch.object( + purchase_order_parser, "get_text_by_element_id", return_value="06-12-2024" + ): + result = purchase_order_parser.get_purchase_order_issue_date_from_html(html) + + assert ( + result == expected_issue_date + ), f"Expected {expected_issue_date}, got {result}" + + +def test_get_purchase_order_issue_date_from_html_invalid_date( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_issue_date_from_html raises a ValueError for an invalid date + """ + + html = """ + + + invalid-date + + + """ + + with patch.object( + purchase_order_parser, "get_text_by_element_id", return_value="invalid-date" + ): + with pytest.raises( + ValueError, + match=re.escape( + "The date string 'invalid-date' does not match ISO (YYYY-MM-DD) or dd-mm-yyyy formats." + ), + ): + purchase_order_parser.get_purchase_order_issue_date_from_html(html) + + +def test_get_purchase_order_tender_code_from_html( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_tender_code_from_html returns the expected tender code + """ + + html = """ + + + 750301-54-L124 + + + """ + expected_tender_code = "750301-54-L124" + + with patch.object( + purchase_order_parser, "get_text_by_element_id", return_value="750301-54-L124" + ): + result = purchase_order_parser.get_purchase_order_tender_code_from_html(html) + + assert ( + result == expected_tender_code + ), f"Expected {expected_tender_code}, got {result}" + + +def test_get_purchase_order_tender_code_from_html_no_element( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_tender_code_from_html returns None if the element does not exist + """ + + html = """ + + + + + """ + + with patch.object(purchase_order_parser, "has_element_id", return_value=False): + result = purchase_order_parser.get_purchase_order_tender_code_from_html(html) + + assert result is None, f"Expected None, got {result}" + + +def test_get_purchase_order_tender_code_from_html_element_exists( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_tender_code_from_html returns the expected tender code if the element exists + """ + + html = """ + + + 750301-54-L124 + + + """ + expected_tender_code = "750301-54-L124" + + with patch.object(purchase_order_parser, "has_element_id", return_value=True): + with patch.object( + purchase_order_parser, + "get_text_by_element_id", + return_value="750301-54-L124", + ): + result = purchase_order_parser.get_purchase_order_tender_code_from_html( + html + ) + + assert ( + result == expected_tender_code + ), f"Expected {expected_tender_code}, got {result}" + + +def test_get_purchase_order_commune_from_html( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_commune_from_html returns the expected commune + """ + html = """ + + + Santiago + + + """ + expected_commune = Commune("Viña del Mar") + + with patch.object( + purchase_order_parser, "get_text_by_element_id", return_value="Viña del Mar" + ): + result = purchase_order_parser.get_purchase_order_commune_from_html(html) + + assert result == expected_commune, f"Expected {expected_commune}, got {result}" + + +def test_get_purchase_order_region_from_html( + purchase_order_parser: PurchaseOrderParser, +) -> None: + """ + Test that the method get_purchase_order_region_from_html returns the expected region + """ + commune = Commune("Santiago") + + expected_region = GeographyChile[commune].region + result = purchase_order_parser.get_purchase_order_region_from_html(commune) + + assert result == expected_region, f"Expected {expected_region}, got {result}"