Files
nodered-mcp/tests/conftest.py
Joe Carter 764d123fdb Initial commit: Node-RED MCP server (Python/FastMCP)
22-tool MCP server for Node-RED flow management with Pydantic models,
incremental flow patching, layout linting, and flexible auth (token,
basic, OAuth2). 137 tests, full ruff compliance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:19:33 +00:00

252 lines
7.0 KiB
Python

"""Shared test fixtures for nodered_mcp tests."""
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from nodered_mcp.client import NodeRedClient
# ── Sample API data fixtures ──────────────────────────────────────
@pytest.fixture
def sample_flows_data() -> list[dict[str, Any]]:
"""Raw Node-RED flows API response (heterogeneous array of tabs, nodes, subflows)."""
return [
# Two tabs
{
"id": "tab1",
"type": "tab",
"label": "Main Flow",
"disabled": False,
"info": "Primary workspace",
},
{
"id": "tab2",
"type": "tab",
"label": "Test Flow",
"disabled": True,
"info": "",
},
# Several nodes of different types
{
"id": "inject1",
"type": "inject",
"z": "tab1",
"name": "Test Inject",
"topic": "",
"payload": "test",
"payloadType": "str",
"repeat": "",
"crontab": "",
"once": False,
"onceDelay": 0.1,
"x": 100,
"y": 100,
"wires": [["function1"]],
},
{
"id": "function1",
"type": "function",
"z": "tab1",
"name": "Process Data",
"func": "return msg;",
"outputs": 1,
"noerr": 0,
"x": 300,
"y": 100,
"wires": [["debug1"]],
},
{
"id": "debug1",
"type": "debug",
"z": "tab1",
"name": "Output",
"active": True,
"tosidebar": True,
"console": False,
"tostatus": False,
"complete": "payload",
"x": 500,
"y": 100,
"wires": [],
},
{
"id": "inject2",
"type": "inject",
"z": "tab2",
"name": "Another Inject",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": False,
"x": 100,
"y": 200,
"wires": [[]],
},
# One subflow
{
"id": "subflow1",
"type": "subflow",
"name": "Test Subflow",
"info": "",
"in": [{"x": 50, "y": 50, "wires": [{"id": "sf-node1"}]}],
"out": [{"x": 250, "y": 50, "wires": [{"id": "sf-node1", "port": 0}]}],
},
]
@pytest.fixture
def sample_flow_data() -> dict[str, Any]:
"""Single flow API response (GET /flow/:id)."""
return {
"id": "tab1",
"label": "Main Flow",
"nodes": [
{
"id": "inject1",
"type": "inject",
"name": "Test",
"x": 100,
"y": 100,
"wires": [["debug1"]],
},
{
"id": "debug1",
"type": "debug",
"name": "Output",
"x": 300,
"y": 100,
"wires": [],
},
],
"configs": [],
"subflows": [],
}
@pytest.fixture
def sample_node_module_data() -> dict[str, Any]:
"""NodeModule API response (GET /nodes/:module)."""
return {
"name": "node-red-contrib-test",
"version": "1.0.0",
"nodes": [
{
"id": "node-red-contrib-test/test-node",
"name": "test-node",
"types": ["test-input", "test-output"],
"enabled": True,
"module": "node-red-contrib-test",
"version": "1.0.0",
}
],
}
@pytest.fixture
def sample_node_set_data() -> dict[str, Any]:
"""NodeSet API response."""
return {
"id": "node-red/inject",
"name": "inject",
"types": ["inject"],
"enabled": True,
"module": "node-red",
"version": "3.1.0",
}
@pytest.fixture
def sample_settings_data() -> dict[str, Any]:
"""Settings API response (GET /settings)."""
return {
"httpNodeRoot": "/api",
"version": "3.1.0",
"user": {
"username": "admin",
"permissions": "*",
},
"flowFilePretty": True,
"editorTheme": {
"page": {"title": "Node-RED"},
},
}
@pytest.fixture
def sample_diagnostics_data() -> dict[str, Any]:
"""Diagnostics API response (GET /diagnostics)."""
return {
"report": {
"versions": {
"node-red": "3.1.0",
"nodejs": "18.17.0",
"npm": "9.6.7",
},
"platform": {
"os": "darwin",
"arch": "arm64",
"cpus": 8,
"mem": 17179869184,
},
"modules": {
"node-red-contrib-test": "1.0.0",
},
},
}
@pytest.fixture
def sample_flows_state_data() -> dict[str, Any]:
"""Flow state API response (GET /flows/state)."""
return {"state": "start"}
# ── Mock client fixture ───────────────────────────────────────────
@pytest.fixture
def mock_client(
sample_flows_data: list[dict[str, Any]],
sample_flow_data: dict[str, Any],
sample_node_module_data: dict[str, Any],
sample_node_set_data: dict[str, Any],
sample_settings_data: dict[str, Any],
sample_diagnostics_data: dict[str, Any],
sample_flows_state_data: dict[str, Any],
) -> NodeRedClient:
"""Mock NodeRedClient with all methods stubbed."""
client = AsyncMock(spec=NodeRedClient)
# Flow endpoints
client.get_flows.return_value = sample_flows_data
client.get_flow.return_value = sample_flow_data
client.update_flows.return_value = None
client.update_flow.return_value = None
client.create_flow.return_value = {"id": "new-flow-id"}
client.delete_flow.return_value = None
client.get_flows_state.return_value = sample_flows_state_data
client.set_flows_state.return_value = sample_flows_state_data
# Node endpoints
client.get_nodes.return_value = [sample_node_set_data]
client.get_node_info.return_value = sample_node_module_data
client.toggle_node_module.return_value = sample_node_module_data
client.inject.return_value = None
# Settings endpoints
client.get_settings.return_value = sample_settings_data
client.get_diagnostics.return_value = sample_diagnostics_data
with (
patch("nodered_mcp.tools.flows.get_client", return_value=client),
patch("nodered_mcp.tools.nodes.get_client", return_value=client),
patch("nodered_mcp.tools.settings.get_client", return_value=client),
patch("nodered_mcp.tools.layout.get_client", return_value=client),
):
yield client