From 2aca985b0059e38e3a4ea46f382e6e88efc66d06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20Schr=C3=B6der?=
 <fschroeder@techfak.uni-bielefeld.de>
Date: Thu, 29 Feb 2024 14:25:10 +0100
Subject: [PATCH] Update test suite and add coverage configuration

Extended the test suite by adding new test cases and a new test file for dispensers and serving windows. The CI/CD pipeline was updated to include a coverage configuration file (.coveragerc), ensuring lines not relevant to coverage are excluded. Additionally, minor changes were made to the basic.layout file.
---
 .coveragerc                                   |   4 +
 .gitlab-ci.yml                                |   2 +-
 .../configs/layouts/basic.layout              |   2 +-
 tests/test_counter.py                         | 101 ++++++++++++++++++
 tests/test_start.py                           |  29 +++++
 5 files changed, 136 insertions(+), 2 deletions(-)
 create mode 100644 .coveragerc
 create mode 100644 tests/test_counter.py

diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 00000000..58c84c79
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[report]
+exclude_lines =
+    if TYPE_CHECKING:
+    if __name__ == .__main__.:
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4a4f3ff0..d7fbaa1b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,7 +4,7 @@ pytest:
     - apt-get update -qy
     - apt-get install -y python3-dev python3-pip graphviz graphviz-dev
     - pip install '.[test]'
-    - pytest --cov --cov-report term-missing --cov-report xml:./coverage.xml --junitxml=./test.xml
+    - pytest --cov --cov-report term-missing --cov-report xml:./coverage.xml --junitxml=./test.xml --cov-config=.coveragerc
   coverage: '/^TOTAL .+?(\d+%)$/'
   artifacts:
     reports:
diff --git a/cooperative_cuisine/configs/layouts/basic.layout b/cooperative_cuisine/configs/layouts/basic.layout
index f3d0c2ec..556080f3 100644
--- a/cooperative_cuisine/configs/layouts/basic.layout
+++ b/cooperative_cuisine/configs/layouts/basic.layout
@@ -1,5 +1,5 @@
 #QU#FO#TNLB#
-#__________M
+@__________M
 |__________K
 $__________I
 #__A_____A_D
