From 97e7a791171e7f3d0d7a882f3fcee96e398994df Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 25 Aug 2026 13:45:36 +0800 Subject: [PATCH] fix: report empty projects as not locked GET /projects/{id}/locked returned True for a project with no drawings or nodes: both loops ran zero times and the fallback return won. Locking and unlocking such a project always succeeded while the state stayed "locked", so it could never be unlocked. Report a project with nothing to lock as not locked, and re-check the state after unlock in the route tests. --- gns3server/controller/project.py | 4 +++ tests/api/routes/controller/test_projects.py | 26 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index d6563ead0..01506a734 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -2141,6 +2141,10 @@ class Project: Check if all items in a project are locked and not """ + if not self._drawings and not self._nodes: + # a project without drawings or nodes has nothing to lock and would + # otherwise always report as locked, even after unlocking it + return False for drawing in self._drawings.values(): if not drawing.locked: return False diff --git a/tests/api/routes/controller/test_projects.py b/tests/api/routes/controller/test_projects.py index aa7ae8d36..c5d32922a 100644 --- a/tests/api/routes/controller/test_projects.py +++ b/tests/api/routes/controller/test_projects.py @@ -613,3 +613,29 @@ class TestControllerProjectRoutes: assert drawing.locked is False for node in project.nodes.values(): assert node.locked is False + + response = await client.get(app.url_path_for("locked_project", project_id=project.id)) + assert response.status_code == status.HTTP_200_OK + assert response.json() is False + + async def test_lock_unlock_empty_project(self, app: FastAPI, client: AsyncClient, project: Project) -> None: + + # a project without drawings or nodes has nothing to lock and must + # never report as locked, otherwise it could not be unlocked + response = await client.get(app.url_path_for("locked_project", project_id=project.id)) + assert response.status_code == status.HTTP_200_OK + assert response.json() is False + + response = await client.post(app.url_path_for("lock_project", project_id=project.id)) + assert response.status_code == status.HTTP_204_NO_CONTENT + + response = await client.get(app.url_path_for("locked_project", project_id=project.id)) + assert response.status_code == status.HTTP_200_OK + assert response.json() is False + + response = await client.post(app.url_path_for("unlock_project", project_id=project.id)) + assert response.status_code == status.HTTP_204_NO_CONTENT + + response = await client.get(app.url_path_for("locked_project", project_id=project.id)) + assert response.status_code == status.HTTP_200_OK + assert response.json() is False