Browse Source

chore: move tests inside root test folder

Joao Andre 1 year ago
parent
commit
1a91e23c1f

+ 10 - 0
tests/rest/__init__.py

@@ -0,0 +1,10 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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.

+ 315 - 0
tests/rest/conftest.py

@@ -0,0 +1,315 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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.
+
+import os
+import shutil
+import uuid
+from datetime import datetime, timedelta
+
+import pandas as pd
+import pytest
+from dotenv import load_dotenv
+
+from src.taipy.rest.app import create_app
+from taipy.config import Config
+from taipy.config.common.frequency import Frequency
+from taipy.config.common.scope import Scope
+from taipy.core import Cycle, DataNodeId, Job, JobId, Scenario, Sequence, Task
+from taipy.core.cycle._cycle_manager import _CycleManager
+from taipy.core.data.in_memory import InMemoryDataNode
+from taipy.core.job._job_manager import _JobManager
+from taipy.core.task._task_manager import _TaskManager
+
+from .setup.shared.algorithms import evaluate, forecast
+
+
+@pytest.fixture
+def setup_end_to_end():
+    model_cfg = Config.configure_data_node("model", path="setup/my_model.p", storage_type="pickle")
+
+    day_cfg = Config.configure_data_node(id="day")
+    forecasts_cfg = Config.configure_data_node(id="forecasts")
+    forecast_task_cfg = Config.configure_task(
+        id="forecast_task",
+        input=[model_cfg, day_cfg],
+        function=forecast,
+        output=forecasts_cfg,
+    )
+
+    historical_temperature_cfg = Config.configure_data_node(
+        "historical_temperature",
+        storage_type="csv",
+        path="setup/historical_temperature.csv",
+        has_header=True,
+    )
+    evaluation_cfg = Config.configure_data_node("evaluation")
+    evaluate_task_cfg = Config.configure_task(
+        "evaluate_task",
+        input=[historical_temperature_cfg, forecasts_cfg, day_cfg],
+        function=evaluate,
+        output=evaluation_cfg,
+    )
+
+    scenario_config = Config.configure_scenario(
+        "scenario", [forecast_task_cfg, evaluate_task_cfg], frequency=Frequency.DAILY
+    )
+    scenario_config.add_sequences({"sequence": [forecast_task_cfg, evaluate_task_cfg]})
+
+
+@pytest.fixture()
+def app():
+    load_dotenv(".testenv")
+    app = create_app(testing=True)
+    app.config.update(
+        {
+            "TESTING": True,
+        }
+    )
+    with app.app_context(), app.test_request_context():
+        yield app
+
+
+@pytest.fixture()
+def client(app):
+    return app.test_client()
+
+
+@pytest.fixture
+def datanode_data():
+    return {
+        "name": "foo",
+        "storage_type": "in_memory",
+        "scope": "scenario",
+        "default_data": ["1991-01-01T00:00:00"],
+    }
+
+
+@pytest.fixture
+def task_data():
+    return {
+        "config_id": "foo",
+        "input_ids": ["DATASOURCE_foo_3b888e17-1974-4a56-a42c-c7c96bc9cd54"],
+        "function_name": "print",
+        "function_module": "builtins",
+        "output_ids": ["DATASOURCE_foo_4d9923b8-eb9f-4f3c-8055-3a1ce8bee309"],
+    }
+
+
+@pytest.fixture
+def sequence_data():
+    return {
+        "name": "foo",
+        "task_ids": ["TASK_foo_3b888e17-1974-4a56-a42c-c7c96bc9cd54"],
+    }
+
+
+@pytest.fixture
+def scenario_data():
+    return {
+        "name": "foo",
+        "sequence_ids": ["SEQUENCE_foo_3b888e17-1974-4a56-a42c-c7c96bc9cd54"],
+        "properties": {},
+    }
+
+
+@pytest.fixture
+def default_datanode():
+    return InMemoryDataNode(
+        "input_ds",
+        Scope.SCENARIO,
+        DataNodeId("f"),
+        "my name",
+        "owner_id",
+        properties={"default_data": [1, 2, 3, 4, 5, 6]},
+    )
+
+
+@pytest.fixture
+def default_df_datanode():
+    return InMemoryDataNode(
+        "input_ds",
+        Scope.SCENARIO,
+        DataNodeId("id_uio2"),
+        "my name",
+        "owner_id",
+        properties={"default_data": pd.DataFrame([{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}])},
+    )
+
+
+@pytest.fixture
+def default_datanode_config():
+    return Config.configure_data_node(f"taipy_{uuid.uuid4().hex}", "in_memory", Scope.SCENARIO)
+
+
+@pytest.fixture
+def default_datanode_config_list():
+    configs = []
+    for i in range(10):
+        configs.append(Config.configure_data_node(id=f"ds_{i}", storage_type="in_memory", scope=Scope.SCENARIO))
+    return configs
+
+
+def __default_task():
+    input_ds = InMemoryDataNode(
+        "input_ds",
+        Scope.SCENARIO,
+        DataNodeId("id_uio"),
+        "my name",
+        "owner_id",
+        properties={"default_data": "In memory Data Source"},
+    )
+
+    output_ds = InMemoryDataNode(
+        "output_ds",
+        Scope.SCENARIO,
+        DataNodeId("id_uio"),
+        "my name",
+        "owner_id",
+        properties={"default_data": "In memory Data Source"},
+    )
+    return Task(
+        config_id="foo",
+        properties={},
+        function=print,
+        input=[input_ds],
+        output=[output_ds],
+        id=None,
+    )
+
+
+@pytest.fixture
+def default_task():
+    return __default_task()
+
+
+@pytest.fixture
+def default_task_config():
+    return Config.configure_task("task1", print, [], [])
+
+
+@pytest.fixture
+def default_task_config_list():
+    configs = []
+    for i in range(10):
+        configs.append(Config.configure_task(f"task_{i}", print, [], []))
+    return configs
+
+
+def __default_sequence():
+    return Sequence(properties={"name": "foo"}, tasks=[__default_task()], sequence_id="SEQUENCE_foo_SCENARIO_acb")
+
+
+def __task_config():
+    return Config.configure_task("task1", print, [], [])
+
+
+@pytest.fixture
+def default_sequence():
+    return __default_sequence()
+
+
+@pytest.fixture
+def default_scenario_config():
+    task_config = __task_config()
+    scenario_config = Config.configure_scenario(
+        f"taipy_{uuid.uuid4().hex}",
+        [task_config],
+    )
+    scenario_config.add_sequences({"sequence": [task_config]})
+    return scenario_config
+
+
+@pytest.fixture
+def default_scenario_config_list():
+    configs = []
+    for _ in range(10):
+        task_config = Config.configure_task(f"taipy_{uuid.uuid4().hex}", print)
+        scenario_config = Config.configure_scenario(
+            f"taipy_{uuid.uuid4().hex}",
+            [task_config],
+        )
+        scenario_config.add_sequences({"sequence": [task_config]})
+        configs.append(scenario_config)
+    return configs
+
+
+@pytest.fixture
+def default_scenario():
+    return Scenario(config_id="foo", properties={}, tasks=[__default_task()], scenario_id="SCENARIO_scenario_id")
+
+
+def __create_cycle(name="foo"):
+    now = datetime.now()
+    return Cycle(
+        name=name,
+        frequency=Frequency.DAILY,
+        properties={},
+        creation_date=now,
+        start_date=now,
+        end_date=now + timedelta(days=5),
+    )
+
+
+@pytest.fixture
+def create_cycle_list():
+    cycles = []
+    manager = _CycleManager
+    for i in range(10):
+        c = __create_cycle(f"cycle_{1}")
+        manager._set(c)
+    return cycles
+
+
+@pytest.fixture
+def cycle_data():
+    return {
+        "name": "foo",
+        "frequency": "daily",
+        "properties": {},
+        "creation_date": "2022-02-03T22:17:27.317114",
+        "start_date": "2022-02-03T22:17:27.317114",
+        "end_date": "2022-02-08T22:17:27.317114",
+    }
+
+
+@pytest.fixture
+def default_cycle():
+    return __create_cycle()
+
+
+def __create_job():
+    task_manager = _TaskManager
+    task = __default_task()
+    task_manager._set(task)
+    submit_id = f"SUBMISSION_{str(uuid.uuid4())}"
+    return Job(id=JobId(f"JOB_{uuid.uuid4()}"), task=task, submit_id=submit_id, submit_entity_id=task.id)
+
+
+@pytest.fixture
+def default_job():
+    return __create_job()
+
+
+@pytest.fixture
+def create_job_list():
+    jobs = []
+    manager = _JobManager
+    for i in range(10):
+        c = __create_job()
+        manager._set(c)
+    return jobs
+
+
+@pytest.fixture(scope="function", autouse=True)
+def cleanup_files():
+    Config.unblock_update()
+    if os.path.exists(".data"):
+        shutil.rmtree(".data")

