Corrige/Melhora PidProviderXML devido a um bug e uma melhoria packtools 4.16.11 - #1469
Conversation
…packtools Adiciona fix_get_article_data() e fix_get_data_to_compare() como camadas de compatibilidade em torno de xml_with_pre.get_article_data() e xml_adapter.get_data_to_compare(), já que o formato retornado por esses métodos mudou entre versões do packtools (partial_body -> body_fragment; z_partial_body -> body_fragment_fingerprint). fix_get_article_data() remove a chave legada 'partial_body' do dict retornado. fix_get_data_to_compare() força a chave 'z_partial_body' a vir de xml_with_pre.body_fragment_fingerprint, independente do que get_data_to_compare() retornar. Em QueryBuilderPidProviderXML: - z_partial_body agora é lido diretamente de xml_adapter.xml_with_pre.z_partial_body (hash legado), não mais de xml_adapter.data. - z_body (body_fingerprint, hash do corpo inteiro) deixou de ser usado — removido de __init__ e de partial_body_query. - partial_body_query agora combina apenas z_partial_body (legado) e z_body_fragment (body_fragment_fingerprint), como set, caindo em isnull=True quando nenhum dos dois existe. - validate_input_data passa a checar a chave 'body_fragment' (novo nome) em vez de 'partial_body'.
Substitui as chamadas diretas a xml_with_pre.get_article_data() e xml_adapter.get_data_to_compare() por fix_get_article_data() e fix_get_data_to_compare(), garantindo que os dados usados em readable_data, no diff de conflito de pid v3 e nas comparações de registro fiquem normalizados entre versões do packtools. Também corrige get_registered_versions() para usar body_fragment_fingerprint (novo fingerprint) em vez do atributo antigo z_partial_body diretamente no dict de readable_data.
…al do código O arquivo de testes anterior descrevia um refactor que não estava implementado no código-fonte atual, fazendo 13 de 55 testes falharem. Principais ajustes: - make_xml_adapter() passa a configurar z_partial_body diretamente em xml_with_pre (não mais em adapter.data), e usa 'body_fragment' como chave do dict de get_article_data(), incluindo uma chave 'partial_body' só para exercitar o pop feito por fix_get_article_data(). - Remove body_fingerprint (corpo inteiro) das comparações de partial_body_query e article_data_query, já que esse fingerprint não é mais usado. - Adiciona testes cobrindo validate_input_data com a chave 'body_fragment' e o caso de branco. - Ajusta CompareTests para refletir que compare() usa input_data.get(label) e não pula labels ausentes, incluindo o caso de ZeroDivisionError quando registered_items é vazio.
…paração
get_best_match() agora recebe um dict de comparação que inclui a chave 'z_partial_body' (adicionada por fix_get_data_to_compare), então a asserção passa a usar ANY para esse valor em vez de comparar apenas por {'title': 'Foo'}.
Adiciona docstrings que faltavam (compare, compare_lists, compare_items, get_score, zero_to_none, fix_get_article_data) e corrige documentação desatualizada: - __init__ de QueryBuilderPidProviderXML: o comentário ainda descrevia um atributo z_body/body_fingerprint (fingerprint do corpo INTEIRO do artigo) que não é mais atribuído no código atual; substituído por uma docstring listando os atributos realmente definidos (z_body_fragment, z_partial_body, adapter_data, xml_with_pre_data). - partial_body_query: corrige a referência de onde vem o hash legado, que dizia vir de xml_adapter.z_partial_body mas na verdade é lido de xml_adapter.xml_with_pre.z_partial_body (via self.z_partial_body). - validate_input_data, pkg_name_list, article_data_query e get_article_data_query passam a ter docstrings explicando o comportamento. - Remove comentários de linha redundantes em pkg_name_list e identifier_queries que só repetiam o que o código já deixa claro, agora que a lógica está descrita na docstring do método.
|
|
||
|
|
||
| def fix_get_article_data(xml_with_pre, max_length=None): | ||
| data = xml_with_pre.get_article_data(max_length) |
There was a problem hiding this comment.
Atentar que aqui max_length tem valor padrão None e é repassado explicitamente ao Packtools.
No Packtools 4.16.11, get_article_data() usa 300 quando o argumento é omitido, mas get_article_data(None) chama get_body_fragment(None) e retorna o corpo inteiro.
Não seria melhor colocar o padrão de 300 caracteres ou por meio de max_length=300 ou chmando get_article_data() sem argumento quando max_length is None?
| "z_collab": self.z_collab, | ||
| "z_links": self.z_links, | ||
| "z_partial_body": self.z_partial_body, | ||
| "z_partial_body": self.body_fragment_fingerprint, |
There was a problem hiding this comment.
Não deveria ser self.xml_with_pre.body_fragment_fingerprint? O outro caminho gera AttributeError ao tentar usar o data_to_compare (PidProviderXML.data_to_compare)
pitangainnovare
left a comment
There was a problem hiding this comment.
Há dois comentários, sendo que em um deles menciona um AttributeError obtido.
Analisar.
- Renomeia a chave 'z_partial_body' para 'body_fragment_fingerprint' em fix_get_data_to_compare, alinhando o nome ao valor efetivamente comparado. - fix_get_article_data passa a priorizar xml_with_pre.readable_data quando disponível, caindo para get_article_data(max_length=300) como fallback. - compare_items passa a incluir 'input_data' na resposta quando o score não é 1, permitindo rastrear o valor comparado em caso de divergência.
…roviderXML
- Substitui os campos 'created'/'updated'/'record_status' fixos por uma property record_status, que calcula o estado do registro ('created' ou 'updated') com base na diferença de tempo entre criação e atualização.
- Adiciona 'ppx_id' ao dicionário de dados retornado.
- get_readable_data remove 'partial_body' do dado armazenado antes de retorná-lo.
- data_to_compare passa a incluir article_titles e body_fragment apenas quando presentes, e usa body_fragment_fingerprint no lugar de z_partial_body.
- get_best_match passa a usar um limiar de aceitação dinâmico (min_rate), reduzido para 0.49 quando há poucos dados disponíveis para comparação, e retorna também a resposta detalhada de cada comparação junto ao dado do candidato.
- Evita processar atualização quando o registro correspondente não possui readable_data.
- is_equal_to é considerado falso quando o registro não possui readable_data, evitando falso positivo de igualdade.
- Melhora a mensagem de erro de PidProviderXMLPidV3ConflictError, tornando-a mais informativa sobre o resultado do pareamento.
pitangainnovare
left a comment
There was a problem hiding this comment.
Os dois problemas apontados anteriormente foram resolvidos.
Os testes precisam ser adaptados aos comportamentos introduzidos nos últimos commits. Ao rodar os testes automáticos por meio de:
docker compose -p core -f local.yml run --rm --no-deps django pytest pid_provider -qForam obtidos 19 falhas:
WARN[0000] /home/rafaeljpd/Repos/pitanga/scl/core-pr-1469/local.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
Container core-django-run-16102b4ede99 Creating
Container core-django-run-16102b4ede99 Created
PostgreSQL is available
Test session starts (platform: linux, Python 3.11.13, pytest 7.4.3, pytest-sugar 0.9.7)
django: version: 5.2.7, settings: config.settings.test (from option)
rootdir: /app
configfile: pytest.ini
plugins: sugar-0.9.7, django-4.8.0, Faker-40.36.0, anyio-3.7.1, django-test-migrations-1.3.0
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PidProviderXMLBestMatchesTests.test_get_best_match_no_candidates_approved ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_get_best_match.PidProviderXMLBestMatchesTests testMethod=test_get_best_match_no_candidates_approved>, mock_compare = <MagicMock name='compare' id='124830705684432'>
@patch("pid_provider.models.compare")
def test_get_best_match_no_candidates_approved(self, mock_compare):
"""Quando nenhum candidato atinge score > 0.6, nem 'registered' nem 'matched' devem existir."""
item_fraco = MagicMock(spec=PidProviderXML)
item_fraco.id = 201
item_fraco.updated.isoformat.return_value = "2026-06-27T14:00:00"
item_fraco.data_to_compare = {"title": "Quase igual, mas nao o suficiente"}
item_fraco.data = {"id": 201, "title": "Quase igual, mas nao o suficiente"}
mock_compare.return_value = {"percentual_score": 0.48}
result = PidProviderXML.get_best_match([item_fraco], self.xml_adapter_data_mock)
self.assertNotIn("registered", result)
self.assertNotIn("matched", result)
self.assertEqual(len(result["unmatched"]), 1)
> self.assertEqual(result["unmatched"][0]["id"], 201)
E KeyError: 'id'
pid_provider/tests/test_get_best_match.py:67: KeyError
pid_provider/tests/test_get_best_match.py ⨯ 1% ▎
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PidProviderXMLBestMatchesTests.test_get_best_match_single_match_does_not_expose_matched_key ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_get_best_match.PidProviderXMLBestMatchesTests testMethod=test_get_best_match_single_match_does_not_expose_matched_key>, mock_compare = <MagicMock name='compare' id='124830711403856'>
@patch("pid_provider.models.compare")
def test_get_best_match_single_match_does_not_expose_matched_key(self, mock_compare):
"""Com apenas 1 item aprovado (>0.6), 'registered' deve existir mas 'matched' NÃO deve ser exposto."""
item_bom = MagicMock(spec=PidProviderXML)
item_bom.id = 101
item_bom.updated.isoformat.return_value = "2026-06-27T12:00:00"
item_bom.data_to_compare = {"title": "Titulo Original", "z_surnames": "Silva; Santos"}
item_bom.data = {"id": 101, "title": "Titulo Original", "z_surnames": "Silva; Santos"}
item_ruim = MagicMock(spec=PidProviderXML)
item_ruim.id = 102
item_ruim.updated.isoformat.return_value = "2026-06-27T13:00:00"
item_ruim.data_to_compare = {"title": "Outro Titulo Completamente Diferente", "z_surnames": "Alves"}
item_ruim.data = {"id": 102, "title": "Outro Titulo Completamente Diferente", "z_surnames": "Alves"}
def side_effect_compare(item_data, xml_adapter_data):
if item_data["title"] == "Titulo Original":
return {"percentual_score": 0.95}
return {"percentual_score": 0.20}
mock_compare.side_effect = side_effect_compare
# Enviados fora de ordem propositalmente
candidates = [item_ruim, item_bom]
result = PidProviderXML.get_best_match(candidates, self.xml_adapter_data_mock)
# Apenas 1 item passou do corte -> "matched" não deve aparecer
self.assertNotIn("matched", result)
# "registered" deve existir e ser o OBJETO item de maior score
self.assertEqual(result["registered"], item_bom)
# "unmatched" sempre é exposto
self.assertEqual(len(result["unmatched"]), 1)
> self.assertEqual(result["unmatched"][0]["id"], 102)
E KeyError: 'id'
pid_provider/tests/test_get_best_match.py:48: KeyError
pid_provider/tests/test_get_best_match.py ⨯ 2% ▎
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PidProviderXMLBestMatchesTests.test_get_best_match_three_matches_only_secondary_items_in_matched ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_get_best_match.PidProviderXMLBestMatchesTests testMethod=test_get_best_match_three_matches_only_secondary_items_in_matched>, mock_compare = <MagicMock name='compare' id='124830705618896'>
@patch("pid_provider.models.compare")
def test_get_best_match_three_matches_only_secondary_items_in_matched(self, mock_compare):
"""Com 3+ itens aprovados, 'registered' fica com o 1º colocado e 'matched' com os demais, na mesma ordem de score."""
item_1 = MagicMock(spec=PidProviderXML)
item_1.id = 401
item_1.updated.isoformat.return_value = "2026-06-01T00:00:00"
item_1.data_to_compare = {"title": "A"}
item_1.data = {"id": 401, "title": "A"}
item_2 = MagicMock(spec=PidProviderXML)
item_2.id = 402
item_2.updated.isoformat.return_value = "2026-06-01T00:00:00"
item_2.data_to_compare = {"title": "B"}
item_2.data = {"id": 402, "title": "B"}
item_3 = MagicMock(spec=PidProviderXML)
item_3.id = 403
item_3.updated.isoformat.return_value = "2026-06-01T00:00:00"
item_3.data_to_compare = {"title": "C"}
item_3.data = {"id": 403, "title": "C"}
def side_effect_compare(item_data, xml_adapter_data):
scores = {"A": 0.95, "B": 0.85, "C": 0.75}
return {"percentual_score": scores[item_data["title"]]}
mock_compare.side_effect = side_effect_compare
result = PidProviderXML.get_best_match([item_3, item_1, item_2], self.xml_adapter_data_mock)
# item_1 (0.95) é o de maior score -> vira "registered" e some da lista "matched"
self.assertEqual(result["registered"], item_1)
# "matched" deve conter apenas item_2 (0.85) e item_3 (0.75), nessa ordem
self.assertEqual(len(result["matched"]), 2)
> self.assertEqual(result["matched"][0]["id"], 402)
E KeyError: 'id'
pid_provider/tests/test_get_best_match.py:136: KeyError
pid_provider/tests/test_get_best_match.py ⨯ 3% ▍
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PidProviderXMLBestMatchesTests.test_get_best_match_two_matches_excludes_registered_from_matched ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_get_best_match.PidProviderXMLBestMatchesTests testMethod=test_get_best_match_two_matches_excludes_registered_from_matched>, mock_compare = <MagicMock name='compare' id='124830706124240'>
@patch("pid_provider.models.compare")
def test_get_best_match_two_matches_excludes_registered_from_matched(self, mock_compare):
"""Com 2 itens aprovados, 'registered' recebe o de maior score e 'matched' deve conter só o restante (matched[1:])."""
item_antigo = MagicMock(spec=PidProviderXML)
item_antigo.id = 301
item_antigo.updated.isoformat.return_value = "2026-01-01T00:00:00"
item_antigo.data_to_compare = {"title": "Clone"}
item_antigo.data = {"id": 301, "title": "Clone"}
item_recente = MagicMock(spec=PidProviderXML)
item_recente.id = 302
item_recente.updated.isoformat.return_value = "2026-06-27T00:00:00" # Mais recente
item_recente.data_to_compare = {"title": "Clone"}
item_recente.data = {"id": 302, "title": "Clone"}
# Mesmo score alto para os dois -> desempate por 'updated'
mock_compare.return_value = {"percentual_score": 0.90}
result = PidProviderXML.get_best_match([item_antigo, item_recente], self.xml_adapter_data_mock)
# reverse=True em (score, updated.isoformat(), id);
# "2026-06-27..." > "2026-01-01..." lexicograficamente, então item_recente vem primeiro (registered).
self.assertEqual(result["registered"], item_recente)
# "matched" agora é matched[1:] -> exclui o item que virou "registered"
self.assertIn("matched", result)
self.assertEqual(len(result["matched"]), 1)
> self.assertEqual(result["matched"][0]["id"], 301)
E KeyError: 'id'
pid_provider/tests/test_get_best_match.py:97: KeyError
pid_provider/tests/test_get_best_match.py ⨯ 4% ▌
pid_provider/tests/test_register.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 27% ██▊
pid_provider/tests/test_select_record.py ✓✓✓ 30% ███
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PidProviderXMLSelectRecordTests.test_select_record_passes_candidates_and_comparison_data_to_get_best_match ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_select_record.PidProviderXMLSelectRecordTests testMethod=test_select_record_passes_candidates_and_comparison_data_to_get_best_match>, mock_get_best_match = <MagicMock name='get_best_match' id='124830705917456'>
@patch("pid_provider.models.PidProviderXML.get_best_match")
def test_select_record_passes_candidates_and_comparison_data_to_get_best_match(self, mock_get_best_match):
"""get_best_match deve ser chamado com a lista de candidatos do label e os dados já processados do xml_adapter."""
candidates = self._make_results(1)
xml_adapter = self._make_xml_adapter(data_to_compare={"title": "Foo"})
mock_get_best_match.return_value = {"unmatched": ["ITEM_DATA"]}
PidProviderXML.select_record(xml_adapter, [("journal", candidates)])
> mock_get_best_match.assert_called_once_with(candidates, {"title": "Foo", "z_partial_body": ANY})
pid_provider/tests/test_select_record.py:216:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.11/unittest/mock.py:951: in assert_called_once_with
return self.assert_called_with(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='get_best_match' id='124830705917456'>, args = ([<MagicMock name='candidate_0' id='124830705915344'>], {'title': 'Foo', 'z_partial_body': <ANY>}), kwargs = {}
expected = call([<MagicMock name='candidate_0' id='124830705915344'>], {'title': 'Foo', 'z_partial_body': <ANY>})
actual = call([<MagicMock name='candidate_0' id='124830705915344'>], {'title': 'Foo', 'body_fragment_fingerprint': <MagicMock name='mock.xml_with_pre.body_fragment_fingerprint' id='124830705961424'>})
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7188691d5d00>, cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: get_best_match([<MagicMock name='candidate_0' id='124830705915344'>], {'title': 'Foo', 'z_partial_body': <ANY>})
E Actual: get_best_match([<MagicMock name='candidate_0' id='124830705915344'>], {'title': 'Foo', 'body_fragment_fingerprint': <MagicMock name='mock.xml_with_pre.body_fragment_fingerprint' id='124830705961424'>})
/usr/local/lib/python3.11/unittest/mock.py:939: AssertionError
pid_provider/tests/test_select_record.py ⨯✓✓✓✓✓ 37% ███▋
pid_provider/tests/test_select_records.py ✓✓✓ 40% ████
pid_provider/tests/test_query_params.py ✓✓✓✓ 44% ████▌
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ValidateInputDataTests.test_raises_not_enough_parameters_when_all_empty ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.ValidateInputDataTests testMethod=test_raises_not_enough_parameters_when_all_empty>
def test_raises_not_enough_parameters_when_all_empty(self):
adapter = make_xml_adapter(
data={"pub_year": "2026", "issn_electronic": "0000-1111"},
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> with self.assertRaises(exceptions.NotEnoughParametersToGetPidProviderXMLError):
E AssertionError: NotEnoughParametersToGetPidProviderXMLError not raised
pid_provider/tests/test_query_params.py:181: AssertionError
pid_provider/tests/test_query_params.py ⨯ 45% ████▌
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ValidateInputDataTests.test_raises_not_enough_parameters_when_body_fragment_is_blank ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.ValidateInputDataTests testMethod=test_raises_not_enough_parameters_when_body_fragment_is_blank>
def test_raises_not_enough_parameters_when_body_fragment_is_blank(self):
adapter = make_xml_adapter(
data={"pub_year": "2026", "issn_electronic": "0000-1111"},
body_fragment="",
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> with self.assertRaises(exceptions.NotEnoughParametersToGetPidProviderXMLError):
E AssertionError: NotEnoughParametersToGetPidProviderXMLError not raised
pid_provider/tests/test_query_params.py:200: AssertionError
pid_provider/tests/test_query_params.py ⨯ 46% ████▋
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ValidateInputDataTests.test_raises_not_enough_parameters_when_titles_are_blank ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.ValidateInputDataTests testMethod=test_raises_not_enough_parameters_when_titles_are_blank>
def test_raises_not_enough_parameters_when_titles_are_blank(self):
"""Lista de títulos só com valores falsy deve ser tratada como vazia."""
adapter = make_xml_adapter(
data={"pub_year": "2026", "issn_electronic": "0000-1111"},
article_titles=["", None],
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> with self.assertRaises(exceptions.NotEnoughParametersToGetPidProviderXMLError):
E AssertionError: NotEnoughParametersToGetPidProviderXMLError not raised
pid_provider/tests/test_query_params.py:191: AssertionError
pid_provider/tests/test_query_params.py ⨯✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 65% ██████▌
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PartialBodyQueryTests.test_deduplicates_when_both_hashes_are_equal ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.PartialBodyQueryTests testMethod=test_deduplicates_when_both_hashes_are_equal>
def test_deduplicates_when_both_hashes_are_equal(self):
adapter = make_xml_adapter(
data={},
z_partial_body="hash-igual",
body_fragment_fingerprint="hash-igual",
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> self.assertEqual(
qbuilder.partial_body_query, Q(z_partial_body__in={"hash-igual"})
)
E AssertionError: <Q: ([24 chars]n', {<MagicMock name='mock.z_partial_body' id=[31 chars]'}))> != <Q: ([24 chars]n', {'hash-igual'}))>
pid_provider/tests/test_query_params.py:377: AssertionError
pid_provider/tests/test_query_params.py ⨯ 66% ██████▋
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PartialBodyQueryTests.test_ignores_body_fingerprint ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.PartialBodyQueryTests testMethod=test_ignores_body_fingerprint>
def test_ignores_body_fingerprint(self):
adapter = make_xml_adapter(
data={},
z_partial_body="hash-legado",
body_fragment_fingerprint="hash-fragmento-corpo",
body_fingerprint="hash-corpo-inteiro-que-nao-deve-ser-usado",
)
qbuilder = QueryBuilderPidProviderXML(adapter)
expected = Q(
z_partial_body__in={"hash-legado", "hash-fragmento-corpo"}
)
> self.assertDictEqual(
dict(qbuilder.partial_body_query.children),
dict(expected.children),
)
E AssertionError: {'z_p[14 chars]n': {<MagicMock name='mock.z_partial_body' id=[39 chars]po'}} != {'z_p[14 chars]n': {'hash-legado', 'hash-fragmento-corpo'}}
E - {'z_partial_body__in': {'hash-fragmento-corpo',
E ? ^
E
E + {'z_partial_body__in': {'hash-legado', 'hash-fragmento-corpo'}}
E ? +++++++++++++++ ^^
E
E - <MagicMock name='mock.z_partial_body' id='124830711058960'>}}
pid_provider/tests/test_query_params.py:410: AssertionError
pid_provider/tests/test_query_params.py ⨯ 67% ██████▋
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PartialBodyQueryTests.test_uses_in_with_both_hashes_when_both_present_and_different ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.PartialBodyQueryTests testMethod=test_uses_in_with_both_hashes_when_both_present_and_different>
def test_uses_in_with_both_hashes_when_both_present_and_different(self):
adapter = make_xml_adapter(
data={},
z_partial_body="hash-legado",
body_fragment_fingerprint="hash-fragmento-corpo",
)
qbuilder = QueryBuilderPidProviderXML(adapter)
expected = Q(
z_partial_body__in={"hash-legado", "hash-fragmento-corpo"}
)
> self.assertDictEqual(
dict(qbuilder.partial_body_query.children),
dict(expected.children),
)
E AssertionError: {'z_p[20 chars]hash-fragmento-corpo', <MagicMock name='mock.z[33 chars]0'>}} != {'z_p[20 chars]hash-legado', 'hash-fragmento-corpo'}}
E - {'z_partial_body__in': {'hash-fragmento-corpo',
E ? ^
E
E + {'z_partial_body__in': {'hash-legado', 'hash-fragmento-corpo'}}
E ? +++++++++++++++ ^^
E
E - <MagicMock name='mock.z_partial_body' id='124830693212880'>}}
pid_provider/tests/test_query_params.py:365: AssertionError
pid_provider/tests/test_query_params.py ⨯ 68% ██████▊
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PartialBodyQueryTests.test_uses_in_with_only_body_fragment_fingerprint ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.PartialBodyQueryTests testMethod=test_uses_in_with_only_body_fragment_fingerprint>
def test_uses_in_with_only_body_fragment_fingerprint(self):
adapter = make_xml_adapter(
data={},
body_fragment_fingerprint="hash-fragmento-corpo",
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> self.assertEqual(
qbuilder.partial_body_query,
Q(z_partial_body__in={"hash-fragmento-corpo"}),
)
E AssertionError: <Q: ([24 chars]n', {<MagicMock name='mock.z_partial_body' id=[41 chars]'}))> != <Q: ([24 chars]n', {'hash-fragmento-corpo'}))>
pid_provider/tests/test_query_params.py:350: AssertionError
pid_provider/tests/test_query_params.py ⨯ 69% ██████▉
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PartialBodyQueryTests.test_uses_in_with_only_legacy_partial_body ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.PartialBodyQueryTests testMethod=test_uses_in_with_only_legacy_partial_body>
def test_uses_in_with_only_legacy_partial_body(self):
adapter = make_xml_adapter(
data={},
z_partial_body="hash-legado",
body_fragment_fingerprint=None,
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> self.assertEqual(
qbuilder.partial_body_query, Q(z_partial_body__in={"hash-legado"})
)
E AssertionError: <Q: ([24 chars]n', {<MagicMock name='mock.z_partial_body' id=[17 chars]>}))> != <Q: ([24 chars]n', {'hash-legado'}))>
pid_provider/tests/test_query_params.py:340: AssertionError
pid_provider/tests/test_query_params.py ⨯ 70% ███████
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― PartialBodyQueryTests.test_uses_isnull_when_neither_hash_is_present ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.PartialBodyQueryTests testMethod=test_uses_isnull_when_neither_hash_is_present>
def test_uses_isnull_when_neither_hash_is_present(self):
"""
Regressão do incidente: quando o XML de entrada não tem nenhum
hash de corpo, a query deve usar isnull=True (equivalente ao
antigo Q(z_partial_body=None)), e JAMAIS __in=(None, None), que
em SQL nunca casaria com candidatos cujo z_partial_body é NULL
(NULL = NULL é UNKNOWN, não True).
"""
adapter = make_xml_adapter(
data={}, z_partial_body=None, body_fragment_fingerprint=None
)
qbuilder = QueryBuilderPidProviderXML(adapter)
> self.assertEqual(qbuilder.partial_body_query, Q(z_partial_body__isnull=True))
E AssertionError: <Q: ([19 chars]dy__in', {<MagicMock name='mock.z_partial_body[22 chars]>}))> != <Q: ([19 chars]dy__isnull', True))>
pid_provider/tests/test_query_params.py:393: AssertionError
pid_provider/tests/test_query_params.py ⨯ 71% ███████▏
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ArticleDataQueryTests.test_combines_textual_fields_with_partial_body_query_both_hashes ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.ArticleDataQueryTests testMethod=test_combines_textual_fields_with_partial_body_query_both_hashes>
def test_combines_textual_fields_with_partial_body_query_both_hashes(self):
adapter = make_xml_adapter(
data={
"z_surnames": "Silva",
"z_collab": None,
"z_links": None,
},
z_partial_body="hash-legado",
body_fragment_fingerprint="hash-fragmento-corpo",
)
qbuilder = QueryBuilderPidProviderXML(adapter)
expected = Q(z_surnames="Silva", z_collab=None, z_links=None) & Q(
z_partial_body__in={"hash-legado", "hash-fragmento-corpo"}
)
> self.assertDictEqual(
dict(qbuilder.article_data_query.children),
dict(expected.children),
)
E AssertionError: {'z_c[78 chars]hash-fragmento-corpo', <MagicMock name='mock.z[33 chars]0'>}} != {'z_c[78 chars]hash-legado', 'hash-fragmento-corpo'}}
E {'z_collab': None,
E 'z_links': None,
E - 'z_partial_body__in': {'hash-fragmento-corpo',
E + 'z_partial_body__in': {'hash-legado', 'hash-fragmento-corpo'},
E ? +++++++++++++++ +
E
E - <MagicMock name='mock.z_partial_body' id='124830710734800'>},
E 'z_surnames': 'Silva'}
pid_provider/tests/test_query_params.py:437: AssertionError
pid_provider/tests/test_query_params.py ⨯ 72% ███████▎
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ArticleDataQueryTests.test_falls_back_to_isnull_when_no_body_hash_available ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.ArticleDataQueryTests testMethod=test_falls_back_to_isnull_when_no_body_hash_available>
def test_falls_back_to_isnull_when_no_body_hash_available(self):
adapter = make_xml_adapter(data={}, body_fragment_fingerprint=None)
qbuilder = QueryBuilderPidProviderXML(adapter)
expected = Q(z_surnames=None, z_collab=None, z_links=None) & Q(
z_partial_body__isnull=True
)
> self.assertEqual(qbuilder.article_data_query, expected)
E AssertionError: <Q: ([80 chars]dy__in', {<MagicMock name='mock.z_partial_body[22 chars]>}))> != <Q: ([80 chars]dy__isnull', True))>
pid_provider/tests/test_query_params.py:448: AssertionError
pid_provider/tests/test_query_params.py ⨯✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 89% ████████▉
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― CompareItemsTests.test_different_scalars_uses_how_similar_and_includes_registered ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.CompareItemsTests testMethod=test_different_scalars_uses_how_similar_and_includes_registered>, mock_how_similar = <MagicMock name='how_similar' id='124830705601680'>
@patch("pid_provider.query_params.how_similar")
def test_different_scalars_uses_how_similar_and_includes_registered(
self, mock_how_similar
):
mock_how_similar.return_value = 0.4
result = compare_items("z_surnames", "Silva", "Souza")
> self.assertEqual(
result, {"label": "z_surnames", "score": 0.4, "registered": "Silva"}
)
E AssertionError: {'lab[15 chars]s', 'score': 0.4, 'registered': 'Silva', 'input_data': 'Souza'} != {'lab[15 chars]s', 'score': 0.4, 'registered': 'Silva'}
E + {'label': 'z_surnames', 'registered': 'Silva', 'score': 0.4}
E - {'input_data': 'Souza',
E - 'label': 'z_surnames',
E - 'registered': 'Silva',
E - 'score': 0.4}
pid_provider/tests/test_query_params.py:596: AssertionError
pid_provider/tests/test_query_params.py ⨯✓✓✓ 94% █████████▍
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― CompareItemsTests.test_none_input_data_falls_back_to_empty_string_for_how_similar ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.CompareItemsTests testMethod=test_none_input_data_falls_back_to_empty_string_for_how_similar>, mock_how_similar = <MagicMock name='how_similar' id='124830692963024'>
@patch("pid_provider.query_params.how_similar")
def test_none_input_data_falls_back_to_empty_string_for_how_similar(
self, mock_how_similar
):
mock_how_similar.return_value = 0.2
result = compare_items("z_links", "algum-link", None)
> self.assertEqual(
result, {"label": "z_links", "score": 0.2, "registered": "algum-link"}
)
E AssertionError: {'lab[14 chars], 'score': 0.2, 'registered': 'algum-link', 'input_data': None} != {'lab[14 chars], 'score': 0.2, 'registered': 'algum-link'}
E + {'label': 'z_links', 'registered': 'algum-link', 'score': 0.2}
E - {'input_data': None,
E - 'label': 'z_links',
E - 'registered': 'algum-link',
E - 'score': 0.2}
pid_provider/tests/test_query_params.py:607: AssertionError
pid_provider/tests/test_query_params.py ⨯ 95% █████████▌
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― CompareItemsTests.test_none_registered_falls_back_to_empty_string_for_how_similar ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
self = <pid_provider.tests.test_query_params.CompareItemsTests testMethod=test_none_registered_falls_back_to_empty_string_for_how_similar>, mock_how_similar = <MagicMock name='how_similar' id='124830693055696'>
@patch("pid_provider.query_params.how_similar")
def test_none_registered_falls_back_to_empty_string_for_how_similar(
self, mock_how_similar
):
mock_how_similar.return_value = 0.3
result = compare_items("z_links", None, "algum-link")
> self.assertEqual(result, {"label": "z_links", "score": 0.3, "registered": None})
E AssertionError: {'lab[14 chars], 'score': 0.3, 'registered': None, 'input_data': 'algum-link'} != {'lab[14 chars], 'score': 0.3, 'registered': None}
E + {'label': 'z_links', 'registered': None, 'score': 0.3}
E - {'input_data': 'algum-link',
E - 'label': 'z_links',
E - 'registered': None,
E - 'score': 0.3}
pid_provider/tests/test_query_params.py:618: AssertionError
pid_provider/tests/test_query_params.py ⨯✓✓✓✓ 100% ██████████
====================================================================================================================== warnings summary =======================================================================================================================
../usr/local/lib/python3.11/site-packages/django/db/backends/utils.py:98
/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py:98: RuntimeWarning: Accessing the database during app initialization is discouraged. To fix this warning, avoid executing queries in AppConfig.ready() or when your app modules are imported.
warnings.warn(self.APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning)
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=================================================================================================================== short test summary info ===================================================================================================================
FAILED pid_provider/tests/test_get_best_match.py::PidProviderXMLBestMatchesTests::test_get_best_match_no_candidates_approved - KeyError: 'id'
FAILED pid_provider/tests/test_get_best_match.py::PidProviderXMLBestMatchesTests::test_get_best_match_single_match_does_not_expose_matched_key - KeyError: 'id'
FAILED pid_provider/tests/test_get_best_match.py::PidProviderXMLBestMatchesTests::test_get_best_match_three_matches_only_secondary_items_in_matched - KeyError: 'id'
FAILED pid_provider/tests/test_get_best_match.py::PidProviderXMLBestMatchesTests::test_get_best_match_two_matches_excludes_registered_from_matched - KeyError: 'id'
FAILED pid_provider/tests/test_select_record.py::PidProviderXMLSelectRecordTests::test_select_record_passes_candidates_and_comparison_data_to_get_best_match - AssertionError: expected call not found.
FAILED pid_provider/tests/test_query_params.py::ValidateInputDataTests::test_raises_not_enough_parameters_when_all_empty - AssertionError: NotEnoughParametersToGetPidProviderXMLError not raised
FAILED pid_provider/tests/test_query_params.py::ValidateInputDataTests::test_raises_not_enough_parameters_when_body_fragment_is_blank - AssertionError: NotEnoughParametersToGetPidProviderXMLError not raised
FAILED pid_provider/tests/test_query_params.py::ValidateInputDataTests::test_raises_not_enough_parameters_when_titles_are_blank - AssertionError: NotEnoughParametersToGetPidProviderXMLError not raised
FAILED pid_provider/tests/test_query_params.py::PartialBodyQueryTests::test_deduplicates_when_both_hashes_are_equal - AssertionError: <Q: ([24 chars]n', {<MagicMock name='mock.z_partial_body' id=[31 chars]'}))> != <Q: ([24 chars]n', {'hash-igual'}))>
FAILED pid_provider/tests/test_query_params.py::PartialBodyQueryTests::test_ignores_body_fingerprint - AssertionError: {'z_p[14 chars]n': {<MagicMock name='mock.z_partial_body' id=[39 chars]po'}} != {'z_p[14 chars]n': {'hash-legado', 'hash-fragmento-corpo'}}
FAILED pid_provider/tests/test_query_params.py::PartialBodyQueryTests::test_uses_in_with_both_hashes_when_both_present_and_different - AssertionError: {'z_p[20 chars]hash-fragmento-corpo', <MagicMock name='mock.z[33 chars]0'>}} != {'z_p[20 chars]hash-legado', 'hash-fragmento-corpo'}}
FAILED pid_provider/tests/test_query_params.py::PartialBodyQueryTests::test_uses_in_with_only_body_fragment_fingerprint - AssertionError: <Q: ([24 chars]n', {<MagicMock name='mock.z_partial_body' id=[41 chars]'}))> != <Q: ([24 chars]n', {'hash-fragmento-corpo'}))>
FAILED pid_provider/tests/test_query_params.py::PartialBodyQueryTests::test_uses_in_with_only_legacy_partial_body - AssertionError: <Q: ([24 chars]n', {<MagicMock name='mock.z_partial_body' id=[17 chars]>}))> != <Q: ([24 chars]n', {'hash-legado'}))>
FAILED pid_provider/tests/test_query_params.py::PartialBodyQueryTests::test_uses_isnull_when_neither_hash_is_present - AssertionError: <Q: ([19 chars]dy__in', {<MagicMock name='mock.z_partial_body[22 chars]>}))> != <Q: ([19 chars]dy__isnull', True))>
FAILED pid_provider/tests/test_query_params.py::ArticleDataQueryTests::test_combines_textual_fields_with_partial_body_query_both_hashes - AssertionError: {'z_c[78 chars]hash-fragmento-corpo', <MagicMock name='mock.z[33 chars]0'>}} != {'z_c[78 chars]hash-legado', 'hash-fragmento-corpo'}}
FAILED pid_provider/tests/test_query_params.py::ArticleDataQueryTests::test_falls_back_to_isnull_when_no_body_hash_available - AssertionError: <Q: ([80 chars]dy__in', {<MagicMock name='mock.z_partial_body[22 chars]>}))> != <Q: ([80 chars]dy__isnull', True))>
FAILED pid_provider/tests/test_query_params.py::CompareItemsTests::test_different_scalars_uses_how_similar_and_includes_registered - AssertionError: {'lab[15 chars]s', 'score': 0.4, 'registered': 'Silva', 'input_data': 'Souza'} != {'lab[15 chars]s', 'score': 0.4, 'registered': 'Silva'}
FAILED pid_provider/tests/test_query_params.py::CompareItemsTests::test_none_input_data_falls_back_to_empty_string_for_how_similar - AssertionError: {'lab[14 chars], 'score': 0.2, 'registered': 'algum-link', 'input_data': None} != {'lab[14 chars], 'score': 0.2, 'registered': 'algum-link'}
FAILED pid_provider/tests/test_query_params.py::CompareItemsTests::test_none_registered_falls_back_to_empty_string_for_how_similar - AssertionError: {'lab[14 chars], 'score': 0.3, 'registered': None, 'input_data': 'algum-link'} != {'lab[14 chars], 'score': 0.3, 'registered': None}
Results (2.93s):
74 passed
19 failed
- pid_provider/tests/test_get_best_match.py:50 PidProviderXMLBestMatchesTests.test_get_best_match_no_candidates_approved
- pid_provider/tests/test_get_best_match.py:13 PidProviderXMLBestMatchesTests.test_get_best_match_single_match_does_not_expose_matched_key
- pid_provider/tests/test_get_best_match.py:101 PidProviderXMLBestMatchesTests.test_get_best_match_three_matches_only_secondary_items_in_matched
- pid_provider/tests/test_get_best_match.py:69 PidProviderXMLBestMatchesTests.test_get_best_match_two_matches_excludes_registered_from_matched
- pid_provider/tests/test_select_record.py:205 PidProviderXMLSelectRecordTests.test_select_record_passes_candidates_and_comparison_data_to_get_best_match
- pid_provider/tests/test_query_params.py:176 ValidateInputDataTests.test_raises_not_enough_parameters_when_all_empty
- pid_provider/tests/test_query_params.py:194 ValidateInputDataTests.test_raises_not_enough_parameters_when_body_fragment_is_blank
- pid_provider/tests/test_query_params.py:184 ValidateInputDataTests.test_raises_not_enough_parameters_when_titles_are_blank
- pid_provider/tests/test_query_params.py:370 PartialBodyQueryTests.test_deduplicates_when_both_hashes_are_equal
- pid_provider/tests/test_query_params.py:398 PartialBodyQueryTests.test_ignores_body_fingerprint
- pid_provider/tests/test_query_params.py:355 PartialBodyQueryTests.test_uses_in_with_both_hashes_when_both_present_and_different
- pid_provider/tests/test_query_params.py:344 PartialBodyQueryTests.test_uses_in_with_only_body_fragment_fingerprint
- pid_provider/tests/test_query_params.py:333 PartialBodyQueryTests.test_uses_in_with_only_legacy_partial_body
- pid_provider/tests/test_query_params.py:381 PartialBodyQueryTests.test_uses_isnull_when_neither_hash_is_present
- pid_provider/tests/test_query_params.py:423 ArticleDataQueryTests.test_combines_textual_fields_with_partial_body_query_both_hashes
- pid_provider/tests/test_query_params.py:442 ArticleDataQueryTests.test_falls_back_to_isnull_when_no_body_hash_available
- pid_provider/tests/test_query_params.py:590 CompareItemsTests.test_different_scalars_uses_how_similar_and_includes_registered
- pid_provider/tests/test_query_params.py:601 CompareItemsTests.test_none_input_data_falls_back_to_empty_string_for_how_similar
- pid_provider/tests/test_query_params.py:612 CompareItemsTests.test_none_registered_falls_back_to_empty_string_for_how_similarCobre PidProviderSetting (ex.: record_all_registration_events e demais opções configuráveis via Wagtail Admin). Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre o modelo XMLVersion (versionamento de XML associado a PidProviderXML). Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre geração/resolução de URLs associadas ao XML registrado. Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre o relacionamento OtherPid/InlinePanel 'Other PID' em PidProviderXML. Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre lógica de fix/normalização do PID v2 (ex.: CollectionPidV2 ou rotina de correção equivalente). Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre funções utilitárias usadas por PidProviderXML (possivelmente os wrappers fix_get_article_data/fix_get_data_to_compare ou equivalentes). Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre rotinas de manutenção/limpeza sobre registros existentes de PidProviderXML. Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
Cobre o modelo PidProviderXMLRegistration e a lógica de quando gravar eventos de auditoria (erro, ambiguidade ou record_all_registration_events). Mensagem gerada a partir do nome do arquivo — ajustar detalhes de implementação após revisão do conteúdo.
…z_partial_body Ajusta make_xml_adapter() para configurar explicitamente adapter.z_partial_body (agora lido como atributo direto pelo QueryBuilderPidProviderXML, não mais via xml_adapter.data.get()), evitando MagicMock não configurado no teste. CompareItemsTests: passa a esperar também a chave 'input_data' no retorno de compare_items() quando o score é != 1, além de 'registered'. CompareTests, reescrito para refletir que compare() NÃO pula mais labels ausentes em input_data (usa .get(label), tratando ausência como None): - test_missing_input_key_is_treated_as_none_not_skipped (renomeado de ...is_skipped_not_treated_as_none): label ausente com valor registrado falsy agora ENTRA em items com score 1, em vez de ser descartado. - test_missing_input_key_with_truthy_registered_value_lowers_score (novo): label ausente com valor registrado truthy cai no ramo how_similar (score < 1). - test_empty_registered_items_raises_zero_division_error (renomeado de test_all_labels_missing_raises_zero_division_error): único cenário que ainda levanta ZeroDivisionError é registered_items vazio, não mais 'todos os labels ausentes em input_data'.
…response
get_best_match() passou a envolver cada candidato em
{'data': ..., 'response': ...} nas listas 'matched'/'unmatched', em
vez do dict .data cru. Ajusta as asserções de
result['unmatched'][0]['id'] e result['matched'][N]['id'] para
result[...][N]['data']['id'] nos quatro testes afetados.
select_record() não usa mais xml_adapter.get_data_to_compare() puro: passa a chamar fix_get_data_to_compare(xml_adapter), que acrescenta a chave 'body_fragment_fingerprint' (lida de xml_adapter.xml_with_pre.body_fragment_fingerprint) ao dict retornado por get_data_to_compare(). _make_xml_adapter() passa a aceitar body_fragment_fingerprint e configurá-lo no mock, para não ficar um MagicMock não configurado. test_select_record_passes_candidates_and_comparison_data_to_get_best_match atualizado para esperar que get_best_match seja chamado com o dict já acrescido de 'body_fragment_fingerprint', não mais o retorno cru de get_data_to_compare().
Alinha os testes à mudança de register(), que agora faz input_data.update(xml_with_pre.readable_data) em vez de usar get_article_data(). O helper make_xml_with_pre() passa a configurar readable_data como um dict de verdade (com surnames, collab, links, article_titles, body_fragment) e body_fragment_fingerprint, já que um MagicMock não configurado quebra dict.update() com TypeError — erro que era silenciosamente capturado pelo except Exception de register() e mascarado como event_status='error'. Adiciona patch de PidProviderSetting.load no setUp da classe base (RegisterTestBase), desativado por padrão, para desacoplar os testes da configuração persistida e permitir reuso em RecordAllEventsSettingTest sem duplicar o patch. Adiciona RegisterResponseSchemaTest como teste de contrato: valida apenas as chaves presentes em cada nível do response de register() (sucesso limpo, erro, conflito, skipped), incluindo verificação explícita de que as chaves de readable_data substituem as antigas (ex.: ausência de 'partial_body'). Funciona como sentinela contra mudanças silenciosas de schema que testes anteriores, focados apenas em event_status/v3, não detectavam.
Remove a chave 'finger_print' solta em record_data() e passa a aninhar o retorno de get_readable_data() sob a chave 'registered_data', em vez de mesclá-lo (update) diretamente no nível superior do dict. Isso evita colisão de chaves entre os dados legíveis e os demais campos do registro.
O que esse PR faz?
Corrige, no próprio core, o efeito de 2 bugs existentes no packtools 4.16.11, sem precisar atualizar a versão da dependência:
XMLWithPre.get_article_data()retorna uma chavepartial_bodyque não deveria fazer parte desse dict — ela é resquício de um formato antigo e não deveria mais ser exposta ali.PidProviderXMLAdapter.get_data_to_compare()retorna a chavez_partial_bodyno lugar debody_fragment_fingerprint, que é o valor que de fato deveria ser usado na comparação de corpo do artigo.Como não é possível alterar o pacote packtools neste momento, este PR resolve os dois problemas isolando-os em funções wrapper no core (
fix_get_article_data()efix_get_data_to_compare()), que corrigem o dado depois que ele sai do packtools, antes de ser usado no restante dopid_provider:fix_get_article_data()remove a chave indevidapartial_bodydo dict retornado porxml_with_pre.get_article_data()(bug 1).fix_get_data_to_compare()sobrescrevez_partial_bodyno dict retornado porxml_adapter.get_data_to_compare()com o valor correto,xml_adapter.xml_with_pre.body_fragment_fingerprint(bug 2).QueryBuilderPidProviderXMLpara ler o hash legado (z_partial_body) diretamente dexml_adapter.xml_with_pre.z_partial_body(não mais dexml_adapter.data, que era afetado pelo bug 2), remove o uso do fingerprint do corpo inteiro (z_body/body_fingerprint, que não deveria ter sido usado empartial_body_query) e ajustavalidate_input_datapara checar a chave corretabody_fragmentno lugar departial_body.pid_provider/models.py, ondereadable_data, o diff de conflito de pid v3 e as comparações de registro passam a usar as funções wrapper em vez de chamar os métodos do packtools diretamente.test_query_params.pyetest_select_record.pycom o comportamento real do código (13 de 55 testes estavam falhando antes por descreverem um refactor não implementado).compare,compare_lists,compare_items,get_score,zero_to_none,fix_get_article_data,validate_input_data,pkg_name_list,article_data_query,get_article_data_query) e corrige comentários desatualizados no__init__deQueryBuilderPidProviderXMLe empartial_body_query, que ainda referenciavam um atributo (z_body/body_fingerprint) e uma origem de dado (xml_adapter.z_partial_bodya partir deadapter.data) que não refletem mais o código atual.Onde a revisão poderia começar?
pid_provider/query_params.py— em especialfix_get_data_to_compare()efix_get_article_data()(os wrappers que corrigem os dois bugs do packtools) eQueryBuilderPidProviderXML.partial_body_query, que consome esses dados corrigidos. Em seguida,pid_provider/models.pypara ver onde essas funções passam a ser chamadas no lugar dos métodos originais do packtools.Como este poderia ser testado manualmente?
4.16.11(não houve bump em requirements/setup — a correção é só no core).pytest pid_provider/tests/test_query_params.py pid_provider/tests/test_select_record.py(todos devem passar).readable_datanão contém mais a chavepartial_body.z_partial_bodylegado (ex.: "ARTIGO DE REVISÃO") mas corpos diferentes não colidem mais em uma mesma query de correspondência — evidência de que a comparação passou a usarbody_fragment_fingerprintcorretamente, e não o valor incorreto vindo do bug 2.Algum cenário de contexto que queira dar?
Foram identificados 2 bugs no packtools 4.16.11:
XMLWithPre.get_article_data()expõe uma chavepartial_bodyque não deveria fazer parte desse dict.PidProviderXMLAdapter.get_data_to_compare()retornaz_partial_bodyno lugar do valor correto,body_fragment_fingerprint.Corrigir isso no próprio pacote exigiria uma nova release do packtools e, potencialmente, uma atualização de versão no core — o que traria mudanças de escopo maior nos requirements e não é desejável agora. Por isso, optamos por contornar os dois bugs isoladamente no código do
pid_provider, via funções wrapper (fix_get_article_data()efix_get_data_to_compare()), mantendo o packtools fixado em4.16.11. Isso resolve o problema imediato sem tocar em requirements, e essas correções podem ser removidas do core quando o packtools corrigir os bugs em uma versão futura.Screenshots
Não aplicável.
Quais são os tickets relevantes?
#1470
Referências
Segurança da informação (NSI.04)
Este PR manipula dados sensíveis ou pessoais (LGPD)?
Este PR altera autenticação, autorização, controle de acesso ou gerenciamento de sessão?
Este PR introduz, atualiza ou remove dependências de terceiros?
Este PR foi validado pelo pipeline de segurança (SonarQube / Trivy)?
Este PR concatena, monta ou executa comandos SQL, HTML ou JavaScript a partir de entrada externa?
Este PR expõe novos endpoints, telas ou serviços?
Algum segredo, senha, chave ou token está sendo adicionado ao código-fonte?