jason810496 commented on code in PR #61279:
URL: https://github.com/apache/airflow/pull/61279#discussion_r2807304650
##########
airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py:
##########
@@ -874,6 +874,82 @@ def
test_structure_includes_historical_removed_task_with_proper_shape(self, sess
assert "is_mapped" not in t4
assert "children" not in t4
+ def test_task_converted_to_taskgroup_doesnt_crash(self, session,
test_client):
+ """Test that converting a TaskGroup to a task with same name doesn't
crash grid view.
+
+ Regression test for https://github.com/apache/airflow/issues/61208
+ When a TaskGroup is converted to a task between DAG versions, old runs
have
+ children=[...] while new runs have children=None.
+ """
+ from airflow.models import DagRun
+ from airflow.providers.standard.operators.python import PythonOperator
+ from airflow.sdk import DAG
+
+ from tests_common.test_utils.dag import sync_dag_to_db
+
+ dag_id = "test_task_to_group_conversion"
+
+ # Version 1: task_a is a TaskGroup with subtasks
+ with DAG(
+ dag_id=dag_id,
+ start_date=pendulum.datetime(2024, 1, 1, tz="UTC"),
+ schedule=None,
+ ) as dag_v1:
+ with TaskGroup(group_id="task_a"):
+ PythonOperator(task_id="task_a1", python_callable=lambda: True)
+ PythonOperator(task_id="task_a2", python_callable=lambda: True)
+ PythonOperator(task_id="task_b", python_callable=lambda: True)
+
+ # Create DagModel and serialize old version
+ sync_dag_to_db(dag_v1, bundle_name="test", session=session)
+ session.commit()
+
+ dr1 = DagRun(
+ dag_id=dag_id,
+ run_id="test_run_1",
+ run_type=DagRunType.MANUAL,
+ run_after=pendulum.datetime(2024, 1, 1, tz="UTC"),
+ )
+ session.add(dr1)
+ session.commit()
+
+ # Version 2: task_a becomes a simple task
+ with DAG(
+ dag_id=dag_id,
+ start_date=pendulum.datetime(2024, 1, 1, tz="UTC"),
+ schedule=None,
+ ) as dag_v2:
+ PythonOperator(task_id="task_a", python_callable=lambda: True)
+ PythonOperator(task_id="task_b", python_callable=lambda: True)
+
+ # Update DagModel and serialize new version
+ sync_dag_to_db(dag_v2, bundle_name="test", session=session)
+ session.commit()
+
+ # Create another DagRun with the new version
+ dr2 = DagRun(
+ dag_id=dag_id,
+ run_id="test_run_2",
+ run_type=DagRunType.MANUAL,
+ run_after=pendulum.datetime(2024, 1, 2, tz="UTC"),
+ )
+ session.add(dr2)
+ session.commit()
+
+ # This should not crash with TypeError: 'NoneType' object is not
iterable
+ response = test_client.get(f"/grid/structure/{dag_id}")
+ assert response.status_code == 200
+
+ # Verify the structure includes both versions merged correctly
+ nodes = response.json()
+ assert len(nodes) == 2 # task_a (now a simple task) and task_b
+
+ # Find task_a - it should be a simple task now (no children)
+ task_a_node = next((n for n in nodes if n["id"] == "task_a"), None)
+ assert task_a_node is not None
+ # After conversion from TaskGroup to task, children should be None or
not present
+ assert task_a_node.get("children") is None or "children" not in
task_a_node
Review Comment:
Non-blocking nit: Could we assert with the full expected JSON result instead
of partial assertions?
Those comments are clear and we can leave them as is, but having the
expected JSON response would make it even clearer than the multi-condition
assertion.
##########
airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py:
##########
@@ -874,6 +874,82 @@ def
test_structure_includes_historical_removed_task_with_proper_shape(self, sess
assert "is_mapped" not in t4
assert "children" not in t4
+ def test_task_converted_to_taskgroup_doesnt_crash(self, session,
test_client):
+ """Test that converting a TaskGroup to a task with same name doesn't
crash grid view.
+
+ Regression test for https://github.com/apache/airflow/issues/61208
+ When a TaskGroup is converted to a task between DAG versions, old runs
have
+ children=[...] while new runs have children=None.
+ """
+ from airflow.models import DagRun
+ from airflow.providers.standard.operators.python import PythonOperator
+ from airflow.sdk import DAG
+
+ from tests_common.test_utils.dag import sync_dag_to_db
+
+ dag_id = "test_task_to_group_conversion"
+
+ # Version 1: task_a is a TaskGroup with subtasks
+ with DAG(
+ dag_id=dag_id,
+ start_date=pendulum.datetime(2024, 1, 1, tz="UTC"),
+ schedule=None,
+ ) as dag_v1:
+ with TaskGroup(group_id="task_a"):
+ PythonOperator(task_id="task_a1", python_callable=lambda: True)
+ PythonOperator(task_id="task_a2", python_callable=lambda: True)
+ PythonOperator(task_id="task_b", python_callable=lambda: True)
+
+ # Create DagModel and serialize old version
+ sync_dag_to_db(dag_v1, bundle_name="test", session=session)
+ session.commit()
+
+ dr1 = DagRun(
+ dag_id=dag_id,
+ run_id="test_run_1",
+ run_type=DagRunType.MANUAL,
+ run_after=pendulum.datetime(2024, 1, 1, tz="UTC"),
+ )
+ session.add(dr1)
+ session.commit()
+
Review Comment:
Additionally, could we add another API call here so we can better compare
the v1 and v2 responses?
--
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]