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]

Reply via email to