diff --git a/tests/test_counter.py b/tests/test_counter.py
new file mode 100644
index 00000000..b0fb93a9
--- /dev/null
+++ b/tests/test_counter.py
@@ -0,0 +1,101 @@
+import numpy as np
+
+from cooperative_cuisine.counters import ServingWindow, Dispenser
+from cooperative_cuisine.game_items import Item, Plate, ItemInfo, ItemType
+from cooperative_cuisine.hooks import Hooks
+from cooperative_cuisine.utils import create_init_env_time
+
+
+def test_serving_window():
+    class DummyOrderManager:
+        def serve_meal(self, item, env_time, player) -> bool:
+            return (isinstance(item, str) and item == "Test123") or item.content_list[
+                0
+            ].name == "TestMeal"
+
+    class DummyPlateDispenser:
+        plate_received = False
+
+        def update_plate_out_of_kitchen(self, env_time):
+            self.plate_received = True
+
+    plate_dispenser = DummyPlateDispenser()
+
+    serving_window = ServingWindow(
+        order_manager=DummyOrderManager(),
+        meals={"TestMeal", "TestMeal2"},
+        env_time_func=create_init_env_time,
+        plate_dispenser=plate_dispenser,
+        pos=np.array([1.0, 1.0]),
+        hook=Hooks(None),
+    )
+
+    serving_window.drop_off(item="Test123")
+    assert (
+        plate_dispenser.plate_received
+    ), "ServingWindow needs to update plate out of kitchen for ordered meal."
+    plate_dispenser.plate_received = False
+    plate = Plate(transitions={}, clean=True, item_info=None)
+    plate.content_list = [Item(name="TestMeal", item_info=None)]
+    assert serving_window.can_drop_off(
+        item=plate
+    ), "ServingWindow could drop off a known meal."
+    assert (
+        serving_window.drop_off(item=plate) is None
+    ), "ServingWindow drop_off should return None for a served meal."
+    assert (
+        plate_dispenser.plate_received
+    ), "ServingWindow needs to update plate out of kitchen for ordered meal."
+    plate_dispenser.plate_received = False
+
+    plate.content_list = [Item(name="TestMeal2", item_info=None)]
+    assert serving_window.can_drop_off(
+        item=plate
+    ), "ServingWindow could drop off a known meal."
+    assert (
+        serving_window.drop_off(item=plate) == plate
+    ), "ServingWindow should return the item for not ordered meals."
+
+    assert (
+        serving_window.pick_up() is None
+    ), "Player should not be able to pick something from the ServingWindow."
+
+
+def test_dispenser():
+    dispenser = Dispenser(
+        dispensing=ItemInfo(
+            type=ItemType.Ingredient,
+            name="MyIngredient",
+            seconds=0,
+            needs=["MySecondIngredient"],
+            equipment=None,
+        ),
+        pos=np.array([1.0, 1.0]),
+        hook=Hooks(None),
+        undo_dispenser_pickup=False,
+    )
+    assert (
+        dispenser.occupied_by.name == "MyIngredient"
+    ), "Initialized dispenser should be occupied by dispensing item"
+    assert (
+        dispenser.pick_up().name == "MyIngredient"
+    ), "Picked up item should be the dispensing item"
+    assert (
+        dispenser.occupied_by is not None
+    ), "After pickup a new occupied by should be generated"
+    assert (
+        dispenser.occupied_by.name == "MyIngredient"
+    ), "After pick up a new occupied by item should be generated"
+
+    assert not dispenser.can_drop_off(
+        dispenser.pick_up()
+    ), "Config undo_dispenser_pickup==False should stop the player to drop off picked up items"
+
+    dispenser.undo_dispenser_pickup = True
+    assert dispenser.can_drop_off(
+        dispenser.pick_up()
+    ), "Config undo_dispenser_pickup==True should allow the player to drop off picked up items"
+    assert (
+        dispenser.drop_off(dispenser.pick_up()) is None
+    ), "Config undo_dispenser_pickup==True should allow the player to drop off picked up items"
+    # check combine?
diff --git a/tests/test_start.py b/tests/test_start.py
index eb4746df..e69b1ac9 100644
--- a/tests/test_start.py
+++ b/tests/test_start.py
@@ -12,7 +12,17 @@ from cooperative_cuisine.environment import (
     InterActionData,
 )
 from cooperative_cuisine.game_items import Item, ItemInfo, ItemType
+from cooperative_cuisine.game_server import PlayerRequestType
 from cooperative_cuisine.hooks import Hooks
+from cooperative_cuisine.server_results import (
+    PlayerInfo,
+    CreateEnvResult,
+    PlayerRequestResult,
+)
+from cooperative_cuisine.state_representation import (
+    StateRepresentation,
+    create_json_schema,
+)
 from cooperative_cuisine.utils import create_init_env_time
 
 layouts_folder = ROOT_DIR / "configs" / "layouts"
@@ -118,6 +128,10 @@ def test_player_movement_speed(env_config, layout_empty_config, item_info):
         np.linalg.norm(expected - env.players[player_name].pos), 0
     ), "Performed movement do not move the player as expected."
 
+    assert StateRepresentation.model_validate_json(
+        json_data=env.get_json_state(player_id="1")
+    ), "json state does not match expected StateRepresentation."
+
 
 def test_player_reach(env_config, layout_empty_config, item_info):
     env = Environment(env_config, layout_empty_config, item_info, as_files=False)
@@ -281,3 +295,18 @@ def test_time_limit():
     env.step(passed_time_2)
 
     assert env.game_ended, "Game has ended now."
+
+
+def test_json_schema():
+    assert isinstance(create_json_schema(), dict)
+
+
+def test_server_result_definition():
+    plater_info = PlayerInfo(client_id="123", player_hash="234567890", player_id="0")
+    CreateEnvResult(env_id="123344", player_info={"0": plater_info}, recipe_graphs=[])
+    PlayerRequestResult(
+        request_type=PlayerRequestType.READY,
+        status=200,
+        msg="123",
+        player_hash="1234324",
+    )
-- 
GitLab