This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 9645786ad3 Add missing tests for the DAG endpoint (#35158)
9645786ad3 is described below

commit 9645786ad30529986447661ca7357398559fd3fa
Author: Ephraim Anierobi <splendidzig...@gmail.com>
AuthorDate: Tue Oct 24 16:12:40 2023 +0100

    Add missing tests for the DAG endpoint (#35158)
    
    This commit adds missing tests for the DAG endpoint to improve the coverage.
    
    Related: https://github.com/apache/airflow/issues/35127
---
 scripts/cov/restapi_coverage.py                    |   1 -
 tests/api_connexion/endpoints/test_dag_endpoint.py | 217 +++++++++++++++++++++
 2 files changed, 217 insertions(+), 1 deletion(-)

diff --git a/scripts/cov/restapi_coverage.py b/scripts/cov/restapi_coverage.py
index 35309dbd85..8d94894da9 100644
--- a/scripts/cov/restapi_coverage.py
+++ b/scripts/cov/restapi_coverage.py
@@ -28,7 +28,6 @@ source_files = ["airflow/api_experimental", 
"airflow/api_connexion", "airflow/ap
 restapi_files = ["tests/api_experimental", "tests/api_connexion", 
"tests/api_internal"]
 
 files_not_fully_covered = [
-    "airflow/api_connexion/endpoints/dag_endpoint.py",
     "airflow/api_connexion/endpoints/dag_run_endpoint.py",
     "airflow/api_connexion/endpoints/forward_to_fab_endpoint.py",
     "airflow/api_connexion/endpoints/pool_endpoint.py",
diff --git a/tests/api_connexion/endpoints/test_dag_endpoint.py 
b/tests/api_connexion/endpoints/test_dag_endpoint.py
index 5b99f7b420..f390564e5d 100644
--- a/tests/api_connexion/endpoints/test_dag_endpoint.py
+++ b/tests/api_connexion/endpoints/test_dag_endpoint.py
@@ -29,6 +29,7 @@ from airflow.models.serialized_dag import SerializedDagModel
 from airflow.operators.empty import EmptyOperator
 from airflow.security import permissions
 from airflow.utils.session import provide_session
+from airflow.utils.state import TaskInstanceState
 from tests.test_utils.api_connexion_utils import assert_401, create_user, 
delete_user
 from tests.test_utils.config import conf_vars
 from tests.test_utils.db import clear_db_dags, clear_db_runs, 
clear_db_serialized_dags
@@ -1057,6 +1058,39 @@ class TestPatchDag(TestDagEndpoint):
             "type": EXCEPTIONS_LINK_MAP[400],
         }
 
+    def test_validation_error_raises_400(self):
+        patch_body = {
+            "ispaused": True,
+        }
+        dag_model = self._create_dag_model()
+        response = self.client.patch(
+            f"/api/v1/dags/{dag_model.dag_id}",
+            json=patch_body,
+            environ_overrides={"REMOTE_USER": "test_granular_permissions"},
+        )
+        assert response.status_code == 400
+        assert response.json == {
+            "detail": "{'ispaused': ['Unknown field.']}",
+            "status": 400,
+            "title": "Bad Request",
+            "type": EXCEPTIONS_LINK_MAP[400],
+        }
+
+    def test_non_existing_dag_raises_not_found(self):
+        patch_body = {
+            "is_paused": True,
+        }
+        response = self.client.patch(
+            "/api/v1/dags/non_existing_dag", json=patch_body, 
environ_overrides={"REMOTE_USER": "test"}
+        )
+        assert response.status_code == 404
+        assert response.json == {
+            "detail": None,
+            "status": 404,
+            "title": "Dag with id: 'non_existing_dag' not found",
+            "type": EXCEPTIONS_LINK_MAP[404],
+        }
+
     def test_should_respond_404(self):
         response = self.client.get("/api/v1/dags/INVALID_DAG", 
environ_overrides={"REMOTE_USER": "test"})
         assert response.status_code == 404
@@ -1257,6 +1291,138 @@ class TestPatchDags(TestDagEndpoint):
             "total_entries": 2,
         } == response.json
 
+    def test_should_respond_200_on_patch_is_paused_using_update_mask(self, 
session, url_safe_serializer):
+        file_token = url_safe_serializer.dumps("/tmp/dag_1.py")
+        file_token2 = url_safe_serializer.dumps("/tmp/dag_2.py")
+        self._create_dag_models(2)
+        self._create_deactivated_dag()
+
+        dags_query = session.query(DagModel).filter(~DagModel.is_subdag)
+        assert len(dags_query.all()) == 3
+
+        response = self.client.patch(
+            "/api/v1/dags?dag_id_pattern=~&update_mask=is_paused",
+            json={
+                "is_paused": False,
+            },
+            environ_overrides={"REMOTE_USER": "test"},
+        )
+
+        assert response.status_code == 200
+        assert {
+            "dags": [
+                {
+                    "dag_id": "TEST_DAG_1",
+                    "description": None,
+                    "fileloc": "/tmp/dag_1.py",
+                    "file_token": file_token,
+                    "is_paused": False,
+                    "is_active": True,
+                    "is_subdag": False,
+                    "owners": [],
+                    "root_dag_id": None,
+                    "schedule_interval": {
+                        "__type": "CronExpression",
+                        "value": "2 2 * * *",
+                    },
+                    "tags": [],
+                    "next_dagrun": None,
+                    "has_task_concurrency_limits": True,
+                    "next_dagrun_data_interval_start": None,
+                    "next_dagrun_data_interval_end": None,
+                    "max_active_runs": 16,
+                    "next_dagrun_create_after": None,
+                    "last_expired": None,
+                    "max_active_tasks": 16,
+                    "last_pickled": None,
+                    "default_view": None,
+                    "last_parsed_time": None,
+                    "scheduler_lock": None,
+                    "timetable_description": None,
+                    "has_import_errors": False,
+                    "pickle_id": None,
+                },
+                {
+                    "dag_id": "TEST_DAG_2",
+                    "description": None,
+                    "fileloc": "/tmp/dag_2.py",
+                    "file_token": file_token2,
+                    "is_paused": False,
+                    "is_active": True,
+                    "is_subdag": False,
+                    "owners": [],
+                    "root_dag_id": None,
+                    "schedule_interval": {
+                        "__type": "CronExpression",
+                        "value": "2 2 * * *",
+                    },
+                    "tags": [],
+                    "next_dagrun": None,
+                    "has_task_concurrency_limits": True,
+                    "next_dagrun_data_interval_start": None,
+                    "next_dagrun_data_interval_end": None,
+                    "max_active_runs": 16,
+                    "next_dagrun_create_after": None,
+                    "last_expired": None,
+                    "max_active_tasks": 16,
+                    "last_pickled": None,
+                    "default_view": None,
+                    "last_parsed_time": None,
+                    "scheduler_lock": None,
+                    "timetable_description": None,
+                    "has_import_errors": False,
+                    "pickle_id": None,
+                },
+            ],
+            "total_entries": 2,
+        } == response.json
+
+    def test_wrong_value_as_update_mask_rasise(self, session):
+        self._create_dag_models(2)
+        self._create_deactivated_dag()
+
+        dags_query = session.query(DagModel).filter(~DagModel.is_subdag)
+        assert len(dags_query.all()) == 3
+
+        response = self.client.patch(
+            "/api/v1/dags?dag_id_pattern=~&update_mask=ispaused",
+            json={
+                "is_paused": False,
+            },
+            environ_overrides={"REMOTE_USER": "test"},
+        )
+
+        assert response.status_code == 400
+        assert response.json == {
+            "detail": "Only `is_paused` field can be updated through the REST 
API",
+            "status": 400,
+            "title": "Bad Request",
+            "type": EXCEPTIONS_LINK_MAP[400],
+        }
+
+    def test_invalid_request_body_raises_badrequest(self, session):
+        self._create_dag_models(2)
+        self._create_deactivated_dag()
+
+        dags_query = session.query(DagModel).filter(~DagModel.is_subdag)
+        assert len(dags_query.all()) == 3
+
+        response = self.client.patch(
+            "/api/v1/dags?dag_id_pattern=~&update_mask=is_paused",
+            json={
+                "ispaused": False,
+            },
+            environ_overrides={"REMOTE_USER": "test"},
+        )
+
+        assert response.status_code == 400
+        assert response.json == {
+            "detail": "{'ispaused': ['Unknown field.']}",
+            "status": 400,
+            "title": "Bad Request",
+            "type": EXCEPTIONS_LINK_MAP[400],
+        }
+
     def test_only_active_true_returns_active_dags(self, url_safe_serializer):
         file_token = url_safe_serializer.dumps("/tmp/dag_1.py")
         self._create_dag_models(1)
@@ -1811,3 +1977,54 @@ class TestPatchDags(TestDagEndpoint):
             environ_overrides={"REMOTE_USER": "test"},
         )
         assert response.status_code == 400
+
+
+class TestDeleteDagEndpoint(TestDagEndpoint):
+    def test_that_dag_can_be_deleted(self):
+        self._create_dag_models(1)
+
+        response = self.client.delete(
+            "/api/v1/dags/TEST_DAG_1",
+            environ_overrides={"REMOTE_USER": "test"},
+        )
+        assert response.status_code == 204
+
+    def test_raise_when_dag_is_not_found(self):
+        response = self.client.delete(
+            "/api/v1/dags/TEST_DAG_1",
+            environ_overrides={"REMOTE_USER": "test"},
+        )
+        assert response.status_code == 404
+        assert response.json == {
+            "detail": None,
+            "status": 404,
+            "title": "Dag with id: 'TEST_DAG_1' not found",
+            "type": EXCEPTIONS_LINK_MAP[404],
+        }
+
+    def test_raises_when_task_instances_of_dag_is_still_running(self, 
dag_maker, session):
+        with dag_maker("TEST_DAG_1"):
+            EmptyOperator(task_id="dummy")
+        dr = dag_maker.create_dagrun()
+        ti = dr.get_task_instances()[0]
+        ti.set_state(TaskInstanceState.RUNNING)
+        session.flush()
+        response = self.client.delete(
+            "/api/v1/dags/TEST_DAG_1",
+            environ_overrides={"REMOTE_USER": "test"},
+        )
+        assert response.status_code == 409
+        assert response.json == {
+            "detail": "Task instances of dag with id: 'TEST_DAG_1' are still 
running",
+            "status": 409,
+            "title": "Conflict",
+            "type": EXCEPTIONS_LINK_MAP[409],
+        }
+
+    def test_users_without_delete_permission_cannot_delete_dag(self):
+        self._create_dag_models(1)
+        response = self.client.delete(
+            "/api/v1/dags/TEST_DAG_1",
+            environ_overrides={"REMOTE_USER": "test_no_permissions"},
+        )
+        assert response.status_code == 403

Reply via email to