Skip to content

Commit 3142ed5

Browse files
committed
fix(defects): unify auto-defect numbering on project_seq listener with retry
1 parent 2509e4d commit 3142ed5

1 file changed

Lines changed: 31 additions & 35 deletions

File tree

backend/app/crud_modules/test_management.py

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import logging
88
import re
99
from .. import schemas
10+
from ..retry_utils import seq_conflict_retry
1011
from ..services.execution_timing import apply_test_result_execution_timing
1112
from ..services.user_lifecycle import (
1213
create_user_invitation,
@@ -602,42 +603,37 @@ def _auto_create_defect_for_failed_result(db: Session, test_result: TestResult,
602603
# Get project_id from test run
603604
project_id = test_run.project_id
604605

605-
# Find the next defect number for this project
606-
all_defects = db.query(Defect).filter(
607-
Defect.project_id == project_id
608-
).all()
609-
610-
max_defect_number = 0
611-
for existing_defect in all_defects:
612-
if existing_defect.defect_id:
613-
import re
614-
match = re.search(r'(?:P\d+-)?DEF-(\d+)', existing_defect.defect_id)
615-
if match:
616-
defect_number = int(match.group(1))
617-
if defect_number > max_defect_number:
618-
max_defect_number = defect_number
619-
620-
# Generate defect_id
621-
defect_id = f"P{project_id}-DEF-{max_defect_number + 1:03d}"
622-
623-
# Create the defect
624-
new_defect = Defect(
625-
title=defect_title,
626-
description=defect_description,
627-
defect_id=defect_id,
628-
status=DefectStatus.OPEN,
629-
severity=DefectSeverity.MEDIUM,
630-
priority=DefectPriority.MEDIUM,
631-
project_id=project_id,
632-
test_case_id=test_case.id,
633-
test_run_id=test_run.id,
634-
reported_by=test_result.executed_by or 1, # Use executor or fallback to user 1
635-
actual_result=test_result.actual_result,
636-
)
606+
# Numbering is owned centrally by the project_seq before_insert listener
607+
# (services/sequence_service.py), which derives defect_id as
608+
# P{project_id}-DEF-{seq:03d}. Don't recompute it here: a second, manual
609+
# max-scan (the old full-table SELECT-all) is a divergent source of truth
610+
# that can disagree with the URL/badge number. We leave defect_id unset
611+
# and let the listener allocate it.
612+
#
613+
# MAX(project_seq)+1 can still race two concurrent failed-result inserts
614+
# into a unique-index (project_id, project_seq) collision — surfaced as an
615+
# IntegrityError on commit. Retry the whole build+commit so a fresh
616+
# instance re-runs allocation and picks the next free number.
617+
@seq_conflict_retry()
618+
def _insert_defect() -> Defect:
619+
new_defect = Defect(
620+
title=defect_title,
621+
description=defect_description,
622+
status=DefectStatus.OPEN,
623+
severity=DefectSeverity.MEDIUM,
624+
priority=DefectPriority.MEDIUM,
625+
project_id=project_id,
626+
test_case_id=test_case.id,
627+
test_run_id=test_run.id,
628+
reported_by=test_result.executed_by or 1, # executor or fallback to user 1
629+
actual_result=test_result.actual_result,
630+
)
631+
db.add(new_defect)
632+
safe_commit(db)
633+
db.refresh(new_defect)
634+
return new_defect
637635

638-
db.add(new_defect)
639-
safe_commit(db)
640-
db.refresh(new_defect)
636+
new_defect = _insert_defect()
641637

642638
# Create a link between the test result and the new defect
643639
defect_link = TestResultDefectLink(

0 commit comments

Comments
 (0)