From f43a717b20629073557988dd606d353411fdca5d Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sat, 15 Aug 2026 23:32:24 +0800 Subject: [PATCH] copilot: sync CONSOLE_TYPES with the server ConsoleType enum The vendored gns3fy copy keeps its type lists as literals (the module is shared with the standalone MCP service and cannot import server enums), and CONSOLE_TYPES had drifted: 'ssh' and 'docker_exec' were missing while both are valid server-side. Impact: the copilot topology reader validates the whole node list in one pydantic pass, so a single vendor NOS node (console_type 'docker_exec') made it drop the entire project and return zero devices to every copilot device tool. Add the missing values plus drift tests asserting the vendored lists cover the server enums (skipped when ai-features extras are absent). --- .../gns3_copilot/gns3_client/custom_gns3fy.py | 5 ++ tests/agent/test_custom_gns3fy.py | 71 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tests/agent/test_custom_gns3fy.py diff --git a/gns3server/agent/gns3_copilot/gns3_client/custom_gns3fy.py b/gns3server/agent/gns3_copilot/gns3_client/custom_gns3fy.py index edb9612da..582e7100b 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/custom_gns3fy.py +++ b/gns3server/agent/gns3_copilot/gns3_client/custom_gns3fy.py @@ -93,14 +93,19 @@ NODE_TYPES = [ "qemu", ] +# Keep in sync with gns3server.schemas.common.ConsoleType. The values are +# duplicated as literals because this module is shared with the standalone +# MCP service and cannot import the enum. "null" is gns3fy legacy. CONSOLE_TYPES = [ "vnc", "telnet", + "ssh", "http", "https", "spice", "spice+agent", "none", + "docker_exec", "null", ] diff --git a/tests/agent/test_custom_gns3fy.py b/tests/agent/test_custom_gns3fy.py new file mode 100644 index 000000000..a07266432 --- /dev/null +++ b/tests/agent/test_custom_gns3fy.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# Copyright (C) 2026 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +The vendored gns3fy copy keeps its node/console type lists as literals +(it is shared with the standalone MCP service and cannot import server +enums). These tests fail when the server enums grow a value the vendored +lists have not picked up — exactly what happened with "docker_exec": one +vendor node failing validation made the copilot's topology reader drop +the whole project. +""" + +import pytest + + +def test_console_types_cover_server_enum(): + """ + Every server ConsoleType value must be accepted by the vendored Node model. + """ + pytest.importorskip("jwt", reason="ai-features extras not installed") + from gns3server.agent.gns3_copilot.gns3_client.custom_gns3fy import CONSOLE_TYPES + from gns3server.schemas.common import ConsoleType + + missing = {e.value for e in ConsoleType} - set(CONSOLE_TYPES) + assert not missing, f"CONSOLE_TYPES drifted from ConsoleType, missing: {missing}" + + +def test_node_types_cover_server_enum(): + """ + Every server NodeType value must be accepted by the vendored Node model. + """ + pytest.importorskip("jwt", reason="ai-features extras not installed") + from gns3server.agent.gns3_copilot.gns3_client.custom_gns3fy import NODE_TYPES + from gns3server.schemas.controller.nodes import NodeType + + missing = {e.value for e in NodeType} - set(NODE_TYPES) + assert not missing, f"NODE_TYPES drifted from NodeType, missing: {missing}" + + +def test_node_accepts_docker_exec_console(): + """ + Vendor NOS nodes use console_type "docker_exec"; the topology reader + validates the whole node list in one pass, so rejecting it poisoned + every copilot device tool for the project. + """ + pytest.importorskip("jwt", reason="ai-features extras not installed") + from gns3server.agent.gns3_copilot.gns3_client.custom_gns3fy import Node + + node = Node( + name="R1", + project_id="5f517ce3-1bc6-4245-b866-1a2fbd0ee5a7", + node_id="0d15c2e6-8f83-4b79-8875-9dbc3e5f2f1e", + node_type="docker", + console_type="docker_exec", + status="started", + ) + assert node.console_type == "docker_exec"