kaxil commented on code in PR #72183:
URL: https://github.com/apache/airflow/pull/72183#discussion_r3963568514


##########
providers/common/ai/tests/unit/common/ai/operators/test_llm_branch.py:
##########
@@ -420,6 +420,29 @@ def 
test_execute_complete_reject_skips_downstream_except_teardowns(self, mock_do
         assert list(mock_skip.call_args.kwargs["tasks"]) == [task_a]
         mock_do_branch.assert_not_called()
 
+    @patch.object(LLMBranchOperator, "skip")
+    @patch.object(LLMBranchOperator, "do_branch")
+    def 
test_execute_complete_reject_skips_indirect_downstream_when_ignoring_trigger_rules(
+        self, mock_do_branch, mock_skip
+    ):
+        op = LLMBranchOperator(task_id="t", prompt="p", llm_conn_id="c", 
ignore_downstream_trigger_rules=True)
+        op.downstream_task_ids = {"task_a"}
+        event = {"chosen_options": ["Reject"], "responded_by_user": "admin"}
+        task_a = MagicMock(is_teardown=False)
+        indirect = MagicMock(is_teardown=False)
+        cleanup = MagicMock(is_teardown=True)
+        task = MagicMock()
+        task.get_flat_relatives.return_value = [task_a, indirect, cleanup]
+        ti = MagicMock()
+        ctx = MagicMock(**{"__getitem__": lambda self, key: {"task": task, 
"ti": ti}[key]})
+
+        op.execute_complete(ctx, generated_output="task_a", event=event)
+
+        task.get_flat_relatives.assert_called_once_with(upstream=False)
+        task.get_direct_relatives.assert_not_called()
+        assert list(mock_skip.call_args.kwargs["tasks"]) == [task_a, indirect]

Review Comment:
   These assert which getter was called and echo back the stubbed return value, 
so the traversal itself is never exercised -- `get_flat_relatives` is a 
`MagicMock` here, and the test passes whatever a real Dag would actually return.
   
   `TestShortCircuitWithTeardown::test_short_circuit_with_teardowns` in 
`providers/standard/tests/unit/standard/operators/test_python.py` covers the 
same flag against a real Dag: `dag_maker` with `op1 >> op2 >> op3 >> op4`, 
parametrized over `ignore_downstream_trigger_rules` and `with_teardown`, 
asserting the skipped task IDs. The split there between a teardown and no 
teardown -- `["op2", "op3"]` against `["op2", "op3", "op4"]` -- is what pins 
the transitive-plus-teardown behaviour that a mocked return value can't reach.
   
   Since the teardown carve-out in #71073 only surfaced on a real Dag, one 
`dag_maker` case here would carry more than the two call assertions.



##########
providers/common/ai/src/airflow/providers/common/ai/operators/llm_branch.py:
##########
@@ -78,12 +83,14 @@ def __init__(
         *,
         allow_multiple_branches: bool = False,
         fail_on_reject: bool = False,
+        ignore_downstream_trigger_rules: bool = False,

Review Comment:
   `execute_complete` is only reachable through `defer_for_approval`, which 
`execute` calls under `if self.require_approval`. So this flag does nothing at 
all unless `require_approval=True`, and neither the param docs nor the rst 
mention that precondition.
   
   That is also where the `ApprovalOperator` parity the docs claim stops 
holding: there the operator always defers, so the flag always applies. Someone 
carrying the idiom across would set it, see no change in behaviour, and have 
nothing pointing at why.
   
   `self.require_approval` is already set by the `super().__init__()` just 
below, so raising here is possible -- though it would mean the existing reject 
tests need `require_approval=True` to construct, so a sentence on the param is 
probably the cheaper fix. Same property applies to `fail_on_reject`, so this 
could ride along with moving that one to `LLMApprovalMixin`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to