+ 9 - 0
tests/rest/json/expected/cycle.json

@@ -0,0 +1,9 @@
+{
+    "id": "CYCLE_Frequency.DAILY_2022-03-31T215052.349698_4dfea10a-605f-4d02-91cd-cc8c43cd7d2f",
+    "name": "Frequency.DAILY_2022-03-31T21:50:52.349698",
+    "frequency": "<Frequency.DAILY: 1>",
+    "properties": {},
+    "creation_date": "2022-03-31T21:50:52.349698",
+    "start_date": "2022-03-31T00:00:00",
+    "end_date": "2022-03-31T23:59:59.999999"
+}

+ 18 - 0
tests/rest/json/expected/datanode.json

@@ -0,0 +1,18 @@
+{
+    "id": "DATANODE_day_8c0595aa-2dcf-4080-aa78-2eebd7619c9b",
+    "config_id": "day",
+    "scope": "<Scope.SCENARIO: 2>",
+    "storage_type": "pickle",
+    "name": "DATANODE_day_8c0595aa-2dcf-4080-aa78-2eebd7619c9b",
+    "owner_id": "SCENARIO_scenario_a9c3eea2-2af3-4a85-a0c3-ef98ff5bd586",
+    "parent_ids": ["TASK_evaluate_task_31f1ecdf-2b75-4cd8-9509-9e70edab9189"],
+    "last_edit_date": null,
+    "job_ids": [],
+    "validity_days": null,
+    "validity_seconds": null,
+    "edit_in_progress": false,
+    "cacheable": false,
+    "data_node_properties": {
+    },
+    "version": "1.0"
+}

