This is an automated email from the ASF dual-hosted git repository.
vincbeck 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 4d86340e5a Add tests to `airflow/auth/managers/fab/decorators/auth.py`
(#35358)
4d86340e5a is described below
commit 4d86340e5a280ab089a0240a25cb1720a4e50d20
Author: Vincent <[email protected]>
AuthorDate: Thu Nov 2 11:49:59 2023 -0400
Add tests to `airflow/auth/managers/fab/decorators/auth.py` (#35358)
---
tests/auth/managers/fab/decorators/__init__.py | 16 +++
tests/auth/managers/fab/decorators/test_auth.py | 141 ++++++++++++++++++++++++
2 files changed, 157 insertions(+)
diff --git a/tests/auth/managers/fab/decorators/__init__.py
b/tests/auth/managers/fab/decorators/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/auth/managers/fab/decorators/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/tests/auth/managers/fab/decorators/test_auth.py
b/tests/auth/managers/fab/decorators/test_auth.py
new file mode 100644
index 0000000000..d152bddf43
--- /dev/null
+++ b/tests/auth/managers/fab/decorators/test_auth.py
@@ -0,0 +1,141 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from airflow.api_connexion.exceptions import PermissionDenied
+from airflow.auth.managers.fab.decorators.auth import _has_access_fab,
_requires_access_fab
+from airflow.security.permissions import ACTION_CAN_READ, RESOURCE_DAG
+from airflow.www import app as application
+
+
[email protected](scope="module")
+def app():
+ return application.create_app(testing=True)
+
+
[email protected]
+def mock_sm():
+ return Mock()
+
+
[email protected]
+def mock_appbuilder(mock_sm):
+ appbuilder = Mock()
+ appbuilder.sm = mock_sm
+ return appbuilder
+
+
[email protected]
+def mock_app(mock_appbuilder):
+ app = Mock()
+ app.appbuilder = mock_appbuilder
+ return app
+
+
+mock_call = Mock()
+
+permissions = [(ACTION_CAN_READ, RESOURCE_DAG)]
+
+
+@_has_access_fab(permissions)
+def decorated_has_access_fab():
+ mock_call()
+
+
+class TestFabAuthManagerDecorators:
+ def setup_method(self) -> None:
+ mock_call.reset_mock()
+
+ @patch("airflow.auth.managers.fab.decorators.auth.get_airflow_app")
+ def test_requires_access_fab_sync_resource_permissions(
+ self, mock_get_airflow_app, mock_sm, mock_appbuilder, mock_app
+ ):
+ mock_appbuilder.update_perms = True
+ mock_get_airflow_app.return_value = mock_app
+
+ @_requires_access_fab()
+ def decorated_requires_access_fab():
+ pass
+
+ mock_sm.sync_resource_permissions.assert_called_once()
+
+ @patch("airflow.auth.managers.fab.decorators.auth.get_airflow_app")
+ @patch("airflow.auth.managers.fab.decorators.auth.check_authentication")
+ def test_requires_access_fab_access_denied(
+ self, mock_check_authentication, mock_get_airflow_app, mock_sm,
mock_app
+ ):
+ mock_get_airflow_app.return_value = mock_app
+ mock_sm.check_authorization.return_value = False
+
+ @_requires_access_fab(permissions)
+ def decorated_requires_access_fab():
+ pass
+
+ with pytest.raises(PermissionDenied):
+ decorated_requires_access_fab()
+
+ mock_check_authentication.assert_called_once()
+ mock_sm.check_authorization.assert_called_once()
+ mock_call.assert_not_called()
+
+ @patch("airflow.auth.managers.fab.decorators.auth.get_airflow_app")
+ @patch("airflow.auth.managers.fab.decorators.auth.check_authentication")
+ def test_requires_access_fab_access_granted(
+ self, mock_check_authentication, mock_get_airflow_app, mock_sm,
mock_app
+ ):
+ mock_get_airflow_app.return_value = mock_app
+ mock_sm.check_authorization.return_value = True
+
+ @_requires_access_fab(permissions)
+ def decorated_requires_access_fab():
+ mock_call()
+
+ decorated_requires_access_fab()
+
+ mock_check_authentication.assert_called_once()
+ mock_sm.check_authorization.assert_called_once()
+ mock_call.assert_called_once()
+
+ @pytest.mark.db_test
+ @patch("airflow.auth.managers.fab.decorators.auth._has_access")
+ def test_has_access_fab_with_no_dags(self, mock_has_access, mock_sm,
mock_appbuilder, app):
+ app.appbuilder = mock_appbuilder
+ with app.test_request_context():
+ decorated_has_access_fab()
+
+ mock_sm.check_authorization.assert_called_once_with(permissions, None)
+ mock_has_access.assert_called_once()
+
+ @pytest.mark.db_test
+ @patch("airflow.auth.managers.fab.decorators.auth.render_template")
+ @patch("airflow.auth.managers.fab.decorators.auth._has_access")
+ def test_has_access_fab_with_multiple_dags_render_error(
+ self, mock_has_access, mock_render_template, mock_sm, mock_appbuilder,
app
+ ):
+ app.appbuilder = mock_appbuilder
+ with app.test_request_context() as mock_context:
+ mock_context.request.args = {"dag_id": "dag1"}
+ mock_context.request.form = {"dag_id": "dag2"}
+ decorated_has_access_fab()
+
+ mock_sm.check_authorization.assert_not_called()
+ mock_has_access.assert_not_called()
+ mock_render_template.assert_called_once()