Skip to content

Commit bf58868

Browse files
authored
prevent attempting to update a diff for a deleted branch (#7668)
* prevent attempting to update a diff for a deleted branch * add test for log message * prevent attempting a diff update on a deleted branch
1 parent c1b3702 commit bf58868

File tree

7 files changed

+120
-16
lines changed

7 files changed

+120
-16
lines changed

backend/infrahub/core/branch/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,9 @@ async def get_by_name(cls, name: str, db: InfrahubDatabase, ignore_deleting: boo
141141
"""
142142

143143
params: dict[str, Any] = {"name": name}
144+
params["ignore_statuses"] = []
144145
if ignore_deleting:
145-
params["ignore_statuses"] = [BranchStatus.DELETING.value]
146+
params["ignore_statuses"].append(BranchStatus.DELETING.value)
146147

147148
results = await db.execute_query(query=query, params=params, name="branch_get_by_name", type=QueryType.READ)
148149

backend/infrahub/core/branch/tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,9 +502,13 @@ async def post_process_branch_merge(source_branch: str, target_branch: str, cont
502502
parameters={"branch": target_branch, "source": GeneratorDefinitionRunSource.MERGE},
503503
)
504504

505+
active_branches = await Branch.get_list(db=db)
506+
active_branch_names = {branch.name for branch in active_branches}
507+
505508
for diff_root in branch_diff_roots:
506509
if (
507510
diff_root.base_branch_name != diff_root.diff_branch_name
511+
and diff_root.diff_branch_name in active_branch_names
508512
and diff_root.tracking_id
509513
and isinstance(diff_root.tracking_id, BranchTrackingId)
510514
):

backend/infrahub/core/diff/tasks.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from infrahub.core.diff.models import RequestDiffUpdate # noqa: TC001 needed for prefect flow
99
from infrahub.core.diff.repository.repository import DiffRepository
1010
from infrahub.dependencies.registry import get_component_registry
11+
from infrahub.exceptions import BranchNotFoundError
1112
from infrahub.log import get_logger
1213
from infrahub.workers.dependencies import get_database, get_workflow
1314
from infrahub.workflows.catalogue import DIFF_REFRESH
@@ -24,7 +25,11 @@ async def update_diff(model: RequestDiffUpdate) -> None:
2425
async with database.start_session(read_only=False) as db:
2526
component_registry = get_component_registry()
2627
base_branch = await registry.get_branch(db=db, branch=registry.default_branch)
27-
diff_branch = await registry.get_branch(db=db, branch=model.branch_name)
28+
try:
29+
diff_branch = await registry.get_branch(db=db, branch=model.branch_name)
30+
except BranchNotFoundError:
31+
log.warn(f"Branch {model.branch_name} not found, skipping diff update")
32+
return
2833

2934
diff_coordinator = await component_registry.get_component(DiffCoordinator, db=db, branch=diff_branch)
3035

backend/tests/integration/diff/test_diff_update.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from tests.adapters.message_bus import BusSimulator
3030

3131
BRANCH_NAME = "branch1"
32+
DELETED_BRANCH_NAME = "deleted_branch"
3233
PROPOSED_CHANGE_NAME = "branch1-pc"
3334
DIFF_UPDATE_QUERY = """
3435
mutation DiffUpdate($branch_name: String!, $wait_for_completion: Boolean = true) {
@@ -195,15 +196,36 @@ async def create_diff(self, db: InfrahubDatabase, initial_dataset, client: Infra
195196
john.age.value = 26 # type: ignore[attr-defined]
196197
await john.save(db=db)
197198

199+
@pytest.fixture(scope="class")
200+
async def deleted_branch(self, db: InfrahubDatabase) -> Branch:
201+
return await create_branch(db=db, branch_name=DELETED_BRANCH_NAME)
202+
203+
@pytest.fixture(scope="class")
204+
async def diff_on_deleted_branch(
205+
self, db: InfrahubDatabase, initial_dataset, client: InfrahubClient, deleted_branch: Branch
206+
) -> EnrichedDiffRoot:
207+
kara = await NodeManager.get_one(db=db, branch=deleted_branch, id=initial_dataset["kara"].id)
208+
kara.description.value = "I think she's an angel now"
209+
await kara.save(db=db)
210+
211+
result = await client.execute_graphql(query=DIFF_UPDATE_QUERY, variables={"branch_name": DELETED_BRANCH_NAME})
212+
assert result["DiffUpdate"]["ok"]
213+
214+
diff = await self.get_branch_diff(db=db, branch=deleted_branch)
215+
assert len(diff.nodes) == 1
216+
217+
await deleted_branch.delete(db=db)
218+
return diff
219+
198220
@staticmethod
199221
async def get_branch_diff(db: InfrahubDatabase, branch: Branch) -> EnrichedDiffRoot:
200222
# Validate if the diff has been updated properly
201223
component_registry = get_component_registry()
202224
diff_repo = await component_registry.get_component(DiffRepository, db=db, branch=branch)
203225

204226
return await diff_repo.get_one(
205-
tracking_id=BranchTrackingId(name=BRANCH_NAME),
206-
diff_branch_name=BRANCH_NAME,
227+
tracking_id=BranchTrackingId(name=branch.name),
228+
diff_branch_name=branch.name,
207229
)
208230

209231
async def _get_proposed_change_and_data_validator(self, db) -> tuple[Node, Node]:
@@ -222,7 +244,7 @@ async def _get_proposed_change_and_data_validator(self, db) -> tuple[Node, Node]
222244
return (pc, data_validator)
223245

224246
async def test_diff_first_update(
225-
self, db: InfrahubDatabase, initial_dataset, create_diff, client: InfrahubClient
247+
self, db: InfrahubDatabase, initial_dataset, create_diff, diff_on_deleted_branch, client: InfrahubClient
226248
) -> None:
227249
"""Validate if the diff is properly created the first time"""
228250

@@ -1147,7 +1169,13 @@ async def test_create_another_proposed_change_data_checks_created(
11471169
assert len(core_data_checks) == len(data_checks_by_conflict_id)
11481170

11491171
async def test_merge_proposed_change(
1150-
self, db: InfrahubDatabase, initial_dataset, default_branch, client: InfrahubClient
1172+
self,
1173+
db: InfrahubDatabase,
1174+
initial_dataset,
1175+
default_branch,
1176+
diff_on_deleted_branch: EnrichedDiffRoot,
1177+
deleted_branch: Branch,
1178+
client: InfrahubClient,
11511179
) -> None:
11521180
pc, _ = await self._get_proposed_change_and_data_validator(db=db)
11531181
result = await client.execute_graphql(
@@ -1224,3 +1252,7 @@ async def test_merge_proposed_change(
12241252
murphy_customer_rel = omnicorp_customers_rels_by_peer_id[murphy_id]
12251253
owner_of_property = await murphy_customer_rel.get_owner(db=db)
12261254
assert owner_of_property.get_id() == cardinality_many_property_conflict.expected_value
1255+
1256+
# validate diff not updated for deleted branch
1257+
fresh_deleted_branch_diff = await self.get_branch_diff(db=db, branch=deleted_branch)
1258+
assert fresh_deleted_branch_diff.to_time == diff_on_deleted_branch.to_time
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from unittest.mock import AsyncMock, patch
2+
3+
import pytest
4+
5+
from infrahub.core.branch import Branch
6+
from infrahub.core.diff.models import RequestDiffUpdate
7+
from infrahub.core.diff.tasks import update_diff
8+
from infrahub.database import InfrahubDatabase
9+
from infrahub.dependencies.component.registry import ComponentDependencyRegistry
10+
from infrahub.dependencies.registry import get_component_registry
11+
12+
13+
class DummyComponentRegistry:
14+
async def get_component(self, *args, **kwargs):
15+
mock_component = AsyncMock()
16+
# Needed so .run_update can be awaited and does nothing
17+
mock_component.run_update = AsyncMock()
18+
return mock_component
19+
20+
21+
@pytest.fixture
22+
def wrapped_component_registry() -> ComponentDependencyRegistry:
23+
component_registry = get_component_registry()
24+
wrapped_component_registry = AsyncMock(wraps=component_registry)
25+
26+
with patch("infrahub.dependencies.registry.get_component_registry", return_value=wrapped_component_registry):
27+
yield wrapped_component_registry
28+
29+
30+
async def test_diff_update_for_deleted_branch(
31+
db: InfrahubDatabase,
32+
default_branch: Branch,
33+
wrapped_component_registry: ComponentDependencyRegistry,
34+
caplog: pytest.LogCaptureFixture,
35+
) -> None:
36+
diff_update = RequestDiffUpdate(branch_name="pretend_branch", name="diff")
37+
38+
await update_diff(diff_update)
39+
40+
wrapped_component_registry.get_component.assert_not_awaited()
41+
assert "Branch pretend_branch not found, skipping diff update" in caplog.text

backend/tests/unit/message_bus/operations/event/test_branch.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
from infrahub.core.diff.model.path import BranchTrackingId, EnrichedDiffRoot, NameTrackingId
1111
from infrahub.core.diff.models import RequestDiffUpdate
1212
from infrahub.core.diff.repository.repository import DiffRepository
13+
from infrahub.core.initialization import create_branch
1314
from infrahub.core.timestamp import Timestamp
15+
from infrahub.database import InfrahubDatabase
1416
from infrahub.dependencies.component.registry import ComponentDependencyRegistry
1517
from infrahub.generators.constants import GeneratorDefinitionRunSource
1618
from infrahub.services import InfrahubServices
@@ -40,7 +42,9 @@ def context():
4042
)
4143

4244

43-
async def test_merged(default_branch: Branch, prefect_test_fixture, context: InfrahubContext, init_service):
45+
async def test_merged(
46+
db: InfrahubDatabase, default_branch: Branch, prefect_test_fixture, context: InfrahubContext, init_service
47+
):
4448
"""
4549
Test that merge flow triggers corrects events/workflows. It does not actually test these events/workflows behaviors
4650
as they are mocked.
@@ -49,8 +53,10 @@ async def test_merged(default_branch: Branch, prefect_test_fixture, context: Inf
4953
source_branch_name = "cr1234"
5054
target_branch_name = "main"
5155
right_now = Timestamp()
52-
tracked_diff_roots = [
53-
EnrichedDiffRoot(
56+
tracked_diff_roots = []
57+
58+
for _ in range(2):
59+
tracked_diff_root = EnrichedDiffRoot(
5460
base_branch_name=target_branch_name,
5561
diff_branch_name=str(uuid4()),
5662
from_time=right_now,
@@ -59,10 +65,23 @@ async def test_merged(default_branch: Branch, prefect_test_fixture, context: Inf
5965
partner_uuid=str(uuid4()),
6066
tracking_id=BranchTrackingId(name=str(uuid4())),
6167
)
62-
for _ in range(2)
63-
]
64-
untracked_diff_roots = [
65-
EnrichedDiffRoot(
68+
tracked_diff_roots.append(tracked_diff_root)
69+
await create_branch(db=db, branch_name=tracked_diff_root.diff_branch_name)
70+
71+
# diff root for a deleted branch
72+
tracked_deleted_root = EnrichedDiffRoot(
73+
base_branch_name=target_branch_name,
74+
diff_branch_name=str(uuid4()),
75+
from_time=right_now,
76+
to_time=right_now,
77+
uuid=str(uuid4()),
78+
partner_uuid=str(uuid4()),
79+
tracking_id=BranchTrackingId(name=str(uuid4())),
80+
)
81+
82+
untracked_diff_roots = []
83+
for _ in range(2):
84+
untracked_diff_root = EnrichedDiffRoot(
6685
base_branch_name=target_branch_name,
6786
diff_branch_name=str(uuid4()),
6887
from_time=right_now,
@@ -71,10 +90,11 @@ async def test_merged(default_branch: Branch, prefect_test_fixture, context: Inf
7190
partner_uuid=str(uuid4()),
7291
tracking_id=NameTrackingId(name=str(uuid4())),
7392
)
74-
for _ in range(2)
75-
]
93+
await create_branch(db=db, branch_name=untracked_diff_root.diff_branch_name)
94+
untracked_diff_roots.append(untracked_diff_root)
95+
7696
diff_repo = AsyncMock(spec=DiffRepository)
77-
diff_repo.get_roots_metadata.return_value = untracked_diff_roots + tracked_diff_roots
97+
diff_repo.get_roots_metadata.return_value = untracked_diff_roots + tracked_diff_roots + [tracked_deleted_root]
7898
mock_component_registry = Mock(spec=ComponentDependencyRegistry)
7999
mock_get_component_registry = MagicMock(return_value=mock_component_registry)
80100
mock_component_registry.get_component.return_value = diff_repo

changelog/7666.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent attempting diff update on a deleted branch. Log a warning instead.

0 commit comments

Comments
 (0)