Skip to content

Commit 5e8925d

Browse files
committed
test: expand coverage for cli and APIs
1 parent 6921989 commit 5e8925d

10 files changed

Lines changed: 972 additions & 0 deletions

tests/test_akoma_utils.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import tempfile
2+
import unittest
3+
4+
from normattiva2md.akoma_utils import (
5+
akoma_uri_to_normattiva_url,
6+
extract_akoma_uris_from_xml,
7+
extract_cited_laws,
8+
parse_article_reference,
9+
)
10+
from normattiva2md.constants import AKN_NAMESPACE
11+
12+
13+
class TestAkomaUtils(unittest.TestCase):
14+
def test_parse_article_reference(self):
15+
url = "https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2020-01-01;1~art16bis"
16+
self.assertEqual(parse_article_reference(url), "art_16bis")
17+
self.assertIsNone(parse_article_reference(123))
18+
self.assertIsNone(parse_article_reference("https://www.normattiva.it/"))
19+
20+
def test_akoma_uri_to_normattiva_url_with_article(self):
21+
uri = "/akn/it/act/legge/stato/2003-07-29/229/!main#art_1"
22+
url = akoma_uri_to_normattiva_url(uri)
23+
self.assertTrue(url.endswith("~art1"))
24+
self.assertTrue(url.startswith("https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2003-07-29;229"))
25+
26+
def test_akoma_uri_to_normattiva_url_ignores_article_for_costituzione(self):
27+
uri = "/akn/it/act/costituzione/stato/1948-01-01/1/!main#art_1"
28+
url = akoma_uri_to_normattiva_url(uri)
29+
self.assertTrue(url.startswith("https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:costituzione:1948-01-01"))
30+
self.assertNotIn("~art", url)
31+
32+
def test_akoma_uri_to_normattiva_url_unknown_type(self):
33+
uri = "/akn/it/act/unknown/stato/2003-07-29/229/!main"
34+
self.assertIsNone(akoma_uri_to_normattiva_url(uri))
35+
36+
def test_extract_akoma_uris_from_xml(self):
37+
xml = (
38+
f"<akn:akomaNtoso xmlns:akn=\"{AKN_NAMESPACE['akn']}\">"
39+
"<akn:doc>"
40+
"<akn:ref href=\"/akn/it/act/legge/stato/2003-07-29/229/!main#art_1\"/>"
41+
"<akn:ref href=\"/akn/it/act/legge/stato/2004-01-01/10/!main\"/>"
42+
"<akn:ref href=\"https://example.com\"/>"
43+
"</akn:doc>"
44+
"</akn:akomaNtoso>"
45+
)
46+
with tempfile.NamedTemporaryFile("w", suffix=".xml", delete=False) as tmp:
47+
tmp.write(xml)
48+
tmp_path = tmp.name
49+
50+
uris = extract_akoma_uris_from_xml(tmp_path)
51+
self.assertEqual(len(uris), 2)
52+
self.assertTrue(any(uri.startswith("/akn/") for uri in uris))
53+
54+
def test_extract_cited_laws(self):
55+
xml = (
56+
f"<akn:akomaNtoso xmlns:akn=\"{AKN_NAMESPACE['akn']}\">"
57+
"<akn:doc>"
58+
"<akn:ref href=\"/akn/it/act/legge/stato/2003-07-29/229/!main#art_1\"/>"
59+
"<akn:ref href=\"/akn/it/act/legge/stato/2003-07-29/229/!main#art_2\"/>"
60+
"</akn:doc>"
61+
"</akn:akomaNtoso>"
62+
)
63+
with tempfile.NamedTemporaryFile("w", suffix=".xml", delete=False) as tmp:
64+
tmp.write(xml)
65+
tmp_path = tmp.name
66+
67+
cited = extract_cited_laws(tmp_path)
68+
self.assertEqual(len(cited), 1)
69+
only = next(iter(cited))
70+
self.assertTrue(only.startswith("https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2003-07-29;229"))
71+
72+
73+
if __name__ == "__main__":
74+
unittest.main()