+ 15 - 0
tests/rest/json/expected/job.json

@@ -0,0 +1,15 @@
+{
+    "id": "JOB_602e112d-2bfa-4813-8d02-79da294fe56f",
+    "task_id": "TASK_evaluate_task_bf7796b3-e248-4e44-a87f-420b748ea461",
+    "status": "<Status.BLOCKED: 2>",
+    "force": false,
+    "creation_date": "2022-03-31T22:00:23.710789",
+    "subscribers": [
+        {
+            "fct_name": "_Orchestrator._on_status_change",
+            "fct_module": "taipy.core._orchestrator._orchestrator"
+        }
+    ],
+    "stacktrace": [],
+    "version": "1.0"
+}

+ 19 - 0
tests/rest/json/expected/scenario.json

@@ -0,0 +1,19 @@
+{
+    "id": "SCENARIO_scenario_a9c3eea2-2af3-4a85-a0c3-ef98ff5bd586",
+    "config_id": "scenario",
+    "tasks": [
+        "TASK_forecast_task_7e1bac33-15f1-4cfd-8702-17adeee902cb",
+        "TASK_evaluate_task_31f1ecdf-2b75-4cd8-9509-9e70eddc6973"
+    ],
+    "additional_data_nodes": [],
+    "sequences": [
+        "SEQUENCE_sequence_0e1c5dd3-9896-4221-bfc8-6924acd11147"
+    ],
+    "properties": {},
+    "creation_date": "2022-03-31T21:50:52.360872",
+    "primary_scenario": true,
+    "subscribers": [],
+    "tags": [],
+    "cycle": "CYCLE_Frequency.DAILY_2022-03-31T215052.349698_4dfea10a-605f-4d02-91cd-cc8c43cd7d2f",
+    "version": "1.0"
+}

+ 13 - 0
tests/rest/json/expected/sequence.json

@@ -0,0 +1,13 @@
+{
+    "id": "SEQUENCE_sequence_0e1c5dd3-9896-4221-bfc8-6924pjg11147",
+    "owner_id": "SCENARIO_scenario_a9c3eea2-2af3-4a85-a0c3-ef98ff5bd586",
+    "parent_ids": [],
+    "config_id": "sequence",
+    "properties": {},
+    "tasks": [
+        "TASK_forecast_task_7e1bac33-15f1-4cfd-8702-17adeee902cb",
+        "TASK_evaluate_task_31f1ecdf-2b75-4cd8-9509-9e70eddc6973"
+    ],
+    "subscribers": [],
+    "version": "1.0"
+}

+ 17 - 0
tests/rest/json/expected/task.json

@@ -0,0 +1,17 @@
+{
+    "id": "TASK_evaluate_task_31f1ecdf-2b75-4cd8-9509-9e70eddc6973",
+    "owner_id": "SCENARIO_scenario_a9c3eea2-2af3-4a85-a0c3-ef98ff5bd586",
+    "parent_ids": ["SEQUENCE_sequence_0e1c5aa9-9896-4221-bfc8-6924acd11147"],
+    "config_id": "evaluate_task",
+    "input_ids": [
+        "DATANODE_historical_temperature_5525cf20-c3a3-42ce-a1c9-88e988bb49f9",
+        "DATANODE_forecasts_c1d9ace5-6099-44ce-a90f-6518db37bbf4",
+        "DATANODE_day_8c0595aa-2dcf-4080-aa78-2eebd7619c9b"
+    ],
+    "function_name": "evaluate",
+    "function_module": "tests.setup.shared.algorithms",
+    "output_ids": [
+        "DATANODE_evaluation_a208b475-11ba-452f-aec6-077e46ced49b"
+    ],
+    "version": "1.0"
+}

+ 10 - 0
tests/rest/setup/__init__.py

@@ -0,0 +1,10 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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.

BIN
tests/rest/setup/my_model.p


+ 10 - 0
tests/rest/setup/shared/__init__.py

@@ -0,0 +1,10 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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.

