| # |
| # 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 datetime import timedelta |
| from unittest.mock import patch |
| |
| import pytest |
| |
| from airflow import AirflowException, example_dags as example_dags_module |
| from airflow.models import DagBag |
| from airflow.models.dagcode import DagCode |
| |
| # To move it to a shared module. |
| from airflow.utils.file import open_maybe_zipped |
| from airflow.utils.session import create_session |
| from tests.test_utils.db import clear_db_dag_code |
| |
| |
| def make_example_dags(module): |
| """Loads DAGs from a module for test.""" |
| dagbag = DagBag(module.__path__[0]) |
| return dagbag.dags |
| |
| |
| class TestDagCode: |
| """Unit tests for DagCode.""" |
| |
| def setup_method(self): |
| clear_db_dag_code() |
| |
| def teardown_method(self): |
| clear_db_dag_code() |
| |
| def _write_two_example_dags(self): |
| example_dags = make_example_dags(example_dags_module) |
| bash_dag = example_dags["example_bash_operator"] |
| DagCode(bash_dag.fileloc).sync_to_db() |
| xcom_dag = example_dags["example_xcom"] |
| DagCode(xcom_dag.fileloc).sync_to_db() |
| return [bash_dag, xcom_dag] |
| |
| def _write_example_dags(self): |
| example_dags = make_example_dags(example_dags_module) |
| for dag in example_dags.values(): |
| dag.sync_to_db() |
| return example_dags |
| |
| def test_sync_to_db(self): |
| """Dg code can be written into database.""" |
| example_dags = self._write_example_dags() |
| |
| self._compare_example_dags(example_dags) |
| |
| def test_bulk_sync_to_db(self): |
| """Dg code can be bulk written into database.""" |
| example_dags = make_example_dags(example_dags_module) |
| files = [dag.fileloc for dag in example_dags.values()] |
| with create_session() as session: |
| DagCode.bulk_sync_to_db(files, session=session) |
| session.commit() |
| |
| self._compare_example_dags(example_dags) |
| |
| def test_bulk_sync_to_db_half_files(self): |
| """Dg code can be bulk written into database.""" |
| example_dags = make_example_dags(example_dags_module) |
| files = [dag.fileloc for dag in example_dags.values()] |
| half_files = files[: int(len(files) / 2)] |
| with create_session() as session: |
| DagCode.bulk_sync_to_db(half_files, session=session) |
| session.commit() |
| with create_session() as session: |
| DagCode.bulk_sync_to_db(files, session=session) |
| session.commit() |
| |
| self._compare_example_dags(example_dags) |
| |
| @patch.object(DagCode, "dag_fileloc_hash") |
| def test_detecting_duplicate_key(self, mock_hash): |
| """Dag code detects duplicate key.""" |
| mock_hash.return_value = 0 |
| |
| with pytest.raises(AirflowException): |
| self._write_two_example_dags() |
| |
| def _compare_example_dags(self, example_dags): |
| with create_session() as session: |
| for dag in example_dags.values(): |
| if dag.is_subdag: |
| dag.fileloc = dag.parent_dag.fileloc |
| assert DagCode.has_dag(dag.fileloc) |
| dag_fileloc_hash = DagCode.dag_fileloc_hash(dag.fileloc) |
| result = ( |
| session.query(DagCode.fileloc, DagCode.fileloc_hash, DagCode.source_code) |
| .filter(DagCode.fileloc == dag.fileloc) |
| .filter(DagCode.fileloc_hash == dag_fileloc_hash) |
| .one() |
| ) |
| |
| assert result.fileloc == dag.fileloc |
| with open_maybe_zipped(dag.fileloc, "r") as source: |
| source_code = source.read() |
| assert result.source_code == source_code |
| |
| def test_code_can_be_read_when_no_access_to_file(self): |
| """ |
| Test that code can be retrieved from DB when you do not have access to Code file. |
| Source Code should at least exist in one of DB or File. |
| """ |
| example_dag = make_example_dags(example_dags_module).get("example_bash_operator") |
| example_dag.sync_to_db() |
| |
| # Mock that there is no access to the Dag File |
| with patch("airflow.models.dagcode.open_maybe_zipped") as mock_open: |
| mock_open.side_effect = FileNotFoundError |
| dag_code = DagCode.get_code_by_fileloc(example_dag.fileloc) |
| |
| for test_string in ["example_bash_operator", "also_run_this", "run_this_last"]: |
| assert test_string in dag_code |
| |
| def test_db_code_updated_on_dag_file_change(self): |
| """Test if DagCode is updated in DB when DAG file is changed""" |
| example_dag = make_example_dags(example_dags_module).get("example_bash_operator") |
| example_dag.sync_to_db() |
| |
| with create_session() as session: |
| result = session.query(DagCode).filter(DagCode.fileloc == example_dag.fileloc).one() |
| |
| assert result.fileloc == example_dag.fileloc |
| assert result.source_code is not None |
| |
| with patch("airflow.models.dagcode.os.path.getmtime") as mock_mtime: |
| mock_mtime.return_value = (result.last_updated + timedelta(seconds=1)).timestamp() |
| |
| with patch("airflow.models.dagcode.DagCode._get_code_from_file") as mock_code: |
| mock_code.return_value = "# dummy code" |
| example_dag.sync_to_db() |
| |
| with create_session() as session: |
| new_result = session.query(DagCode).filter(DagCode.fileloc == example_dag.fileloc).one() |
| |
| assert new_result.fileloc == example_dag.fileloc |
| assert new_result.source_code == "# dummy code" |
| assert new_result.last_updated > result.last_updated |