tests/test_api_conversion.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import tempfile
2+
import unittest
3+
from unittest import mock
4+
5+
from normattiva2md.api import convert_url, convert_xml
6+
from normattiva2md.exceptions import ConversionError, InvalidURLError, XMLFileNotFoundError
7+
from normattiva2md.constants import AKN_NAMESPACE
8+
9+
10+
class TestApiConversion(unittest.TestCase):
11+
def _write_xml(self, content):
12+
tmp = tempfile.NamedTemporaryFile("w", suffix=".xml", delete=False)
13+
tmp.write(content)
14+
tmp.flush()
15+
return tmp.name
16+
17+
def _minimal_xml(self):
18+
return (
19+
f"<akn:akomaNtoso xmlns:akn=\"{AKN_NAMESPACE['akn']}\" "
20+
"xmlns:eli=\"http://data.europa.eu/eli/ontology#\">"
21+
"<akn:meta>"
22+
"<akn:identification>"
23+
"<akn:FRBRWork>"
24+
"<akn:FRBRalias name=\"urn:nir\" value=\"urn:nir:stato:legge:2003-07-29;229\"/>"
25+
"</akn:FRBRWork>"
26+
"<akn:FRBRExpression><akn:FRBRdate date=\"2020-01-01\"/></akn:FRBRExpression>"
27+
"<eli:id_local>229</eli:id_local>"
28+
"<eli:date_document>2003-07-29</eli:date_document>"
29+
"</akn:identification>"
30+
"</akn:meta>"
31+
"<akn:body>"
32+
"<akn:article eId=\"art_1\"><akn:content><akn:p>Testo</akn:p></akn:content></akn:article>"
33+
"<akn:ref href=\"/akn/it/act/legge/stato/2004-01-01/10/!main#art_2\"/>"
34+
"<akn:ref href=\"https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2005-01-01;11\"/>"
35+
"</akn:body>"
36+
"</akn:akomaNtoso>"
37+
)
38+
39+
def test_convert_xml_missing_file(self):
40+
with self.assertRaises(XMLFileNotFoundError):
41+
convert_xml("/tmp/does-not-exist.xml")
42+
43+
def test_convert_xml_parse_error(self):
44+
path = self._write_xml("<akn:akomaNtoso")
45+
with self.assertRaises(ConversionError):
46+
convert_xml(path)
47+
48+
def test_convert_url_invalid_domain(self):
49+
with self.assertRaises(InvalidURLError):
50+
convert_url("https://example.com/not-allowed")
51+
52+
def test_convert_xml_with_urls_builds_cross_references(self):
53+
xml_path = self._write_xml(self._minimal_xml())
54+
55+
with mock.patch("normattiva2md.api.generate_markdown_text", return_value="MD") as mock_generate:
56+
result = convert_xml(xml_path, with_urls=True, quiet=True)
57+
58+
self.assertIsNotNone(result)
59+
cross_references = mock_generate.call_args.kwargs["cross_references"]
60+
self.assertIn(
61+
"https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2004-01-01;10~art2",
62+
cross_references,
63+
)
64+
self.assertIn(
65+
"https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2005-01-01;11",
66+
cross_references,
67+
)
68+
69+
def test_convert_xml_merges_metadata(self):
70+
xml_path = self._write_xml(self._minimal_xml())
71+
72+
with mock.patch(
73+
"normattiva2md.api.extract_metadata_from_xml",
74+
return_value={
75+
"dataGU": "20200101",
76+
"codiceRedaz": "XYZ",
77+
"dataVigenza": "20200102",
78+
"url": "xml-url",
79+
},
80+
):
81+
with mock.patch(
82+
"normattiva2md.api.generate_markdown_text", return_value="MD"
83+
) as mock_generate:
84+
convert_xml(
85+
xml_path,
86+
metadata={"dataGU": "20221212", "custom": "value"},
87+
quiet=True,
88+
)
89+
90+
merged_metadata = mock_generate.call_args.kwargs["metadata"]
91+
self.assertEqual(merged_metadata["dataGU"], "20221212")
92+
self.assertEqual(merged_metadata["custom"], "value")
93+
self.assertEqual(merged_metadata["codiceRedaz"], "XYZ")
94+
95+
96+
if __name__ == "__main__":
97+
unittest.main()

