|
28 | 28 | from pathlib import Path |
29 | 29 | from typing import TYPE_CHECKING |
30 | 30 | from unittest import mock |
31 | | -from unittest.mock import patch |
| 31 | +from unittest.mock import call, patch |
32 | 32 |
|
33 | 33 | import pandas as pd |
34 | 34 | import pytest |
|
50 | 50 | from airflow.sdk import ( |
51 | 51 | DAG, |
52 | 52 | BaseOperator, |
| 53 | + BaseOperatorLink, |
53 | 54 | Connection, |
54 | 55 | dag as dag_decorator, |
55 | 56 | get_current_context, |
@@ -1816,6 +1817,93 @@ def execute(self, context): |
1816 | 1817 | map_index=runtime_ti.map_index, |
1817 | 1818 | ) |
1818 | 1819 |
|
| 1820 | + def test_task_failed_with_operator_extra_links( |
| 1821 | + self, create_runtime_ti, mock_supervisor_comms, time_machine |
| 1822 | + ): |
| 1823 | + """Test that operator extra links are pushed to xcoms even when task fails.""" |
| 1824 | + instant = timezone.datetime(2024, 12, 3, 10, 0) |
| 1825 | + time_machine.move_to(instant, tick=False) |
| 1826 | + |
| 1827 | + class DummyTestOperator(BaseOperator): |
| 1828 | + operator_extra_links = (AirflowLink(),) |
| 1829 | + |
| 1830 | + def execute(self, context): |
| 1831 | + raise ValueError("Task failed intentionally") |
| 1832 | + |
| 1833 | + task = DummyTestOperator(task_id="task_with_operator_extra_links") |
| 1834 | + runtime_ti = create_runtime_ti(task=task) |
| 1835 | + context = runtime_ti.get_template_context() |
| 1836 | + runtime_ti.start_date = instant |
| 1837 | + runtime_ti.end_date = instant |
| 1838 | + |
| 1839 | + state, _, error = run(runtime_ti, context=context, log=mock.MagicMock()) |
| 1840 | + assert state == TaskInstanceState.FAILED |
| 1841 | + assert error is not None |
| 1842 | + |
| 1843 | + with mock.patch.object(XCom, "_set_xcom_in_db") as mock_xcom_set: |
| 1844 | + finalize( |
| 1845 | + runtime_ti, |
| 1846 | + log=mock.MagicMock(), |
| 1847 | + state=TaskInstanceState.FAILED, |
| 1848 | + context=context, |
| 1849 | + error=error, |
| 1850 | + ) |
| 1851 | + assert mock_xcom_set.mock_calls == [ |
| 1852 | + call( |
| 1853 | + key="_link_AirflowLink", |
| 1854 | + value="https://airflow.apache.org", |
| 1855 | + dag_id=runtime_ti.dag_id, |
| 1856 | + task_id=runtime_ti.task_id, |
| 1857 | + run_id=runtime_ti.run_id, |
| 1858 | + map_index=runtime_ti.map_index, |
| 1859 | + ) |
| 1860 | + ] |
| 1861 | + |
| 1862 | + def test_operator_extra_links_exception_handling( |
| 1863 | + self, create_runtime_ti, mock_supervisor_comms, time_machine |
| 1864 | + ): |
| 1865 | + """Test that exceptions in get_link() don't prevent other links from being pushed.""" |
| 1866 | + instant = timezone.datetime(2024, 12, 3, 10, 0) |
| 1867 | + time_machine.move_to(instant, tick=False) |
| 1868 | + |
| 1869 | + class FailingLink(BaseOperatorLink): |
| 1870 | + """A link that raises an exception when get_link is called.""" |
| 1871 | + |
| 1872 | + name = "failing_link" |
| 1873 | + |
| 1874 | + def get_link(self, operator, *, ti_key): |
| 1875 | + raise ValueError("Link generation failed") |
| 1876 | + |
| 1877 | + class DummyTestOperator(BaseOperator): |
| 1878 | + operator_extra_links = (FailingLink(), AirflowLink()) |
| 1879 | + |
| 1880 | + def execute(self, context): |
| 1881 | + pass |
| 1882 | + |
| 1883 | + task = DummyTestOperator(task_id="task_with_multiple_links") |
| 1884 | + runtime_ti = create_runtime_ti(task=task) |
| 1885 | + context = runtime_ti.get_template_context() |
| 1886 | + runtime_ti.start_date = instant |
| 1887 | + runtime_ti.end_date = instant |
| 1888 | + |
| 1889 | + with mock.patch.object(XCom, "_set_xcom_in_db") as mock_xcom_set: |
| 1890 | + finalize( |
| 1891 | + runtime_ti, |
| 1892 | + log=mock.MagicMock(), |
| 1893 | + state=TaskInstanceState.SUCCESS, |
| 1894 | + context=context, |
| 1895 | + ) |
| 1896 | + assert mock_xcom_set.mock_calls == [ |
| 1897 | + call( |
| 1898 | + key="_link_AirflowLink", |
| 1899 | + value="https://airflow.apache.org", |
| 1900 | + dag_id=runtime_ti.dag_id, |
| 1901 | + task_id=runtime_ti.task_id, |
| 1902 | + run_id=runtime_ti.run_id, |
| 1903 | + map_index=runtime_ti.map_index, |
| 1904 | + ) |
| 1905 | + ] |
| 1906 | + |
1819 | 1907 | @pytest.mark.parametrize( |
1820 | 1908 | ("cmd", "rendered_cmd"), |
1821 | 1909 | [ |
|
0 commit comments