@@ -257,3 +257,91 @@ def test_get_or_create_advisory_commit(advisory_commit):
257257 assert isinstance (commit , PackageCommitPatch )
258258 assert commit .commit_hash in [c .commit_hash for c in advisory_commit ]
259259 assert commit .vcs_url in [c .vcs_url for c in advisory_commit ]
260+
261+
262+ @pytest .mark .django_db
263+ def test_insert_advisory_v2_handles_multiple_objects_returned ():
264+ """
265+ Test that insert_advisory_v2 correctly handles AdvisoryV2.MultipleObjectsReturned
266+ exception when duplicate advisory records exist.
267+
268+ This test verifies the fix for issue #2081 where the exception handler was
269+ incorrectly catching Advisory.MultipleObjectsReturned instead of
270+ AdvisoryV2.MultipleObjectsReturned.
271+ """
272+ from unittest .mock import MagicMock
273+ from django .db import connection
274+ from vulnerabilities .models import AdvisoryV2
275+ from vulnerabilities .pipes .advisory import insert_advisory_v2
276+ from vulnerabilities .utils import compute_content_id
277+
278+ # Create advisory data
279+ advisory_data = AdvisoryData (
280+ summary = "Test advisory for exception handling" ,
281+ affected_packages = [
282+ AffectedPackage (
283+ package = PackageURL (type = "pypi" , name = "test-package" ),
284+ affected_version_range = VersionRange .from_string ("vers:pypi/>=1.0.0|<=2.0.0" ),
285+ )
286+ ],
287+ references = [Reference (url = "https://example.com/advisory/test" )],
288+ date_published = timezone .now (),
289+ url = "https://test-advisory.example.com/duplicated" ,
290+ advisory_id = "TEST-2024-001" ,
291+ )
292+
293+ content_id = compute_content_id (advisory_data = advisory_data )
294+
295+ # Create the first advisory normally
296+ advisory1 = AdvisoryV2 .objects .create (
297+ unique_content_id = content_id ,
298+ url = advisory_data .url ,
299+ datasource_id = "test_pipeline" ,
300+ advisory_id = advisory_data .advisory_id ,
301+ avid = f"test_pipeline/{ advisory_data .advisory_id } " ,
302+ summary = advisory_data .summary ,
303+ date_collected = timezone .now (),
304+ )
305+
306+ # Force create a duplicate by bypassing the unique constraint at the model level
307+ # This simulates a database inconsistency that could trigger MultipleObjectsReturned
308+ with connection .cursor () as cursor :
309+ # Temporarily disable the unique constraint to create a duplicate
310+ cursor .execute (
311+ """
312+ INSERT INTO vulnerabilities_advisoryv2
313+ (unique_content_id, url, datasource_id, advisory_id, avid, summary, date_collected, status)
314+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
315+ """ ,
316+ [
317+ content_id ,
318+ advisory_data .url ,
319+ "test_pipeline" ,
320+ advisory_data .advisory_id ,
321+ f"test_pipeline/{ advisory_data .advisory_id } " ,
322+ advisory_data .summary ,
323+ timezone .now (),
324+ 1 , # AdvisoryStatusType.PUBLISHED
325+ ]
326+ )
327+
328+ # Verify we have duplicates
329+ assert AdvisoryV2 .objects .filter (unique_content_id = content_id , url = advisory_data .url ).count () == 2
330+
331+ # Create a mock logger to verify error logging
332+ mock_logger = MagicMock ()
333+
334+ # Now calling insert_advisory_v2 should raise AdvisoryV2.MultipleObjectsReturned
335+ with pytest .raises (AdvisoryV2 .MultipleObjectsReturned ):
336+ insert_advisory_v2 (
337+ advisory = advisory_data ,
338+ pipeline_id = "test_pipeline" ,
339+ logger = mock_logger ,
340+ )
341+
342+ # Verify that the error was logged
343+ mock_logger .error .assert_called_once ()
344+ error_message = mock_logger .error .call_args [0 ][0 ]
345+ assert "Multiple Advisories returned" in error_message
346+ assert content_id in error_message
347+ assert advisory_data .url in error_message
0 commit comments