Files
computer-playbook/tests/unit/python/plugins/lookup/test_deployment.py
T
kevinveenbirkenbachandClaude Opus 5.5 ca4f53d0e4 fix(mcp): keep a persistent host's providers through a partial deploy
mcp_servers narrowed the MCP providers to application_closure(deployment.whitelist), which on a persistent host drops every provider an earlier run deployed and a partial run does not touch. sys-svc-mcp-reconcile then converges each client present on the host against that narrowed set: on echoserver the --id web-opt-rdr-domains run rdr-3 removed the Prometheus registration from Flowise.

deployment now reports ephemeral (runtime act or github), and the closure narrows only there, where a whitelist is all a run deploys; a persistent host narrows nothing, as a full deploy already did with its empty whitelist, so its clients keep every provider it serves. CI keeps its narrowing unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 09:24:28 +01:00

253 lines
9.3 KiB
Python

import unittest
from unittest.mock import patch
from plugins.lookup import deployment
from plugins.lookup.deployment import LookupModule
class TestDeploymentLookup(unittest.TestCase):
def setUp(self):
deployment._reset_cache_for_tests()
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_returns_dict_with_all_keys(self, mock_list):
mock_list.return_value = ["web-app-friendica", "web-app-baserow"]
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": ["web-app-friendica"],
"group_names": ["web-app-friendica", "web-app-baserow"],
},
)
self.assertEqual(len(result), 1)
self.assertEqual(
set(result[0].keys()),
{
"whitelist",
"running",
"groups",
"inventory",
"deployed",
"runtime",
"ephemeral",
"all",
},
)
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_running_falls_back_to_groups_when_whitelist_empty(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": [],
"group_names": ["a", "b"],
},
)
self.assertEqual(result[0]["running"], ["a", "b"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_running_uses_whitelist_when_non_empty(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": ["a"],
"group_names": ["a", "b"],
},
)
self.assertEqual(result[0]["running"], ["a"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_whitelist_reflects_raw_input(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": ["web-app-foo"],
"group_names": ["web-app-bar"],
},
)
self.assertEqual(result[0]["whitelist"], ["web-app-foo"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_groups_reflects_raw_input(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": [],
"group_names": ["a", "b", "c"],
},
)
self.assertEqual(result[0]["groups"], ["a", "b", "c"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_all_reflects_invokable_apps(self, mock_list):
mock_list.return_value = ["x", "y", "z"]
result = LookupModule().run([], variables={"group_names": []})
self.assertEqual(result[0]["all"], ["x", "y", "z"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_missing_vars_treated_as_empty(self, mock_list):
mock_list.return_value = ["a"]
result = LookupModule().run([], variables={})
self.assertEqual(result[0]["whitelist"], [])
self.assertEqual(result[0]["groups"], [])
self.assertEqual(result[0]["running"], [])
self.assertEqual(result[0]["all"], ["a"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_inventory_is_play_wide_not_host_scoped(self, mock_list):
mock_list.return_value = []
worker = {
"group_names": ["web-app-mattermost"],
"groups": {
"svc-ai-litellm": ["mgr-01"],
"svc-ai-ollama": ["mgr-01"],
"web-app-mattermost": ["mgr-01", "wrk-01"],
},
}
result = LookupModule().run([], variables=worker)
self.assertEqual(result[0]["groups"], ["web-app-mattermost"])
self.assertEqual(
result[0]["inventory"],
["svc-ai-litellm", "svc-ai-ollama", "web-app-mattermost"],
)
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_inventory_skips_groups_without_hosts(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"group_names": [],
"groups": {"has-hosts": ["h1"], "empty": [], "also-empty": None},
},
)
self.assertEqual(result[0]["inventory"], ["has-hosts"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_inventory_drops_the_implicit_groups(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"group_names": ["web-app-mattermost"],
"groups": {
"all": ["mgr-01", "wrk-01"],
"ungrouped": ["mgr-01"],
"web-app-mattermost": ["mgr-01", "wrk-01"],
},
},
)
self.assertEqual(result[0]["inventory"], ["web-app-mattermost"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_inventory_empty_without_the_magic_var(self, mock_list):
mock_list.return_value = []
result = LookupModule().run([], variables={"group_names": ["a"]})
self.assertEqual(result[0]["inventory"], [])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_cache_key_separates_hosts_with_equal_group_names(self, mock_list):
mock_list.return_value = []
first = LookupModule().run(
[],
variables={"group_names": ["a"], "groups": {"a": ["h1"]}},
)
second = LookupModule().run(
[],
variables={"group_names": ["a"], "groups": {"a": ["h1"], "b": ["h2"]}},
)
self.assertEqual(first[0]["inventory"], ["a"])
self.assertEqual(second[0]["inventory"], ["a", "b"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_caches_same_input(self, mock_list):
mock_list.return_value = ["a"]
LookupModule().run(
[], variables={"APPLICATIONS_WHITELIST": [], "group_names": ["a"]}
)
LookupModule().run(
[], variables={"APPLICATIONS_WHITELIST": [], "group_names": ["a"]}
)
self.assertEqual(mock_list.call_count, 1)
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_whitelist_csv_string_tokenized(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": "web-app-foo,web-app-bar",
"group_names": [],
},
)
self.assertEqual(result[0]["whitelist"], ["web-app-foo", "web-app-bar"])
self.assertEqual(result[0]["running"], ["web-app-foo", "web-app-bar"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_groups_csv_string_tokenized(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": [],
"group_names": "a,b,c",
},
)
self.assertEqual(result[0]["groups"], ["a", "b", "c"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_whitelist_csv_string_whitespace_trimmed(self, mock_list):
mock_list.return_value = []
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": " app1 , app2 ,, ",
"group_names": [],
},
)
self.assertEqual(result[0]["whitelist"], ["app1", "app2"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_deployed_uses_running_in_ephemeral_runtime(self, mock_list):
mock_list.return_value = []
for runtime in ("act", "github"):
deployment._reset_cache_for_tests()
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": ["only-this"],
"group_names": ["only-this", "and-this", "and-that"],
"RUNTIME": runtime,
},
)
self.assertEqual(result[0]["deployed"], ["only-this"])
self.assertEqual(result[0]["runtime"], runtime)
self.assertTrue(result[0]["ephemeral"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_deployed_uses_groups_in_persistent_runtime(self, mock_list):
mock_list.return_value = []
for runtime in ("host", "dev", ""):
deployment._reset_cache_for_tests()
result = LookupModule().run(
[],
variables={
"APPLICATIONS_WHITELIST": ["partial"],
"group_names": ["partial", "still-running", "also-running"],
"RUNTIME": runtime,
},
)
self.assertEqual(
result[0]["deployed"],
["partial", "still-running", "also-running"],
)
self.assertFalse(result[0]["ephemeral"])
if __name__ == "__main__":
unittest.main()