From 2dbe6f7dbc05c65527d8e06198d9e2e8c65cad6f Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 13 Jul 2026 02:01:34 +0200 Subject: [PATCH 1/2] test: atomic matching on new container --- src/shared/tests/test_linkage.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/shared/tests/test_linkage.py b/src/shared/tests/test_linkage.py index c19957de..acfbe56c 100644 --- a/src/shared/tests/test_linkage.py +++ b/src/shared/tests/test_linkage.py @@ -1,5 +1,6 @@ from collections.abc import Callable from datetime import timedelta +from unittest import mock import pytest from django.test import override_settings @@ -549,3 +550,23 @@ def test_package_link_provenance_flags_merged_across_drvs( link.provenance_flags == ProvenanceFlags.PACKAGE_NAME_MATCH | ProvenanceFlags.PRODUCT_MATCH ) + + +@pytest.mark.xfail(reason="Not implemented", strict=True) +def test_build_new_links_is_atomic( + make_container: Callable[..., Container], + make_drv: Callable[..., NixDerivation], +) -> None: + make_drv(pname="foo") + container = make_container(package_name="foo") + + with mock.patch.object( + DerivationClusterProposalLink.objects, + "bulk_create", + side_effect=Exception("simulated DB failure"), + ): + with pytest.raises(Exception, match="simulated DB failure"): + build_new_links(container) + + # A retry must succeed after a failed first attempt + assert build_new_links(container) From 4a5dc615a0eea83a6376fabf852bc4c6bf5e42ba Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 13 Jul 2026 02:01:42 +0200 Subject: [PATCH 2/2] fix: atomic matching on new container --- src/shared/listeners/automatic_linkage.py | 53 ++++++++++++----------- src/shared/tests/test_linkage.py | 1 - 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/shared/listeners/automatic_linkage.py b/src/shared/listeners/automatic_linkage.py index b1e5f829..c3f109b5 100644 --- a/src/shared/listeners/automatic_linkage.py +++ b/src/shared/listeners/automatic_linkage.py @@ -268,35 +268,36 @@ def build_new_links(container: Container) -> bool: outcome = resolve_linkage_candidates(container) - proposal = CVEDerivationClusterProposal.objects.create( - cve=container.cve, - status=( - CVEDerivationClusterProposal.Status.REJECTED + with transaction.atomic(): + proposal = CVEDerivationClusterProposal.objects.create( + cve=container.cve, + status=( + CVEDerivationClusterProposal.Status.REJECTED + if outcome.rejection + else CVEDerivationClusterProposal.Status.PENDING + ), + rejection_reason=outcome.rejection.reason if outcome.rejection else None, + rejection_match_count=outcome.rejection.match_count or None if outcome.rejection - else CVEDerivationClusterProposal.Status.PENDING - ), - rejection_reason=outcome.rejection.reason if outcome.rejection else None, - rejection_match_count=outcome.rejection.match_count or None - if outcome.rejection - else None, - rejection_max_matches_limit=outcome.rejection.max_matches_limit or None - if outcome.rejection - else None, - algorithm_version=CVEDerivationClusterProposal.CURRENT_ALGORITHM_VERSION, - ) - - if outcome.derivations: - links = build_derivation_links(proposal, outcome.derivations) - DerivationClusterProposalLink.objects.bulk_create(links) - pkg_links = build_package_links(proposal, outcome.derivations) - PackageClusterProposalLink.objects.bulk_create(pkg_links) - logger.info( - "Matching suggestion for '%s': %d derivations, %d packages found.", - container.cve, - len(links), - len(pkg_links), + else None, + rejection_max_matches_limit=outcome.rejection.max_matches_limit or None + if outcome.rejection + else None, + algorithm_version=CVEDerivationClusterProposal.CURRENT_ALGORITHM_VERSION, ) + if outcome.derivations: + links = build_derivation_links(proposal, outcome.derivations) + DerivationClusterProposalLink.objects.bulk_create(links) + pkg_links = build_package_links(proposal, outcome.derivations) + PackageClusterProposalLink.objects.bulk_create(pkg_links) + logger.info( + "Matching suggestion for '%s': %d derivations, %d packages found.", + container.cve, + len(links), + len(pkg_links), + ) + return True diff --git a/src/shared/tests/test_linkage.py b/src/shared/tests/test_linkage.py index acfbe56c..1931512b 100644 --- a/src/shared/tests/test_linkage.py +++ b/src/shared/tests/test_linkage.py @@ -552,7 +552,6 @@ def test_package_link_provenance_flags_merged_across_drvs( ) -@pytest.mark.xfail(reason="Not implemented", strict=True) def test_build_new_links_is_atomic( make_container: Callable[..., Container], make_drv: Callable[..., NixDerivation],