+ 55 - 0
tests/rest/setup/shared/algorithms.py

@@ -0,0 +1,55 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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.
+
+import pickle
+import random
+from datetime import datetime, timedelta
+from typing import Any, Dict
+
+import pandas as pd
+
+n_predictions = 14
+
+
+def forecast(model, date: datetime):
+    dates = [date + timedelta(days=i) for i in range(n_predictions)]
+    forecasts = [f + random.uniform(0, 2) for f in model.forecast(len(dates))]
+    days = [str(dt.date()) for dt in dates]
+    res = {"Date": days, "Forecast": forecasts}
+    return pd.DataFrame.from_dict(res)
+
+
+def evaluate(cleaned: pd.DataFrame, forecasts: pd.DataFrame, date: datetime) -> Dict[str, Any]:
+    cleaned = cleaned[cleaned["Date"].isin(forecasts["Date"].tolist())]
+    forecasts_as_series = pd.Series(forecasts["Forecast"].tolist(), name="Forecast")
+    res = pd.concat([cleaned.reset_index(), forecasts_as_series], axis=1)
+    res["Delta"] = abs(res["Forecast"] - res["Value"])
+
+    return {
+        "Date": date,
+        "Dataframe": res,
+        "Mean_absolute_error": res["Delta"].mean(),
+        "Relative_error": (res["Delta"].mean() * 100) / res["Value"].mean(),
+    }
+
+
+if __name__ == "__main__":
+    model = pickle.load(open("../my_model.p", "rb"))
+    day = datetime(2020, 1, 25)
+    forecasts = forecast(model, day)
+
+    historical_temperature = pd.read_csv("../historical_temperature.csv")
+    evaluation = evaluate(historical_temperature, forecasts, day)
+
+    print(evaluation["Dataframe"])
+    print()
+    print(f'Mean absolute error : {evaluation["Mean_absolute_error"]}')
+    print(f'Relative error in %: {evaluation["Relative_error"]}')

+ 42 - 0
tests/rest/setup/shared/config.py

@@ -0,0 +1,42 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 taipy.core import Config, Frequency
+
+from .algorithms import evaluate, forecast
+
+model_cfg = Config.configure_data_node("model", path="my_model.p", storage_type="pickle")
+
+day_cfg = Config.configure_data_node(id="day")
+forecasts_cfg = Config.configure_data_node(id="forecasts")
+forecast_task_cfg = Config.configure_task(
+    id="forecast_task",
+    input=[model_cfg, day_cfg],
+    function=forecast,
+    output=forecasts_cfg,
+)
+
+historical_temperature_cfg = Config.configure_data_node(
+    "historical_temperature",
+    storage_type="csv",
+    path="historical_temperature.csv",
+    has_header=True,
+)
+evaluation_cfg = Config.configure_data_node("evaluation")
+evaluate_task_cfg = Config.configure_task(
+    "evaluate_task",
+    input=[historical_temperature_cfg, forecasts_cfg, day_cfg],
+    function=evaluate,
+    output=evaluation_cfg,
+)
+
+scenario_cfg = Config.configure_scenario("scenario", [forecast_task_cfg, evaluate_task_cfg], frequency=Frequency.DAILY)
+scenario_cfg.add_sequences({"sequence": [forecast_task_cfg, evaluate_task_cfg]})

+ 62 - 0
tests/rest/test_cycle.py

