Copilot commented on code in PR #68974:
URL: https://github.com/apache/airflow/pull/68974#discussion_r3473214464


##########
providers/databricks/src/airflow/providers/databricks/operators/databricks.py:
##########
@@ -876,6 +909,39 @@ def 
_inject_openlineage_properties_into_databricks_job(self, json: dict, context
             )
             return json
 
+    def submit_job(self, context: Context) -> int:
+        normalised = self._prepare_submit_json(context)
+        # Set run_id the instant the run exists so on_kill can cancel it even 
if the worker dies
+        # before polling begins.
+        self.run_id = self._hook.submit_run(normalised)
+        self.log.info("Run submitted with run_id: %s", self.run_id)
+        return self.run_id
+
+    def get_job_status(self, external_id: int, context: Context) -> str:
+        # Databricks splits run state across life_cycle_state and 
result_state; the mixin's status
+        # interface is a single string, so encode both as "LIFE_CYCLE:RESULT" 
and decode them in
+        # is_job_active / is_job_succeeded.
+        run_info = self._hook.get_run(external_id)
+        state = RunState(**run_info["state"])
+        return f"{state.life_cycle_state}:{state.result_state}"
+
+    def is_job_active(self, status: str) -> bool:
+        life_cycle_state = status.split(":", 1)[0]
+        return life_cycle_state not in ("TERMINATED", "SKIPPED", 
"INTERNAL_ERROR")
+
+    def is_job_succeeded(self, status: str) -> bool:
+        return status == "TERMINATED:SUCCESS"
+
+    def poll_until_complete(self, external_id: int, context: Context) -> None:
+        self.run_id = external_id
+        # The run already exists here (fresh submit logged in submit_job, or 
reconnect logged by the
+        # mixin), so the poll helper must not announce a submission.
+        _handle_databricks_operator_execution(self, self._hook, self.log, 
context, announce_submission=False)
+
+    def get_job_result(self, external_id: int, context: Context) -> None:
+        self.run_id = external_id
+        return None

Review Comment:
   In the durable "already succeeded" retry path, 
`ResumableJobMixin.execute_resumable()` calls `get_job_result()` directly and 
skips `poll_until_complete()`. Since XCom push of `run_id` / `run_page_url` 
currently only happens in `_handle_databricks_operator_execution()` (called 
from `poll_until_complete()`), retries that short-circuit as already-succeeded 
will not push these XCom keys even when `do_xcom_push=True`.
   
   That’s a behavior change vs the normal synchronous path and can break 
