Bläddra i källkod

test all the is_* used in context (#323)

* test all the is_ used in context

* fix on the fix

* pycodestyle

* isort

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 1 år sedan
förälder
incheckning
dfacd80090

+ 23 - 12
src/taipy/gui_core/_context.py

@@ -321,7 +321,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
         data = args[0]
         entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
         if not self.__check_readable_editable(
-            state, entity_id, data.get("type", "Scenario"), _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR
+            state, entity_id, data.get("type", "Scenario"), _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR
         ):
             return
         entity: t.Union[Scenario, Sequence] = core_get(entity_id)
@@ -332,7 +332,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     if primary is True:
                         if not is_promotable(entity):
                             state.assign(
-                                _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Scenario {entity_id} is not promotable."
+                                _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, f"Scenario {entity_id} is not promotable."
                             )
                             return
                         set_primary(entity)
@@ -349,8 +349,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
         entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
         if not is_submittable(entity_id):
             state.assign(
-                _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR,
-                f"{data.get('type', 'Scenario')} {entity_id} is not submitable.",
+                _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR,
+                f"{data.get('type', 'Scenario')} {entity_id} is not submittable.",
             )
             return
         entity = core_get(entity_id)
@@ -484,7 +484,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
         return self.data_nodes_by_owner.get(None, []) + self.get_scenarios()
 
     def data_node_adapter(self, data):
-        if data and hasattr(data, "id") and core_get(data.id) is not None:
+        if data and hasattr(data, "id") and is_readable(data.id) and core_get(data.id) is not None:
             if isinstance(data, DataNode):
                 return (data.id, data.get_simple_label(), None, _EntityType.DATANODE.value, False)
             else:
@@ -519,7 +519,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
             return self.jobs_list
 
     def job_adapter(self, data):
-        if hasattr(data, "id") and core_get(data.id) is not None:
+        if hasattr(data, "id") and is_readable(data.id) and core_get(data.id) is not None:
             if isinstance(data, Job):
                 entity = core_get(data.owner_id)
                 return (
@@ -547,6 +547,9 @@ class _GuiCoreContext(CoreEventConsumerBase):
             errs = []
             if job_action == "delete":
                 for job_id in job_ids:
+                    if not is_readable(job_id):
+                        errs.append(f"Job {job_id} is not readable.")
+                        continue
                     if not is_deletable(job_id):
                         errs.append(f"Job {job_id} is not deletable.")
                         continue
@@ -556,6 +559,9 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         errs.append(f"Error deleting job. {e}")
             elif job_action == "cancel":
                 for job_id in job_ids:
+                    if not is_readable(job_id):
+                        errs.append(f"Job {job_id} is not readable.")
+                        continue
                     if not is_editable(job_id):
                         errs.append(f"Job {job_id} is not cancelable.")
                         continue
@@ -587,8 +593,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
             return
         data = args[0]
         entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-        if not is_editable(entity_id):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Datanode {entity_id} is not editable.")
+        if not self.__check_readable_editable(state, entity_id, "Datanode", _GuiCoreContext._DATANODE_VIZ_ERROR_VAR):
             return
         lock = data.get("lock", True)
         entity: DataNode = core_get(entity_id)
@@ -636,7 +641,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                             cycles_scenarios.extend(scenarios)
                         else:
                             cycles_scenarios.append(cycle)
-                else:
+                elif is_readable(owner_id):
                     entity = core_get(owner_id)
                     if entity and (scenarios := self.scenario_by_cycle.get(entity)):
                         cycles_scenarios.extend(scenarios)
@@ -757,9 +762,15 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 )
                 # user_value = payload.get("user_value")
                 data = self.__read_tabular_data(datanode)
-                data.at[idx, col] = val
-                datanode.write(data, comment=user_data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT))
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
+                if hasattr(data, "at"):
+                    data.at[idx, col] = val
+                    datanode.write(data, comment=user_data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT))
+                    state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
+                else:
+                    state.assign(
+                        _GuiCoreContext._DATANODE_VIZ_ERROR_VAR,
+                        "Error updating Datanode tabular value: type does not support at[] indexer.",
+                    )
             except Exception as e:
                 state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode tabular value. {e}")
         setattr(state, _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, dn_id)