@@ -0,0 +1,62 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 unittest import mock
+
+from flask import url_for
+
+
+def test_get_cycle(client, default_cycle):
+    # test 404
+    cycle_url = url_for("api.cycle_by_id", cycle_id="foo")
+    rep = client.get(cycle_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.cycle._cycle_manager._CycleManager._get") as manager_mock:
+        manager_mock.return_value = default_cycle
+
+        # test get_cycle
+        rep = client.get(url_for("api.cycle_by_id", cycle_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_delete_cycle(client):
+    # test 404
+    cycle_url = url_for("api.cycle_by_id", cycle_id="foo")
+    rep = client.get(cycle_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.cycle._cycle_manager._CycleManager._delete"), mock.patch(
+        "taipy.core.cycle._cycle_manager._CycleManager._get"
+    ):
+        # test get_cycle
+        rep = client.delete(url_for("api.cycle_by_id", cycle_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_create_cycle(client, cycle_data):
+    # without config param
+    cycles_url = url_for("api.cycles")
+    data = {"bad": "data"}
+    rep = client.post(cycles_url, json=data)
+    assert rep.status_code == 400
+
+    rep = client.post(cycles_url, json=cycle_data)
+    assert rep.status_code == 201
+
+
+def test_get_all_cycles(client, create_cycle_list):
+    cycles_url = url_for("api.cycles")
+    rep = client.get(cycles_url)
+    assert rep.status_code == 200
+
+    results = rep.get_json()
+    assert len(results) == 10

+ 111 - 0
tests/rest/test_datanode.py

@@ -0,0 +1,111 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 unittest import mock
+
+import pytest
+from flask import url_for
+
+
+def test_get_datanode(client, default_datanode):
+    # test 404
+    user_url = url_for("api.datanode_by_id", datanode_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.data._data_manager._DataManager._get") as manager_mock:
+        manager_mock.return_value = default_datanode
+        # test get_datanode
+        rep = client.get(url_for("api.datanode_by_id", datanode_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_delete_datanode(client):
+    # test 404
+    user_url = url_for("api.datanode_by_id", datanode_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.data._data_manager._DataManager._delete"), mock.patch(
+        "taipy.core.data._data_manager._DataManager._get"
+    ):
+        # test get_datanode
+        rep = client.delete(url_for("api.datanode_by_id", datanode_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_create_datanode(client, default_datanode_config):
+    # without config param
+    datanodes_url = url_for("api.datanodes")
+    rep = client.post(datanodes_url)
+    assert rep.status_code == 400
+
+    # config does not exist
+    datanodes_url = url_for("api.datanodes", config_id="foo")
+    rep = client.post(datanodes_url)
+    assert rep.status_code == 404
+
+    with mock.patch("src.taipy.rest.api.resources.datanode.DataNodeList.fetch_config") as config_mock:
+        config_mock.return_value = default_datanode_config
+        datanodes_url = url_for("api.datanodes", config_id="bar")
+        rep = client.post(datanodes_url)
+        assert rep.status_code == 201
+
+
+def test_get_all_datanodes(client, default_datanode_config_list):
+    for ds in range(10):
+        with mock.patch("src.taipy.rest.api.resources.datanode.DataNodeList.fetch_config") as config_mock:
+            config_mock.return_value = default_datanode_config_list[ds]
+            datanodes_url = url_for("api.datanodes", config_id=config_mock.name)
+            client.post(datanodes_url)
+
+    rep = client.get(datanodes_url)
+    assert rep.status_code == 200
+
+    results = rep.get_json()
+    assert len(results) == 10
+
+
+def test_read_datanode(client, default_df_datanode):
+    with mock.patch("taipy.core.data._data_manager._DataManager._get") as config_mock:
+        config_mock.return_value = default_df_datanode
+
+        # without operators
+        datanodes_url = url_for("api.datanode_reader", datanode_id="foo")
+        rep = client.get(datanodes_url, json={})
+        assert rep.status_code == 200
+
+        # Without operators and body
+        rep = client.get(datanodes_url)
+        assert rep.status_code == 200
+
+        # TODO: Revisit filter test
+        # operators = {"operators": [{"key": "a", "value": 5, "operator": "LESS_THAN"}]}
+        # rep = client.get(datanodes_url, json=operators)
+        # assert rep.status_code == 200
+
+
+def test_write_datanode(client, default_datanode):
+    with mock.patch("taipy.core.data._data_manager._DataManager._get") as config_mock:
+        config_mock.return_value = default_datanode
+        # Get DataNode
+        datanodes_read_url = url_for("api.datanode_reader", datanode_id=default_datanode.id)
+        rep = client.get(datanodes_read_url, json={})
+        assert rep.status_code == 200
+        assert rep.json == {"data": [1, 2, 3, 4, 5, 6]}
+
+        datanodes_write_url = url_for("api.datanode_writer", datanode_id=default_datanode.id)
+        rep = client.put(datanodes_write_url, json=[1, 2, 3])
+        assert rep.status_code == 200
+
+        rep = client.get(datanodes_read_url, json={})
+        assert rep.status_code == 200
+        assert rep.json == {"data": [1, 2, 3]}

+ 100 - 0
tests/rest/test_end_to_end.py

@@ -0,0 +1,100 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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.
+
+import json
+from typing import Dict
+
+from flask import url_for
+
+
+def create_and_submit_scenario(config_id: str, client) -> Dict:
+    response = client.post(url_for("api.scenarios", config_id=config_id))
+    assert response.status_code == 201
+
+    scenario = response.json.get("scenario")
+    assert (set(scenario) - set(json.load(open("tests/rest/json/expected/scenario.json")))) == set()
+
+    response = client.post(url_for("api.scenario_submit", scenario_id=scenario.get("id")))
+    assert response.status_code == 200
+
+    return scenario
+
+
+def get(url, name, client) -> Dict:
+    response = client.get(url)
+    returned_data = response.json.get(name)
+
+    assert (set(returned_data) - set(json.load(open(f"tests/rest/json/expected/{name}.json")))) == set()
+
+    return returned_data
+
+
+def get_assert_status(url, client, status_code) -> None:
+    response = client.get(url)
+    assert response.status_code == status_code
+
+
+def get_all(url, expected_quantity, client):
+    response = client.get(url)
+
+    assert len(response.json) == expected_quantity
+
+
+def delete(url, client):
+    response = client.delete(url)
+
+    assert response.status_code == 200
+
+
+def test_end_to_end(client, setup_end_to_end):
+    # Create Scenario: Should also create all of its dependencies(sequences, tasks, datanodes, etc)
+    scenario = create_and_submit_scenario("scenario", client)
+
+    # Get other models and verify if they return the necessary fields
+    cycle = get(url_for("api.cycle_by_id", cycle_id=scenario.get("cycle")), "cycle", client)
+    sequence = get(
+        url_for("api.sequence_by_id", sequence_id=f"SEQUENCE_sequence_{scenario['id']}"),
+        "sequence",
+        client,
+    )
+    task = get(url_for("api.task_by_id", task_id=sequence.get("tasks")[0]), "task", client)
+    datanode = get(
+        url_for("api.datanode_by_id", datanode_id=task.get("input_ids")[0]),
+        "datanode",
+        client,
+    )
+    # Get All
+    get_all(url_for("api.scenarios"), 1, client)
+    get_all(url_for("api.cycles"), 1, client)
+    get_all(url_for("api.sequences"), 1, client)
+    get_all(url_for("api.tasks"), 2, client)
+    get_all(url_for("api.datanodes"), 5, client)
+    get_all(url_for("api.jobs"), 2, client)
+
+    # Delete entities
+    delete(url_for("api.cycle_by_id", cycle_id=cycle.get("id")), client)
+    delete(url_for("api.sequence_by_id", sequence_id=sequence.get("id")), client)
+    delete(url_for("api.task_by_id", task_id=task.get("id")), client)
+    delete(url_for("api.datanode_by_id", datanode_id=datanode.get("id")), client)
+
+    # Check status code
+    # Non-existing entities should return 404
+    get_assert_status(url_for("api.cycle_by_id", cycle_id=9999999), client, 404)
+    get_assert_status(url_for("api.scenario_by_id", scenario_id=9999999), client, 404)
+    get_assert_status(url_for("api.sequence_by_id", sequence_id=9999999), client, 404)
+    get_assert_status(url_for("api.task_by_id", task_id=9999999), client, 404)
+    get_assert_status(url_for("api.datanode_by_id", datanode_id=9999999), client, 404)
+
+    # Check URL with and without trailing slashes
+    url_with_slash = url_for("api.scenarios")
+    url_without_slash = url_for("api.scenarios")[:-1]
+    get_all(url_with_slash, 1, client)
+    get_all(url_without_slash, 1, client)

+ 83 - 0
tests/rest/test_job.py

@@ -0,0 +1,83 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 unittest import mock
+
+from flask import url_for
+
+
+def test_get_job(client, default_job):
+    # test 404
+    user_url = url_for("api.job_by_id", job_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.job._job_manager._JobManager._get") as manager_mock:
+        manager_mock.return_value = default_job
+
+        # test get_job
+        rep = client.get(url_for("api.job_by_id", job_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_delete_job(client):
+    # test 404
+    user_url = url_for("api.job_by_id", job_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.job._job_manager._JobManager._delete"), mock.patch(
+        "taipy.core.job._job_manager._JobManager._get"
+    ):
+        # test get_job
+        rep = client.delete(url_for("api.job_by_id", job_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_create_job(client, default_task_config):
+    # without config param
+    jobs_url = url_for("api.jobs")
+    rep = client.post(jobs_url)
+    assert rep.status_code == 400
+
+    with mock.patch("src.taipy.rest.api.resources.job.JobList.fetch_config") as config_mock:
+        config_mock.return_value = default_task_config
+        jobs_url = url_for("api.jobs", task_id="foo")
+        rep = client.post(jobs_url)
+        assert rep.status_code == 201
+
+
+def test_get_all_jobs(client, create_job_list):
+    jobs_url = url_for("api.jobs")
+    rep = client.get(jobs_url)
+    assert rep.status_code == 200
+
+    results = rep.get_json()
+    assert len(results) == 10
+
+
+def test_cancel_job(client, default_job):
+    # test 404
+    from taipy.core._orchestrator._orchestrator_factory import _OrchestratorFactory
+
+    _OrchestratorFactory._build_orchestrator()
+    _OrchestratorFactory._build_dispatcher()
+
+    user_url = url_for("api.job_cancel", job_id="foo")
+    rep = client.post(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.job._job_manager._JobManager._get") as manager_mock:
+        manager_mock.return_value = default_job
+
+        # test get_job
+        rep = client.post(url_for("api.job_cancel", job_id="foo"))
+        assert rep.status_code == 200

+ 59 - 0
tests/rest/test_middleware.py

@@ -0,0 +1,59 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 functools import wraps
+from unittest.mock import MagicMock, patch
+
+from src.taipy.rest.api.middlewares._middleware import _middleware
+
+
+def mock_enterprise_middleware(f):
+    @wraps(f)
+    def wrapper(*args, **kwargs):
+        return f(*args, **kwargs)
+
+    return wrapper
+
+
+@patch("src.taipy.rest.api.middlewares._middleware._using_enterprise")
+@patch("src.taipy.rest.api.middlewares._middleware._enterprise_middleware")
+def test_enterprise_middleware_applied_when_enterprise_is_installed(
+    enterprise_middleware: MagicMock, using_enterprise: MagicMock
+):
+    enterprise_middleware.return_value = mock_enterprise_middleware
+    using_enterprise.return_value = True
+
+    @_middleware
+    def f():
+        return "f"
+
+    rv = f()
+    assert rv == "f"
+    using_enterprise.assert_called_once()
+    enterprise_middleware.assert_called_once()
+
+
+@patch("src.taipy.rest.api.middlewares._middleware._using_enterprise")
+@patch("src.taipy.rest.api.middlewares._middleware._enterprise_middleware")
+def test_enterprise_middleware_not_applied_when_enterprise_is_not_installed(
+    enterprise_middleware: MagicMock, using_enterprise: MagicMock
+):
+    enterprise_middleware.return_value = mock_enterprise_middleware
+    using_enterprise.return_value = False
+
+    @_middleware
+    def f():
+        return "f"
+
+    rv = f()
+    assert rv == "f"
+    using_enterprise.assert_called_once()
+    enterprise_middleware.assert_not_called()

+ 90 - 0
tests/rest/test_scenario.py

@@ -0,0 +1,90 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 unittest import mock
+
+import pytest
+from flask import url_for
+
+
+def test_get_scenario(client, default_scenario):
+    # test 404
+    user_url = url_for("api.scenario_by_id", scenario_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._get") as manager_mock:
+        manager_mock.return_value = default_scenario
+
+        # test get_scenario
+        rep = client.get(url_for("api.scenario_by_id", scenario_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_delete_scenario(client):
+    # test 404
+    user_url = url_for("api.scenario_by_id", scenario_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._delete"), mock.patch(
+        "taipy.core.scenario._scenario_manager._ScenarioManager._get"
+    ):
+        # test get_scenario
+        rep = client.delete(url_for("api.scenario_by_id", scenario_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_create_scenario(client, default_scenario_config):
+    # without config param
+    scenarios_url = url_for("api.scenarios")
+    rep = client.post(scenarios_url)
+    assert rep.status_code == 400
+
+    # config does not exist
+    scenarios_url = url_for("api.scenarios", config_id="foo")
+    rep = client.post(scenarios_url)
+    assert rep.status_code == 404
+
+    with mock.patch("src.taipy.rest.api.resources.scenario.ScenarioList.fetch_config") as config_mock:
+        config_mock.return_value = default_scenario_config
+        scenarios_url = url_for("api.scenarios", config_id="bar")
+        rep = client.post(scenarios_url)
+        assert rep.status_code == 201
+
+
+def test_get_all_scenarios(client, default_sequence, default_scenario_config_list):
+    for ds in range(10):
+        with mock.patch("src.taipy.rest.api.resources.scenario.ScenarioList.fetch_config") as config_mock:
+            config_mock.return_value = default_scenario_config_list[ds]
+            scenarios_url = url_for("api.scenarios", config_id=config_mock.name)
+            client.post(scenarios_url)
+
+    rep = client.get(scenarios_url)
+    assert rep.status_code == 200
+
+    results = rep.get_json()
+    assert len(results) == 10
+
+
+@pytest.mark.xfail()
+def test_execute_scenario(client, default_scenario):
+    # test 404
+    user_url = url_for("api.scenario_submit", scenario_id="foo")
+    rep = client.post(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._get") as manager_mock:
+        manager_mock.return_value = default_scenario
+
+        # test get_scenario
+        rep = client.post(url_for("api.scenario_submit", scenario_id="foo"))
+        assert rep.status_code == 200

+ 102 - 0
tests/rest/test_sequence.py

@@ -0,0 +1,102 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 unittest import mock
+
+import pytest
+from flask import url_for
+
+from src.taipy.rest.api.exceptions.exceptions import ScenarioIdMissingException, SequenceNameMissingException
+from taipy.core.exceptions.exceptions import NonExistingScenario
+from taipy.core.scenario._scenario_manager_factory import _ScenarioManagerFactory
+
+
+def test_get_sequence(client, default_sequence):
+    # test 404
+    user_url = url_for("api.sequence_by_id", sequence_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.sequence._sequence_manager._SequenceManager._get") as manager_mock:
+        manager_mock.return_value = default_sequence
+
+        # test get_sequence
+        rep = client.get(url_for("api.sequence_by_id", sequence_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_delete_sequence(client):
+    # test 404
+    user_url = url_for("api.sequence_by_id", sequence_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.sequence._sequence_manager._SequenceManager._delete"), mock.patch(
+        "taipy.core.sequence._sequence_manager._SequenceManager._get"
+    ):
+        # test get_sequence
+        rep = client.delete(url_for("api.sequence_by_id", sequence_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_create_sequence(client, default_scenario):
+    sequences_url = url_for("api.sequences")
+    rep = client.post(sequences_url, json={})
+    assert rep.status_code == 400
+    assert rep.json == {"message": "Scenario id is missing."}
+
+    sequences_url = url_for("api.sequences")
+    rep = client.post(sequences_url, json={"scenario_id": "SCENARIO_scenario_id"})
+    assert rep.status_code == 400
+    assert rep.json == {"message": "Sequence name is missing."}
+
+    sequences_url = url_for("api.sequences")
+    rep = client.post(sequences_url, json={"scenario_id": "SCENARIO_scenario_id", "sequence_name": "sequence"})
+    assert rep.status_code == 404
+
+    _ScenarioManagerFactory._build_manager()._set(default_scenario)
+    with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._get") as config_mock:
+        config_mock.return_value = default_scenario
+        sequences_url = url_for("api.sequences")
+        rep = client.post(
+            sequences_url, json={"scenario_id": default_scenario.id, "sequence_name": "sequence", "tasks": []}
+        )
+        assert rep.status_code == 201
+
+
+def test_get_all_sequences(client, default_scenario_config_list):
+    for ds in range(10):
+        with mock.patch("src.taipy.rest.api.resources.scenario.ScenarioList.fetch_config") as config_mock:
+            config_mock.return_value = default_scenario_config_list[ds]
+            scenario_url = url_for("api.scenarios", config_id=config_mock.name)
+            client.post(scenario_url)
+
+    sequences_url = url_for("api.sequences")
+    rep = client.get(sequences_url)
+    assert rep.status_code == 200
+
+    results = rep.get_json()
+    assert len(results) == 10
+
+
+@pytest.mark.xfail()
+def test_execute_sequence(client, default_sequence):
+    # test 404
+    user_url = url_for("api.sequence_submit", sequence_id="foo")
+    rep = client.post(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.sequence._sequence_manager._SequenceManager._get") as manager_mock:
+        manager_mock.return_value = default_sequence
+
+        # test get_sequence
+        rep = client.post(url_for("api.sequence_submit", sequence_id="foo"))
+        assert rep.status_code == 200

+ 88 - 0
tests/rest/test_task.py

@@ -0,0 +1,88 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed 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 unittest import mock
+
+from flask import url_for
+
+
+def test_get_task(client, default_task):
+    # test 404
+    user_url = url_for("api.task_by_id", task_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.task._task_manager._TaskManager._get") as manager_mock:
+        manager_mock.return_value = default_task
+
+        # test get_task
+        rep = client.get(url_for("api.task_by_id", task_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_delete_task(client):
+    # test 404
+    user_url = url_for("api.task_by_id", task_id="foo")
+    rep = client.get(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.task._task_manager._TaskManager._delete"), mock.patch(
+        "taipy.core.task._task_manager._TaskManager._get"
+    ):
+        # test get_task
+        rep = client.delete(url_for("api.task_by_id", task_id="foo"))
+        assert rep.status_code == 200
+
+
+def test_create_task(client, default_task_config):
+    # without config param
+    tasks_url = url_for("api.tasks")
+    rep = client.post(tasks_url)
+    assert rep.status_code == 400
+
+    # config does not exist
+    tasks_url = url_for("api.tasks", config_id="foo")
+    rep = client.post(tasks_url)
+    assert rep.status_code == 404
+
+    with mock.patch("src.taipy.rest.api.resources.task.TaskList.fetch_config") as config_mock:
+        config_mock.return_value = default_task_config
+        tasks_url = url_for("api.tasks", config_id="bar")
+        rep = client.post(tasks_url)
+        assert rep.status_code == 201
+
+
+def test_get_all_tasks(client, task_data, default_task_config_list):
+    for ds in range(10):
+        with mock.patch("src.taipy.rest.api.resources.task.TaskList.fetch_config") as config_mock:
+            config_mock.return_value = default_task_config_list[ds]
+            tasks_url = url_for("api.tasks", config_id=config_mock.name)
+            client.post(tasks_url)
+
+    rep = client.get(tasks_url)
+    assert rep.status_code == 200
+
+    results = rep.get_json()
+    assert len(results) == 10
+
+
+def test_execute_task(client, default_task):
+    # test 404
+    user_url = url_for("api.task_submit", task_id="foo")
+    rep = client.post(user_url)
+    assert rep.status_code == 404
+
+    with mock.patch("taipy.core.task._task_manager._TaskManager._get") as manager_mock:
+        manager_mock.return_value = default_task
+
+        # test get_task
+        rep = client.post(url_for("api.task_submit", task_id="foo"))
+        assert rep.status_code == 200