fix(campaigns): preserve modification-request audit row after test deletion
Approving a remove_test modification request deletes the underlying Test row, which was cascading through test_id's ON DELETE CASCADE and wiping out the request row itself before it could be read back. Changed to ON DELETE SET NULL so the audit record (justification, reviewer, decision) survives. Adds regression coverage plus double-approve/reject idempotency tests.
This commit is contained in:
@@ -4,7 +4,7 @@ import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.campaign import Campaign, CampaignModificationRequest, CampaignTest
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.enums import TestState
|
||||
@@ -275,12 +275,41 @@ class TestModificationRequests:
|
||||
test_id=str(ct.test_id), justification="Test superseded",
|
||||
)
|
||||
db.commit()
|
||||
request_id = request.id
|
||||
|
||||
approve_modification_request(db, str(request.id), reviewer_id=admin_user.id)
|
||||
approve_modification_request(db, str(request_id), reviewer_id=admin_user.id)
|
||||
|
||||
cts = db.query(CampaignTest).filter(CampaignTest.campaign_id == active_campaign_with_test.id).all()
|
||||
assert len(cts) == 0
|
||||
|
||||
# The request row must survive the cascade-delete of the underlying Test
|
||||
# (ondelete="CASCADE" on CampaignModificationRequest.test_id) — this locks
|
||||
# in the fix where the request's status is persisted BEFORE the test is
|
||||
# removed, not after.
|
||||
reloaded = (
|
||||
db.query(CampaignModificationRequest)
|
||||
.filter(CampaignModificationRequest.id == request_id)
|
||||
.first()
|
||||
)
|
||||
assert reloaded is not None
|
||||
assert reloaded.status == "approved"
|
||||
assert reloaded.reviewed_by == admin_user.id
|
||||
assert reloaded.reviewed_at is not None
|
||||
|
||||
def test_approve_already_decided_request_raises(self, db, active_campaign_with_test, admin_user):
|
||||
ct = db.query(CampaignTest).filter(CampaignTest.campaign_id == active_campaign_with_test.id).first()
|
||||
request = create_modification_request(
|
||||
db, str(active_campaign_with_test.id),
|
||||
requester_id=admin_user.id, action="remove_test",
|
||||
test_id=str(ct.test_id), justification="Test superseded",
|
||||
)
|
||||
db.commit()
|
||||
approve_modification_request(db, str(request.id), reviewer_id=admin_user.id)
|
||||
db.commit()
|
||||
|
||||
with pytest.raises(BusinessRuleViolation, match="Only pending"):
|
||||
approve_modification_request(db, str(request.id), reviewer_id=admin_user.id)
|
||||
|
||||
def test_reject_request_leaves_campaign_untouched(self, db, active_campaign_with_test, admin_user):
|
||||
ct = db.query(CampaignTest).filter(CampaignTest.campaign_id == active_campaign_with_test.id).first()
|
||||
request = create_modification_request(
|
||||
@@ -310,6 +339,20 @@ class TestModificationRequests:
|
||||
with pytest.raises(BusinessRuleViolation, match="Review notes are required"):
|
||||
reject_modification_request(db, str(request.id), reviewer_id=admin_user.id, review_notes="")
|
||||
|
||||
def test_reject_already_decided_request_raises(self, db, active_campaign_with_test, admin_user):
|
||||
ct = db.query(CampaignTest).filter(CampaignTest.campaign_id == active_campaign_with_test.id).first()
|
||||
request = create_modification_request(
|
||||
db, str(active_campaign_with_test.id),
|
||||
requester_id=admin_user.id, action="remove_test",
|
||||
test_id=str(ct.test_id), justification="Test superseded",
|
||||
)
|
||||
db.commit()
|
||||
reject_modification_request(db, str(request.id), reviewer_id=admin_user.id, review_notes="Not needed")
|
||||
db.commit()
|
||||
|
||||
with pytest.raises(BusinessRuleViolation, match="Only pending"):
|
||||
reject_modification_request(db, str(request.id), reviewer_id=admin_user.id, review_notes="Again")
|
||||
|
||||
def test_list_modification_requests_filters_by_status(self, db, active_campaign_with_test, admin_user):
|
||||
ct = db.query(CampaignTest).filter(CampaignTest.campaign_id == active_campaign_with_test.id).first()
|
||||
create_modification_request(
|
||||
|
||||
Reference in New Issue
Block a user