+ 111 - 0
tests/gui_core/test_context_is_deletable.py

@@ -0,0 +1,111 @@
+from unittest.mock import Mock, patch
+
+import pytest
+
+from src.taipy.gui_core._context import _GuiCoreContext
+from taipy.config.common.scope import Scope
+from taipy.core import Job, Scenario, Task
+from taipy.core.data.pickle import PickleDataNode
+
+a_scenario = Scenario("scenario_config_id", [], {}, sequences={"sequence": {}})
+a_task = Task("task_config_id", {}, print)
+a_job = Job("JOB_job_id", a_task, "submit_id", a_scenario.id)
+a_job.isfinished = lambda s: True
+a_datanode = PickleDataNode("data_node_config_id", Scope.SCENARIO)
+
+
+def mock_core_get(entity_id):
+    if entity_id == a_scenario.id:
+        return a_scenario
+    if entity_id == a_job.id:
+        return a_job
+    if entity_id == a_datanode.id:
+        return a_datanode
+    return a_task
+
+
+def mock_is_deletable_false(entity_id):
+    return False
+
+
+def mock_is_true(entity_id):
+    return True
+
+
+class MockState:
+    def __init__(self, **kwargs) -> None:
+        self.assign = kwargs.get("assign")
+
+
+class TestGuiCoreContext_is_deletable:
+    def test_crud_scenario(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
+            "src.taipy.gui_core._context.is_deletable", side_effect=mock_is_true
+        ):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.crud_scenario(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        True,
+                        True,
+                        {"name": "name", "id": a_scenario.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_sc_error"
+            assert str(assign.call_args.args[1]).startswith("Error deleting Scenario.")
+
+            with patch("src.taipy.gui_core._context.is_deletable", side_effect=mock_is_deletable_false):
+                assign.reset_mock()
+                gui_core_context.crud_scenario(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            True,
+                            True,
+                            {"name": "name", "id": a_scenario.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sc_error"
+                assert str(assign.call_args.args[1]).endswith("is not deletable.")
+
+    def test_act_on_jobs(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
+            "src.taipy.gui_core._context.is_deletable", side_effect=mock_is_true
+        ):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.act_on_jobs(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": [a_job.id], "action": "delete"},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_js_error"
+            assert str(assign.call_args.args[1]).find("is not deletable.") == -1
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_deletable_false):
+                gui_core_context.act_on_jobs(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": [a_job.id], "action": "delete"},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_js_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")

+ 273 - 0
tests/gui_core/test_context_is_editable.py

@@ -0,0 +1,273 @@
+from unittest.mock import Mock, patch
+
+import pytest
+
+from src.taipy.gui_core._context import _GuiCoreContext
+from taipy.config.common.scope import Scope
+from taipy.core import Job, Scenario, Task
+from taipy.core.data.pickle import PickleDataNode
+from taipy.gui import Gui
+
+a_scenario = Scenario("scenario_config_id", [], {}, sequences={"sequence": {}})
+a_task = Task("task_config_id", {}, print)
+a_job = Job("JOB_job_id", a_task, "submit_id", a_scenario.id)
+a_job.isfinished = lambda s: True
+a_datanode = PickleDataNode("data_node_config_id", Scope.SCENARIO)
+
+
+def mock_core_get(entity_id):
+    if entity_id == a_scenario.id:
+        return a_scenario
+    if entity_id == a_job.id:
+        return a_job
+    if entity_id == a_datanode.id:
+        return a_datanode
+    return a_task
+
+
+def mock_is_editable_false(entity_id):
+    return False
+
+
+def mock_is_true(entity_id):
+    return True
+
+
+class MockState:
+    def __init__(self, **kwargs) -> None:
+        self.assign = kwargs.get("assign")
+
+
+class TestGuiCoreContext_is_editable:
+    def test_crud_scenario(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.crud_scenario(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        True,
+                        False,
+                        {"name": "name", "id": a_scenario.id},
+                    ]
+                },
+            )
+            assign.assert_not_called()
+
+            with patch("src.taipy.gui_core._context.is_editable", side_effect=mock_is_editable_false):
+                assign.reset_mock()
+                gui_core_context.crud_scenario(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            True,
+                            False,
+                            {"name": "name", "id": a_scenario.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sc_error"
+                assert str(assign.call_args.args[1]).endswith("is not editable.")
+
+    def test_edit_entity(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.edit_entity(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"name": "name", "id": a_scenario.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_sv_error"
+            assert assign.call_args.args[1] == ""
+
+            with patch("src.taipy.gui_core._context.is_editable", side_effect=mock_is_editable_false):
+                assign.reset_mock()
+                gui_core_context.edit_entity(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"name": "name", "id": a_scenario.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sv_error"
+                assert str(assign.call_args.args[1]).endswith("is not editable.")
+
+    def test_act_on_jobs(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
+            "src.taipy.gui_core._context.is_deletable", side_effect=mock_is_true
+        ):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.act_on_jobs(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": [a_job.id], "action": "cancel"},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_js_error"
+            assert str(assign.call_args.args[1]).find("is not editable.") == -1
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_editable_false):
+                gui_core_context.act_on_jobs(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": [a_job.id], "action": "cancel"},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_js_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_edit_data_node(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.edit_data_node(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": a_datanode.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_dv_error"
+            assert assign.call_args.args[1] == ""
+
+            with patch("src.taipy.gui_core._context.is_editable", side_effect=mock_is_editable_false):
+                assign.reset_mock()
+                gui_core_context.edit_data_node(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": a_datanode.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not editable.")
+
+    def test_lock_datanode_for_edit(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            mockGui = Mock(Gui)
+            mockGui._get_client_id = lambda: "a_client_id"
+            gui_core_context = _GuiCoreContext(mockGui)
+            assign = Mock()
+            gui_core_context.lock_datanode_for_edit(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": a_datanode.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_dv_error"
+            assert assign.call_args.args[1] == ""
+
+            with patch("src.taipy.gui_core._context.is_editable", side_effect=mock_is_editable_false):
+                assign.reset_mock()
+                gui_core_context.lock_datanode_for_edit(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": a_datanode.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not editable.")
+
+    def test_update_data(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            mockGui = Mock(Gui)
+            mockGui._get_client_id = lambda: "a_client_id"
+            gui_core_context = _GuiCoreContext(mockGui)
+            assign = Mock()
+            gui_core_context.update_data(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": a_datanode.id},
+                    ]
+                },
+            )
+            assign.assert_called()
+            assert assign.call_args_list[0].args[0] == "gui_core_dv_error"
+            assert assign.call_args_list[0].args[1] == ""
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_editable", side_effect=mock_is_editable_false):
+                gui_core_context.update_data(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": a_datanode.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not editable.")
+
+    def test_tabular_data_edit(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            mockGui = Mock(Gui)
+            mockGui._get_client_id = lambda: "a_client_id"
+            gui_core_context = _GuiCoreContext(mockGui)
+            assign = Mock()
+            gui_core_context.tabular_data_edit(
+                MockState(assign=assign),
+                "",
+                {
+                    "user_data": {"dn_id": a_datanode.id},
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args_list[0].args[0] == "gui_core_dv_error"
+            assert (
+                assign.call_args_list[0].args[1]
+                == "Error updating Datanode tabular value: type does not support at[] indexer."
+            )
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_editable", side_effect=mock_is_editable_false):
+                gui_core_context.tabular_data_edit(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "user_data": {"dn_id": a_datanode.id},
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not editable.")

+ 73 - 0
tests/gui_core/test_context_is_promotable.py

@@ -0,0 +1,73 @@
+from unittest.mock import Mock, patch
+
+import pytest
+
+from src.taipy.gui_core._context import _GuiCoreContext
+from taipy.config.common.scope import Scope
+from taipy.core import Job, Scenario, Task
+from taipy.core.data.pickle import PickleDataNode
+
+a_scenario = Scenario("scenario_config_id", [], {}, sequences={"sequence": {}})
+a_task = Task("task_config_id", {}, print)
+a_job = Job("JOB_job_id", a_task, "submit_id", a_scenario.id)
+a_job.isfinished = lambda s: True
+a_datanode = PickleDataNode("data_node_config_id", Scope.SCENARIO)
+
+
+def mock_core_get(entity_id):
+    if entity_id == a_scenario.id:
+        return a_scenario
+    if entity_id == a_job.id:
+        return a_job
+    if entity_id == a_datanode.id:
+        return a_datanode
+    return a_task
+
+
+def mock_is_promotable_false(entity_id):
+    return False
+
+
+def mock_is_true(entity_id):
+    return True
+
+
+class MockState:
+    def __init__(self, **kwargs) -> None:
+        self.assign = kwargs.get("assign")
+
+
+class TestGuiCoreContext_is_promotable:
+    def test_edit_entity(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
+            "src.taipy.gui_core._context.is_promotable", side_effect=mock_is_true
+        ):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.edit_entity(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"name": "name", "id": a_scenario.id, "primary": True},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_sv_error"
+            assert str(assign.call_args.args[1]).endswith("to primary because it doesn't belong to a cycle.")
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_promotable", side_effect=mock_is_promotable_false):
+                gui_core_context.edit_entity(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"name": "name", "id": a_scenario.id, "primary": True},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sv_error"
+                assert str(assign.call_args.args[1]).endswith("is not promotable.")

+ 406 - 0
tests/gui_core/test_context_is_readable.py

@@ -0,0 +1,406 @@
+from unittest.mock import Mock, patch
+
+import pytest
+
+from src.taipy.gui_core._context import _GuiCoreContext
+from taipy.config.common.scope import Scope
+from taipy.core import Job, Scenario, Task
+from taipy.core.data.pickle import PickleDataNode
+from taipy.gui import Gui
+
+a_scenario = Scenario("scenario_config_id", [], {}, sequences={"sequence": {}})
+a_task = Task("task_config_id", {}, print)
+a_job = Job("JOB_job_id", a_task, "submit_id", a_scenario.id)
+a_job.isfinished = lambda s: True
+a_datanode = PickleDataNode("data_node_config_id", Scope.SCENARIO)
+
+
+def mock_is_readable_false(entity_id):
+    return False
+
+
+def mock_is_true(entity_id):
+    return True
+
+
+def mock_core_get(entity_id):
+    if entity_id == a_scenario.id:
+        return a_scenario
+    if entity_id == a_job.id:
+        return a_job
+    if entity_id == a_datanode.id:
+        return a_datanode
+    return a_task
+
+
+class MockState:
+    def __init__(self, **kwargs) -> None:
+        self.assign = kwargs.get("assign")
+
+
+class TestGuiCoreContext_is_readable:
+    def test_scenario_adapter(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            outcome = gui_core_context.scenario_adapter(a_scenario)
+            assert isinstance(outcome, tuple)
+            assert outcome[0] == a_scenario.id
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                outcome = gui_core_context.scenario_adapter(a_scenario)
+                assert outcome is None
+
+    def test_get_scenario_by_id(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            outcome = gui_core_context.get_scenario_by_id(a_scenario.id)
+            assert outcome is not None
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                outcome = gui_core_context.get_scenario_by_id(a_scenario.id)
+                assert outcome is None
+
+    def test_crud_scenario(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.crud_scenario(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        True,
+                        False,
+                        {"name": "name", "id": a_scenario.id},
+                    ]
+                },
+            )
+            assign.assert_not_called()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                assign.reset_mock()
+                gui_core_context.crud_scenario(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            True,
+                            False,
+                            {"name": "name", "id": a_scenario.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sc_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_edit_entity(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.edit_entity(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"name": "name", "id": a_scenario.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_sv_error"
+            assert assign.call_args.args[1] == ""
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                assign.reset_mock()
+                gui_core_context.edit_entity(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"name": "name", "id": a_scenario.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sv_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_scenario_status_callback(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
+            mockget.reset_mock()
+            gui_core_context = _GuiCoreContext(Mock())
+            gui_core_context.scenario_status_callback(a_job.id)
+            mockget.assert_called()
+            found = False
+            for call in mockget.call_args_list:
+                if call.args[0] == a_job.id:
+                    found = True
+                    break
+            assert found is True
+            mockget.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.scenario_status_callback(a_job.id)
+                mockget.assert_not_called()
+
+    def test_data_node_adapter(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            outcome = gui_core_context.data_node_adapter(a_datanode)
+            assert isinstance(outcome, tuple)
+            assert outcome[0] == a_datanode.id
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                outcome = gui_core_context.data_node_adapter(a_datanode)
+                assert outcome is None
+
+    def test_job_adapter(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            outcome = gui_core_context.job_adapter(a_job)
+            assert isinstance(outcome, tuple)
+            assert outcome[0] == a_job.id
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                outcome = gui_core_context.job_adapter(a_job)
+                assert outcome is None
+
+    def test_act_on_jobs(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
+            "src.taipy.gui_core._context.is_deletable", side_effect=mock_is_true
+        ):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.act_on_jobs(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": [a_job.id], "action": "delete"},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_js_error"
+            assert str(assign.call_args.args[1]).find("is not readable.") == -1
+            assign.reset_mock()
+
+            gui_core_context.act_on_jobs(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": [a_job.id], "action": "cancel"},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_js_error"
+            assert str(assign.call_args.args[1]).find("is not readable.") == -1
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.act_on_jobs(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": [a_job.id], "action": "delete"},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_js_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assign.reset_mock()
+
+                gui_core_context.act_on_jobs(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": [a_job.id], "action": "cancel"},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_js_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_edit_data_node(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.edit_data_node(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": a_datanode.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_dv_error"
+            assert assign.call_args.args[1] == ""
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                assign.reset_mock()
+                gui_core_context.edit_data_node(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": a_datanode.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_lock_datanode_for_edit(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            mockGui = Mock(Gui)
+            mockGui._get_client_id = lambda: "a_client_id"
+            gui_core_context = _GuiCoreContext(mockGui)
+            assign = Mock()
+            gui_core_context.lock_datanode_for_edit(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": a_datanode.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_dv_error"
+            assert assign.call_args.args[1] == ""
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                assign.reset_mock()
+                gui_core_context.lock_datanode_for_edit(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": a_datanode.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_get_scenarios_for_owner(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
+            gui_core_context = _GuiCoreContext(Mock())
+            gui_core_context.get_scenarios_for_owner(a_scenario.id)
+            mockget.assert_called_once()
+            mockget.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.scenario_status_callback(a_scenario.id)
+                mockget.assert_not_called()
+
+    def test_update_data(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            mockGui = Mock(Gui)
+            mockGui._get_client_id = lambda: "a_client_id"
+            gui_core_context = _GuiCoreContext(mockGui)
+            assign = Mock()
+            gui_core_context.update_data(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"id": a_datanode.id},
+                    ]
+                },
+            )
+            assign.assert_called()
+            assert assign.call_args_list[0].args[0] == "gui_core_dv_error"
+            assert assign.call_args_list[0].args[1] == ""
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.update_data(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"id": a_datanode.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_tabular_data_edit(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get):
+            mockGui = Mock(Gui)
+            mockGui._get_client_id = lambda: "a_client_id"
+            gui_core_context = _GuiCoreContext(mockGui)
+            assign = Mock()
+            gui_core_context.tabular_data_edit(
+                MockState(assign=assign),
+                "",
+                {
+                    "user_data": {"dn_id": a_datanode.id},
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args_list[0].args[0] == "gui_core_dv_error"
+            assert (
+                assign.call_args_list[0].args[1]
+                == "Error updating Datanode tabular value: type does not support at[] indexer."
+            )
+            assign.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.tabular_data_edit(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "user_data": {"dn_id": a_datanode.id},
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_dv_error"
+                assert str(assign.call_args.args[1]).endswith("is not readable.")
+
+    def test_get_data_node_tabular_data(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
+            gui_core_context = _GuiCoreContext(Mock())
+            gui_core_context.get_data_node_tabular_data(a_datanode, a_datanode.id)
+            mockget.assert_called_once()
+            mockget.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.get_data_node_tabular_data(a_datanode, a_datanode.id)
+                mockget.assert_not_called()
+
+    def test_get_data_node_tabular_columns(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
+            gui_core_context = _GuiCoreContext(Mock())
+            gui_core_context.get_data_node_tabular_columns(a_datanode, a_datanode.id)
+            mockget.assert_called_once()
+            mockget.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.get_data_node_tabular_columns(a_datanode, a_datanode.id)
+                mockget.assert_not_called()
+
+    def test_get_data_node_chart_config(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
+            gui_core_context = _GuiCoreContext(Mock())
+            gui_core_context.get_data_node_chart_config(a_datanode, a_datanode.id)
+            mockget.assert_called_once()
+            mockget.reset_mock()
+
+            with patch("src.taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
+                gui_core_context.get_data_node_chart_config(a_datanode, a_datanode.id)
+                mockget.assert_not_called()

+ 73 - 0
tests/gui_core/test_context_is_submitable.py

@@ -0,0 +1,73 @@
+from unittest.mock import Mock, patch
+
+import pytest
+
+from src.taipy.gui_core._context import _GuiCoreContext
+from taipy.config.common.scope import Scope
+from taipy.core import Job, Scenario, Task
+from taipy.core.data.pickle import PickleDataNode
+
+a_scenario = Scenario("scenario_config_id", [], {}, sequences={"sequence": {}})
+a_task = Task("task_config_id", {}, print)
+a_job = Job("JOB_job_id", a_task, "submit_id", a_scenario.id)
+a_job.isfinished = lambda s: True
+a_datanode = PickleDataNode("data_node_config_id", Scope.SCENARIO)
+
+
+def mock_is_submittable_false(entity_id):
+    return False
+
+
+def mock_is_true(entity_id):
+    return True
+
+
+def mock_core_get(entity_id):
+    if entity_id == a_scenario.id:
+        return a_scenario
+    if entity_id == a_job.id:
+        return a_job
+    if entity_id == a_datanode.id:
+        return a_datanode
+    return a_task
+
+
+class MockState:
+    def __init__(self, **kwargs) -> None:
+        self.assign = kwargs.get("assign")
+
+
+class TestGuiCoreContext_is_submittable:
+    def test_submit_entity(self):
+        with patch("src.taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
+            "src.taipy.gui_core._context.is_submittable", side_effect=mock_is_true
+        ):
+            gui_core_context = _GuiCoreContext(Mock())
+            assign = Mock()
+            gui_core_context.submit_entity(
+                MockState(assign=assign),
+                "",
+                {
+                    "args": [
+                        {"name": "name", "id": a_scenario.id},
+                    ]
+                },
+            )
+            assign.assert_called_once()
+            assert assign.call_args.args[0] == "gui_core_sv_error"
+            assert str(assign.call_args.args[1]).startswith("Error submitting entity.")
+
+            with patch("src.taipy.gui_core._context.is_submittable", side_effect=mock_is_submittable_false):
+                assign.reset_mock()
+                gui_core_context.submit_entity(
+                    MockState(assign=assign),
+                    "",
+                    {
+                        "args": [
+                            {"name": "name", "id": a_scenario.id},
+                        ]
+                    },
+                )
+                assign.assert_called_once()
+                assert assign.call_args.args[0] == "gui_core_sv_error"
+                assert str(assign.call_args.args[1]).endswith("is not submittable.")