tests/test_api_convert_url.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import os
2+
import tempfile
3+
import unittest
4+
from unittest import mock
5+
6+
from normattiva2md.api import convert_url
7+
8+
9+
class TestApiConvertUrl(unittest.TestCase):
10+
def _write_minimal_xml(self, path):
11+
content = (
12+
'<akn:akomaNtoso xmlns:akn="http://docs.oasis-open.org/legaldocml/ns/akn/3.0">'
13+
"<akn:meta/>"
14+
"<akn:body/>"
15+
"</akn:akomaNtoso>"
16+
)
17+
with open(path, "w", encoding="utf-8") as f:
18+
f.write(content)
19+
20+
def test_convert_url_success(self):
21+
params = {
22+
"dataGU": "20200101",
23+
"codiceRedaz": "X",
24+
"dataVigenza": "20200102",
25+
}
26+
with tempfile.TemporaryDirectory() as tmpdir:
27+
xml_path = os.path.join(tmpdir, "doc.xml")
28+
29+
def fake_download(_params, output_path, _session, quiet=False):
30+
self._write_minimal_xml(output_path)
31+
return True
32+
33+
with mock.patch(
34+
"normattiva2md.api.extract_params_from_normattiva_url",
35+
return_value=(params, object()),
36+
), mock.patch(
37+
"normattiva2md.api.download_akoma_ntoso",
38+
side_effect=fake_download,
39+
):
40+
result = convert_url(
41+
"https://www.normattiva.it/uri-res/N2Ls?urn:nir:stato:legge:2020;1",
42+
quiet=True,
43+
)
44+
self.assertIsNotNone(result)
45+
self.assertEqual(result.metadata["codiceRedaz"], "X")
46+
self.assertTrue(result.url.startswith("https://www.normattiva.it/uri-res/N2Ls"))
47+
48+
49+
if __name__ == "__main__":
50+
unittest.main()

tests/test_api_search.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import unittest
2+
from unittest import mock
3+
4+
from normattiva2md.api import search_law
5+
from normattiva2md.exceptions import APIKeyError
6+
7+
8+
class FakeResponse:
9+
def __init__(self, status_code=200, payload=None, text=""):
10+
self.status_code = status_code
11+
self._payload = payload or {}
12+
self.text = text
13+
14+
def json(self):
15+
return self._payload
16+
17+
18+
class TestApiSearch(unittest.TestCase):
19+
def test_search_law_missing_api_key(self):
20+
with mock.patch("normattiva2md.api.load_env_file"), mock.patch(
21+
"os.getenv", return_value=None
22+
):
23+
with self.assertRaises(APIKeyError):
24+
search_law("legge stanca", quiet=True)
25+
26+
def test_search_law_non_200(self):
27+
response = FakeResponse(status_code=500, text="error")
28+
with mock.patch("normattiva2md.api.load_env_file"), mock.patch(
29+
"os.getenv", return_value="key"
30+
), mock.patch(
31+
"requests.post", return_value=response
32+
):
33+
results = search_law("test", quiet=True)
34+
self.assertEqual(results, [])
35+
36+
def test_search_law_empty_results(self):
37+
response = FakeResponse(status_code=200, payload={"results": []})
38+
with mock.patch("normattiva2md.api.load_env_file"), mock.patch(
39+
"os.getenv", return_value="key"
40+
), mock.patch(
41+
"requests.post", return_value=response
42+
):
43+
results = search_law("test", quiet=True)
44+
self.assertEqual(results, [])
45+
46+
47+
if __name__ == "__main__":
48+
unittest.main()

0 commit comments

Comments
 (0)