downstream tasks that rely on `run_id` / `run_page_url` being present. Consider 
pushing these XCom values from `get_job_result()` as well (it’s the hook point 
the mixin uses for the already-succeeded case).



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx
+
+    @staticmethod
+    def _state(lifecycle: str, result: str = ""):
+        return {"state": {"life_cycle_state": lifecycle, "result_state": 
result, "state_message": ""}}
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_persists_run_id_to_task_state_store_on_fresh_submit(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = None
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_called_once()
+        task_store.set.assert_called_once_with("databricks_run_id", RUN_ID)
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_reconnects_to_active_run_without_resubmitting(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        # status check sees the stored run still RUNNING, the poll then sees 
it finish.
+        db_mock.get_run.side_effect = [self._state("RUNNING"), 
self._state("TERMINATED", "SUCCESS")]
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        task_store.set.assert_not_called()
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_skips_polling_when_stored_run_already_succeeded(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        db_mock.get_run_page_url.assert_not_called()
+        assert op.run_id == RUN_ID

Review Comment:
   If the durable retry short-circuits because the stored run already 
succeeded, the operator should still behave like a successful execution from 
the caller’s perspective (notably: push `run_id` / `run_page_url` XComs when 
`do_xcom_push=True`). This test currently asserts `get_run_page_url` is *not* 
called, which would codify the missing-XCom behavior.
   
   After adjusting the operator to push XComs from `get_job_result()` (for the 
already-succeeded path), update this test to assert the URL is fetched and the 
XCom pushes happen.



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx

Review Comment:
   `MagicMock(autospec=True)` here does not actually apply autospeccing 
("autospec" is a `mock.patch` feature, not a `MagicMock` ctor arg), so this 
ends up as an unspecced mock with an unused attribute. Prefer mocks with an 
explicit `spec`/`spec_set` so the test will fail if the production code calls a 
non-existent method.
   
   Also, `ResumableJobMixin` reads `ti.stats_tags`; initializing it to `{}` 
avoids accidental truthy `MagicMock` values affecting metric tag selection.



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx
+
+    @staticmethod
+    def _state(lifecycle: str, result: str = ""):
+        return {"state": {"life_cycle_state": lifecycle, "result_state": 
result, "state_message": ""}}
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_persists_run_id_to_task_state_store_on_fresh_submit(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = None
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_called_once()
+        task_store.set.assert_called_once_with("databricks_run_id", RUN_ID)
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_reconnects_to_active_run_without_resubmitting(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        # status check sees the stored run still RUNNING, the poll then sees 
it finish.
+        db_mock.get_run.side_effect = [self._state("RUNNING"), 
self._state("TERMINATED", "SUCCESS")]
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        task_store.set.assert_not_called()
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_skips_polling_when_stored_run_already_succeeded(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        db_mock.get_run_page_url.assert_not_called()
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_resubmits_when_stored_run_in_terminal_failure(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        # stored run failed, after fresh resubmit the poll sees success.
+        db_mock.get_run.side_effect = [
+            self._state("TERMINATED", "FAILED"),
+            self._state("TERMINATED", "SUCCESS"),
+        ]
+        task_store = MagicMock()

Review Comment:
   `task_store = MagicMock()` is unspecced; using `spec_set` makes this test 
more robust against accidental API drift/typos.



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx
+
+    @staticmethod
+    def _state(lifecycle: str, result: str = ""):
+        return {"state": {"life_cycle_state": lifecycle, "result_state": 
result, "state_message": ""}}
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_persists_run_id_to_task_state_store_on_fresh_submit(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = None
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_called_once()
+        task_store.set.assert_called_once_with("databricks_run_id", RUN_ID)
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_reconnects_to_active_run_without_resubmitting(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        # status check sees the stored run still RUNNING, the poll then sees 
it finish.
+        db_mock.get_run.side_effect = [self._state("RUNNING"), 
self._state("TERMINATED", "SUCCESS")]
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        task_store.set.assert_not_called()
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_skips_polling_when_stored_run_already_succeeded(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        db_mock.get_run_page_url.assert_not_called()
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_resubmits_when_stored_run_in_terminal_failure(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        # stored run failed, after fresh resubmit the poll sees success.
+        db_mock.get_run.side_effect = [
+            self._state("TERMINATED", "FAILED"),
+            self._state("TERMINATED", "SUCCESS"),
+        ]
+        task_store = MagicMock()
+        task_store.get.return_value = 999
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_called_once()
+        task_store.set.assert_called_once_with("databricks_run_id", RUN_ID)
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_durable_false_never_touches_task_state_store(self, db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}, durable=False
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()

Review Comment:
   This `task_store` mock is unspecced; `spec_set=["get", "set"]` helps catch 
accidental typos and keeps tests aligned with the task state store API.



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx
+
+    @staticmethod
+    def _state(lifecycle: str, result: str = ""):
+        return {"state": {"life_cycle_state": lifecycle, "result_state": 
result, "state_message": ""}}
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_persists_run_id_to_task_state_store_on_fresh_submit(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = None
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_called_once()
+        task_store.set.assert_called_once_with("databricks_run_id", RUN_ID)
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_reconnects_to_active_run_without_resubmitting(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        # status check sees the stored run still RUNNING, the poll then sees 
it finish.
+        db_mock.get_run.side_effect = [self._state("RUNNING"), 
self._state("TERMINATED", "SUCCESS")]
+        task_store = MagicMock()
+        task_store.get.return_value = RUN_ID
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_not_called()
+        task_store.set.assert_not_called()
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_skips_polling_when_stored_run_already_succeeded(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()

Review Comment:
   `task_store` is created as a bare `MagicMock()` here. Consider 
`spec_set=["get", "set"]` so the test enforces the expected task state store 
API surface.



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx
+
+    @staticmethod
+    def _state(lifecycle: str, result: str = ""):
+        return {"state": {"life_cycle_state": lifecycle, "result_state": 
result, "state_message": ""}}
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_persists_run_id_to_task_state_store_on_fresh_submit(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()

Review Comment:
   `task_store` is a `MagicMock()` without a `spec`/`spec_set`, which can hide 
typos in the code under test (e.g. calling `tas_store.get`). Prefer a specced 
mock for the task state store interface.



##########
providers/databricks/tests/unit/databricks/operators/test_databricks.py:
##########
@@ -1651,6 +1668,161 @@ def 
test_inject_parent_job_info_preserves_existing_config(self, db_mock_class, m
         assert 
submitted["new_cluster"]["spark_conf"]["spark.openlineage.parentJobNamespace"] 
== "ns"
 
 
[email protected](
+    not AIRFLOW_V_3_3_PLUS, reason="task_state_store (durable execution) 
requires Airflow 3.3+"
+)
+class TestDatabricksSubmitRunOperatorDurable:
+    @staticmethod
+    def _context(task_store=None):
+        ctx: dict = {"ti": MagicMock(autospec=True)}
+        if task_store is not None:
+            ctx["task_state_store"] = task_store
+        return ctx
+
+    @staticmethod
+    def _state(lifecycle: str, result: str = ""):
+        return {"state": {"life_cycle_state": lifecycle, "result_state": 
result, "state_message": ""}}
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_persists_run_id_to_task_state_store_on_fresh_submit(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        db_mock.submit_run.return_value = RUN_ID
+        db_mock.get_run = make_run_with_state_mock("TERMINATED", "SUCCESS")
+        task_store = MagicMock()
+        task_store.get.return_value = None
+
+        op.execute(self._context(task_store))
+
+        db_mock.submit_run.assert_called_once()
+        task_store.set.assert_called_once_with("databricks_run_id", RUN_ID)
+        assert op.run_id == RUN_ID
+
+    
@mock.patch("airflow.providers.databricks.operators.databricks.DatabricksHook")
+    def test_reconnects_to_active_run_without_resubmitting(self, 
db_mock_class):
+        op = DatabricksSubmitRunOperator(
+            task_id=TASK_ID, json={"new_cluster": NEW_CLUSTER, 
"notebook_task": NOTEBOOK_TASK}
+        )
+        db_mock = db_mock_class.return_value
+        # status check sees the stored run still RUNNING, the poll then sees 
it finish.
+        db_mock.get_run.side_effect = [self._state("RUNNING"), 
self._state("TERMINATED", "SUCCESS")]
+        task_store = MagicMock()

Review Comment:
   `task_store` is an unspecced `MagicMock()`. Using `spec_set` helps ensure 
the test fails if the production code calls an unexpected method on the task 
state store.



-- 
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