diff --git a/assets/chiv2-server-browser-api.yaml b/assets/chiv2-server-browser-api.yaml index bf4a8a6..34ba1a5 100644 --- a/assets/chiv2-server-browser-api.yaml +++ b/assets/chiv2-server-browser-api.yaml @@ -554,10 +554,27 @@ components: current_map: type: string description: The current map being played on the server + map_rotation: + $ref: '#/components/schemas/MapRotation' mods: type: array items: $ref: '#/components/schemas/Mod' + MapRotation: + type: object + description: The map rotation for the server + required: + - maps + properties: + maps: + type: array + items: + type: string + description: An ordered list of maps in the rotation. + index: + type: integer + format: int32 + description: The index of the current map Mod: type: object required: @@ -623,6 +640,8 @@ components: current_map: type: string description: The current map being played on the server + map_rotation: + $ref: '#/components/schemas/MapRotation' player_count: type: integer format: int32 @@ -663,6 +682,8 @@ components: current_map: type: string description: The current map being played on the server + map_rotation: + $ref: '#/components/schemas/MapRotation' player_count: type: integer format: int32 diff --git a/src/server_browser_backend/models/base_models.py b/src/server_browser_backend/models/base_models.py index 65e9ee9..67ff55f 100644 --- a/src/server_browser_backend/models/base_models.py +++ b/src/server_browser_backend/models/base_models.py @@ -23,6 +23,19 @@ def from_json(json: dict): ) +@dataclass(frozen=True) +class MapRotation: + maps: List[str] + index: Optional[int] = None + + @staticmethod + def from_json(json: dict): + return MapRotation( + get_or(json, "maps", list), + get_or_optional(json, "index", int), + ) + + @dataclass(frozen=True) class ServerRegistrationRequest: ports: Chivalry2Ports @@ -34,6 +47,7 @@ class ServerRegistrationRequest: max_players: int mods: List[Mod] local_ip_address: Optional[str] + map_rotation: Optional[MapRotation] @staticmethod def from_json(json: dict): @@ -49,6 +63,7 @@ def from_json(json: dict): get_or(json, "max_players", int), list(map(Mod.from_json, mod_objs)), get_or_optional(json, "local_ip_address", str), + MapRotation.from_json(json["map_rotation"]) if "map_rotation" in json else None, ) @@ -67,6 +82,7 @@ class Server: max_players: int is_verified: bool mods: List[Mod] + map_rotation: Optional[MapRotation] @staticmethod def create_after_registration( @@ -86,6 +102,7 @@ def create_after_registration( registration.max_players, False, registration.mods, + registration.map_rotation, ) @staticmethod @@ -106,6 +123,7 @@ def from_json(json: dict): get_or(json, "max_players", int), False, list(map(Mod.from_json, mod_objs)), + MapRotation.from_json(json["map_rotation"]) if "map_rotation" in json else None, ) def with_heartbeat(self, heartbeat_time: float): @@ -123,6 +141,7 @@ def with_heartbeat(self, heartbeat_time: float): self.max_players, self.is_verified, self.mods, + self.map_rotation, ) def with_update(self, update_request: UpdateRegisteredServer) -> Server: @@ -140,6 +159,7 @@ def with_update(self, update_request: UpdateRegisteredServer) -> Server: update_request.max_players, self.is_verified, update_request.mods if update_request.mods is not None else self.mods, + update_request.map_rotation if update_request.map_rotation is not None else self.map_rotation, ) def unverified(self) -> Server: @@ -157,6 +177,7 @@ def unverified(self) -> Server: self.max_players, False, self.mods, + self.map_rotation, ) def verified(self) -> Server: @@ -174,6 +195,7 @@ def verified(self) -> Server: self.max_players, True, self.mods, + self.map_rotation, ) @@ -190,6 +212,7 @@ class ServerResponse: max_players: int is_verified: bool mods: List[Mod] + map_rotation: Optional[MapRotation] = None @staticmethod def from_server(server: Server) -> ServerResponse: @@ -205,6 +228,7 @@ def from_server(server: Server) -> ServerResponse: server.max_players, server.is_verified, server.mods, + server.map_rotation, ) @@ -215,6 +239,7 @@ class UpdateRegisteredServer: player_count: int max_players: int mods: List[Mod] | None + map_rotation: Optional[MapRotation] = None @staticmethod def from_json(json: dict): @@ -226,7 +251,8 @@ def from_json(json: dict): get_or(json, "current_map", str), get_or(json, "player_count", int), get_or(json, "max_players", int), - mod_objs + mod_objs, + MapRotation.from_json(json["map_rotation"]) if "map_rotation" in json else None, ) diff --git a/src/test/server_browser_backend/routes/test_api_v1.py b/src/test/server_browser_backend/routes/test_api_v1.py index c90fcef..b266de9 100644 --- a/src/test/server_browser_backend/routes/test_api_v1.py +++ b/src/test/server_browser_backend/routes/test_api_v1.py @@ -154,6 +154,105 @@ def test_delete(client: FlaskClient): assert response_json["status"] == "deleted" assert not shared.server_list.exists(id) +test_map_rotation = { + "maps": ["Map 1", "Map 2", "Map 3"], + "index": 0 +} + +test_server_with_rotation_json = { + "name": "Test Server", + "description": "Test Description", + "ports": {"game": 7777, "ping": 7778, "a2s": 27015}, + "player_count": 0, + "max_players": 64, + "current_map": "Map 1", + "password_protected": False, + "mods": [], + "map_rotation": test_map_rotation +} + +def test_register_with_map_rotation(client: FlaskClient): + prepare_test_state() + + response = client.post("/api/v1/servers", json=test_server_with_rotation_json) + assert response.status_code == 201 + + response_json = response.get_json() + assert "server" in response_json + assert "map_rotation" in response_json["server"] + assert response_json["server"]["map_rotation"]["maps"] == test_map_rotation["maps"] + assert response_json["server"]["map_rotation"]["index"] == test_map_rotation["index"] + +def test_update_map_rotation(client: FlaskClient): + prepare_test_state() + + # Register + reg_response = client.post("/api/v1/servers", json=test_server_with_rotation_json) + reg_json = reg_response.get_json() + server_id = reg_json["server"]["unique_id"] + key = reg_json["key"] + + # Update map rotation + new_rotation = { + "maps": ["New Map 1", "New Map 2"], + "index": 1 + } + + update_json = { + "current_map": "New Map 2", + "player_count": 10, + "max_players": 64, + "map_rotation": new_rotation + } + + update_response = client.put( + f"/api/v1/servers/{server_id}", + headers={shared.KEY_HEADER: key}, + json=update_json + ) + + assert update_response.status_code == 200 + update_response_json = update_response.get_json() + assert update_response_json["server"]["map_rotation"]["maps"] == new_rotation["maps"] + assert update_response_json["server"]["map_rotation"]["index"] == new_rotation["index"] + + # Verify in server list + list_response = client.get("/api/v1/servers") + assert list_response.status_code == 200 + list_json = list_response.get_json() + + server_in_list = next(s for s in list_json["servers"] if s["unique_id"] == server_id) + assert server_in_list["map_rotation"]["maps"] == new_rotation["maps"] + assert server_in_list["map_rotation"]["index"] == new_rotation["index"] + +def test_update_keep_map_rotation(client: FlaskClient): + prepare_test_state() + + # Register + reg_response = client.post("/api/v1/servers", json=test_server_with_rotation_json) + reg_json = reg_response.get_json() + server_id = reg_json["server"]["unique_id"] + key = reg_json["key"] + + # Update without map rotation + update_json = { + "current_map": "Map 1", + "player_count": 5, + "max_players": 64 + } + + update_response = client.put( + f"/api/v1/servers/{server_id}", + headers={shared.KEY_HEADER: key}, + json=update_json + ) + + assert update_response.status_code == 200 + update_response_json = update_response.get_json() + # Should still have the original map rotation + assert update_response_json["server"]["map_rotation"]["maps"] == test_map_rotation["maps"] + assert update_response_json["server"]["map_rotation"]["index"] == test_map_rotation["index"] + def test_delete_nonexistant_server(client: FlaskClient): prepare_test_state()