Spaces:
Runtime error
Runtime error
File size: 7,026 Bytes
893a679 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | """
« article 35 du code minier » ouvre le bon document sur le bon article.
Trois choses devaient exister ensemble : reconnaitre le numero, reconnaitre le
document, et verifier que l'article s'y trouve. Avant, la premiere seule etait
faite — `_parse_article_reference` extrayait bien `doc_hint`, mais personne ne
s'en servait, et `direct_navigation` etait conditionne a `total == 1`, ce qui ne
se produit presque jamais.
Usage:
pytest tests/test_services/test_article_reference.py -v
"""
from datetime import date
import pytest
from app.models.law import Article, Law
from app.schemas.search import SearchRequest
from app.services.article_reference import (
ArticleReference,
normalize_number,
parse_reference,
)
from app.services.search_service import SearchService
from app.services.search_vectors import REINDEX_ARTICLES_SQL, REINDEX_LAWS_SQL
CODE_MINIER = 300
CODE_TRAVAIL = 301
@pytest.fixture
async def codes(db_session, category_ids):
from sqlalchemy import text as sql_text
laws = [
Law(id=CODE_MINIER, reference="LOI-2023-014",
title="Loi N°2023/014 du 19 décembre 2023 portant Code Minier",
content="Code régissant les activités minières au Cameroun.",
type="loi", language="fr", status="published",
category_id=category_ids["Droit de l'Environnement et des Ressources Naturelles"],
publication_date=date(2023, 12, 19)),
Law(id=CODE_TRAVAIL, reference="LOI-1992-007",
title="Loi N°92/007 du 14 août 1992 portant Code du Travail",
content="Code régissant les relations de travail.",
type="loi", language="fr", status="published",
category_id=category_ids["Droit du Travail et Sécurité Sociale"],
publication_date=date(1992, 8, 14)),
]
db_session.add_all(laws)
await db_session.flush()
articles = []
for law_id, numbers in ((CODE_MINIER, ("1", "35", "36")), (CODE_TRAVAIL, ("1", "2"))):
for number in numbers:
articles.append(Article(
law_id=law_id, number=number,
content=(f"Dispositions de l'article {number}. "
"La superficie couverte par le permis de recherche est limitée."),
order=int(number), kind="article", embed=True,
))
db_session.add_all(articles)
await db_session.commit()
await db_session.execute(sql_text(REINDEX_LAWS_SQL))
await db_session.execute(sql_text(REINDEX_ARTICLES_SQL))
await db_session.commit()
return laws
@pytest.fixture
def service(db_session):
return SearchService(db_session, use_cache=False)
class TestParsing:
"""Le module unique remplace huit motifs « article N » divergents."""
@pytest.mark.parametrize("query,number,hint", [
("article 35 du code minier", "35", "code minier"),
("Article 35 de la Constitution", "35", "Constitution"),
("art. 35 du code du travail", "35", "code du travail"),
# « art.35 » sans espace : le motif du RAG exigeait `art\.?\s+`.
("art.35", "35", ""),
("article 35", "35", ""),
("article premier de la constitution", "1", "constitution"),
("article 1er du code minier", "1", "code minier"),
("article L 94 du code des impots", "L 94", "code des impots"),
])
def test_parses(self, query, number, hint):
reference = parse_reference(query)
assert reference == ArticleReference(number, hint), f"pour {query!r}"
@pytest.mark.parametrize("query", [
"code minier", "permis de recherche", "", "les articles du code",
])
def test_not_a_reference(self, query):
assert parse_reference(query) is None
@pytest.mark.parametrize("raw,expected", [
("Article 1er", "1"), ("PREMIER", "1"), ("1ÈRE", "1"),
("deuxieme", "2"), ("35", "35"), ("Art. 35", "35"), ("", ""),
])
def test_normalisation(self, raw, expected):
"""`articles.number` stocke « 1 » : rendre « PREMIER » ne matcherait rien."""
assert normalize_number(raw) == expected
class TestDirectNavigation:
@pytest.mark.asyncio
async def test_opens_the_right_document_on_the_right_article(self, service, codes):
response = await service.search(
SearchRequest(query="article 35 du code minier", mode="text", limit=10)
)
assert response.target_law_id == CODE_MINIER
assert response.target_article == "35"
assert response.direct_navigation is True
@pytest.mark.asyncio
async def test_the_document_hint_tolerates_a_typo(self, service, codes):
"""word_similarity('code miner', 'Loi ... portant Code Minier') = 0,727."""
response = await service.search(
SearchRequest(query="article 35 du code miner", mode="text", limit=10)
)
assert response.target_law_id == CODE_MINIER
assert response.direct_navigation is True
@pytest.mark.asyncio
async def test_the_hint_selects_between_two_codes(self, service, codes):
response = await service.search(
SearchRequest(query="article 1 du code du travail", mode="text", limit=10)
)
assert response.target_law_id == CODE_TRAVAIL
@pytest.mark.asyncio
async def test_an_absent_article_does_not_navigate(self, service, codes):
"""
Ouvrir un document sur un article qui n'y est pas serait pire que ne pas
l'ouvrir : l'utilisateur croirait avoir lu la reponse.
"""
response = await service.search(
SearchRequest(query="article 999 du code minier", mode="text", limit=10)
)
assert response.direct_navigation is False
assert response.target_article == "999"
assert response.results, "les resultats de recherche doivent rester"
@pytest.mark.asyncio
async def test_article_premier_resolves_to_the_stored_number(self, service, codes):
response = await service.search(
SearchRequest(query="article premier du code minier", mode="text", limit=10)
)
assert response.target_article == "1"
assert response.target_law_id == CODE_MINIER
@pytest.mark.asyncio
async def test_a_plain_query_does_not_navigate(self, service, codes):
response = await service.search(
SearchRequest(query="permis de recherche", mode="text", limit=10)
)
assert response.direct_navigation is False
assert response.target_article is None
class TestRemovedEndpoint:
@pytest.mark.asyncio
async def test_the_dead_article_endpoint_is_gone(self, client, db_session):
"""
Repondait 500 : il appelait `_parse_article_reference` ET
`_estimate_page_number`, aucun des deux defini dans ce module. Aucun
test ne le couvrait — c'est ce qui lui a permis de survivre.
"""
response = await client.get("/api/v1/search/article?q=article%205")
assert response.status_code == 404
|