Skip to content

Commit ed0b9e9

Browse files
test(jms): close branch-coverage gaps to clear 90% gate
Coverage with --cov-branch (the flag CI uses) was 87.51% before these additions. New tests target the previously-unexercised branches: - map_* converters: parametrized None-input guards for all 14 mappers - _oci_to_dict: cover the SDK-fallback exception path and the __dict__/no-__dict__ branches - util.get_jms_environment: blank value and unparseable input paths - Every remaining tool surfaces oci.exceptions.ServiceError instead of swallowing it (9 tools, parametrized) - summarize_fleet_health: UNKNOWN status when text fragments are empty, and recommended-checks markers for inventory/auth keywords - java_runtime_compliance: short-circuit when version is None, UPGRADE_REQUIRED bucket aggregation, and release-cache hit path - list_fleets: blank sort_by normalization and limit-truncation inside the item loop Result: 87 passed, total coverage 97.44% with --cov-branch.
1 parent 3b05f72 commit ed0b9e9

2 files changed

Lines changed: 313 additions & 0 deletions

File tree

src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
"""
66

77
from datetime import datetime, UTC
8+
from unittest.mock import patch
89

910
import oci
11+
import pytest
1012

13+
from oracle.oci_jms_mcp_server import models, util
1114
from oracle.oci_jms_mcp_server.models import (
15+
map_custom_log,
1216
map_fleet_diagnosis,
1317
map_fleet_error,
18+
map_fleet_error_detail,
1419
map_jms_notice,
1520
map_fleet,
1621
map_fleet_advanced_feature_configuration,
@@ -199,3 +204,63 @@ def test_map_jms_notice():
199204
assert result.key == 1001
200205
assert result.summary == "Planned maintenance"
201206
assert result.time_released == now
207+
208+
209+
@pytest.mark.parametrize(
210+
"mapper",
211+
[
212+
map_custom_log,
213+
map_fleet_summary,
214+
map_fleet,
215+
map_jms_plugin_summary,
216+
map_jms_plugin,
217+
map_installation_site_summary,
218+
map_fleet_agent_configuration,
219+
map_fleet_advanced_feature_configuration,
220+
map_managed_instance_usage,
221+
map_resource_inventory,
222+
map_fleet_diagnosis,
223+
map_fleet_error_detail,
224+
map_fleet_error,
225+
map_jms_notice,
226+
],
227+
)
228+
def test_mapper_returns_none_on_none_input(mapper):
229+
# Every map_* converter short-circuits on a None SDK object so callers
230+
# don't have to guard their pagination loops.
231+
assert mapper(None) is None
232+
233+
234+
def test_oci_to_dict_handles_none_and_plain_dict():
235+
assert models._oci_to_dict(None) is None
236+
assert models._oci_to_dict({"a": 1}) == {"a": 1}
237+
238+
239+
def test_oci_to_dict_falls_back_to_object_dict():
240+
# When oci.util.to_dict can't handle the object, _oci_to_dict falls back
241+
# to the public attributes of __dict__ (skipping anything underscore-prefixed).
242+
class Bag:
243+
def __init__(self):
244+
self.public = "value"
245+
self._private = "hidden"
246+
247+
with patch("oci.util.to_dict", side_effect=TypeError("unsupported")):
248+
result = models._oci_to_dict(Bag())
249+
250+
assert result == {"public": "value"}
251+
252+
253+
def test_oci_to_dict_returns_none_for_bare_object_when_sdk_helper_fails():
254+
# An object without __dict__ (e.g. a bare object()) returns None when
255+
# both the OCI helper and the dict fallback are unusable.
256+
with patch("oci.util.to_dict", side_effect=TypeError("unsupported")):
257+
assert models._oci_to_dict(object()) is None
258+
259+
260+
def test_get_jms_environment_blank_value_returns_none():
261+
assert util.get_jms_environment(" ") is None
262+
263+
264+
def test_get_jms_environment_rejects_unparseable_input():
265+
with pytest.raises(ValueError):
266+
util.get_jms_environment("???")

src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,254 @@ async def test_java_runtime_compliance_propagates_5xx_release_lookup(self, mock_
10491049
with pytest.raises(fastmcp.exceptions.ToolError):
10501050
await client.call_tool("java_runtime_compliance", {"fleet_id": "fleet1"})
10511051

1052+
@pytest.mark.asyncio
1053+
@pytest.mark.parametrize(
1054+
"tool_name,sdk_method,kwargs",
1055+
[
1056+
("list_jms_plugins", "list_jms_plugins", {}),
1057+
("get_jms_plugin", "get_jms_plugin", {"jms_plugin_id": "plugin1"}),
1058+
("list_installation_sites", "list_installation_sites", {"fleet_id": "fleet1"}),
1059+
("get_fleet_agent_configuration", "get_fleet_agent_configuration", {"fleet_id": "fleet1"}),
1060+
(
1061+
"get_fleet_advanced_feature_configuration",
1062+
"get_fleet_advanced_feature_configuration",
1063+
{"fleet_id": "fleet1"},
1064+
),
1065+
("summarize_resource_inventory", "summarize_resource_inventory", {}),
1066+
("summarize_managed_instance_usage", "summarize_managed_instance_usage", {"fleet_id": "fleet1"}),
1067+
("get_fleet_health_diagnostics", "list_fleet_diagnoses", {"fleet_id": "fleet1"}),
1068+
("list_jms_notices", "list_announcements", {}),
1069+
],
1070+
)
1071+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1072+
async def test_tool_propagates_service_error(
1073+
self, mock_get_client, tool_name, sdk_method, kwargs
1074+
):
1075+
# Every tool should surface OCI ServiceError instead of swallowing it.
1076+
mock_client = MagicMock()
1077+
getattr(mock_client, sdk_method).side_effect = oci.exceptions.ServiceError(
1078+
status=500,
1079+
code="InternalServerError",
1080+
message="boom",
1081+
opc_request_id="req",
1082+
headers={},
1083+
)
1084+
mock_get_client.return_value = mock_client
1085+
1086+
async with Client(mcp) as client:
1087+
with pytest.raises(fastmcp.exceptions.ToolError):
1088+
await client.call_tool(tool_name, kwargs)
1089+
1090+
@pytest.mark.asyncio
1091+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1092+
async def test_summarize_fleet_health_unknown_when_text_fragments_empty(self, mock_get_client):
1093+
# Diagnoses/errors that carry no usable text strings collapse to UNKNOWN.
1094+
mock_client = MagicMock()
1095+
mock_get_client.return_value = mock_client
1096+
1097+
diagnosis_response = create_autospec(oci.response.Response)
1098+
diagnosis_response.data = oci.jms.models.FleetDiagnosisCollection(
1099+
items=[oci.jms.models.FleetDiagnosisSummary(resource_diagnosis="UNKNOWN_ENUM_VALUE")]
1100+
)
1101+
diagnosis_response.has_next_page = False
1102+
diagnosis_response.next_page = None
1103+
mock_client.list_fleet_diagnoses.return_value = diagnosis_response
1104+
1105+
errors_response = create_autospec(oci.response.Response)
1106+
errors_response.data = oci.jms.models.FleetErrorCollection(items=[])
1107+
errors_response.has_next_page = False
1108+
errors_response.next_page = None
1109+
mock_client.list_fleet_errors.return_value = errors_response
1110+
1111+
async with Client(mcp) as client:
1112+
result = (
1113+
await client.call_tool("summarize_fleet_health", {"fleet_id": "fleet1"})
1114+
).structured_content
1115+
1116+
assert result["overall_health_status"] == "UNKNOWN"
1117+
1118+
@pytest.mark.asyncio
1119+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1120+
async def test_summarize_fleet_health_triggers_inventory_and_auth_recommendations(
1121+
self, mock_get_client
1122+
):
1123+
# Diagnosis text containing the documented keyword families should produce
1124+
# the matching recommended next-step entries.
1125+
mock_client = MagicMock()
1126+
mock_get_client.return_value = mock_client
1127+
1128+
diagnosis_response = create_autospec(oci.response.Response)
1129+
diagnosis_response.data = oci.jms.models.FleetDiagnosisCollection(
1130+
items=[
1131+
oci.jms.models.FleetDiagnosisSummary(
1132+
resource_diagnosis="Inventory scan reported unauthorized region access"
1133+
)
1134+
]
1135+
)
1136+
diagnosis_response.has_next_page = False
1137+
diagnosis_response.next_page = None
1138+
mock_client.list_fleet_diagnoses.return_value = diagnosis_response
1139+
1140+
errors_response = create_autospec(oci.response.Response)
1141+
errors_response.data = oci.jms.models.FleetErrorCollection(items=[])
1142+
errors_response.has_next_page = False
1143+
errors_response.next_page = None
1144+
mock_client.list_fleet_errors.return_value = errors_response
1145+
1146+
async with Client(mcp) as client:
1147+
result = (
1148+
await client.call_tool("summarize_fleet_health", {"fleet_id": "fleet1"})
1149+
).structured_content
1150+
1151+
recommendations = result["recommended_next_checks"]
1152+
assert any("inventory collection settings" in entry for entry in recommendations)
1153+
assert any("region or compartment alignment" in entry for entry in recommendations)
1154+
assert any("JMS notices" in entry for entry in recommendations)
1155+
1156+
@pytest.mark.asyncio
1157+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1158+
async def test_java_runtime_compliance_short_circuits_when_version_missing(
1159+
self, mock_get_client
1160+
):
1161+
# _safe_get_java_release must skip the SDK call when version is None.
1162+
mock_client = MagicMock()
1163+
mock_get_client.return_value = mock_client
1164+
1165+
usage_response = create_autospec(oci.response.Response)
1166+
usage_response.data = oci.jms.models.JreUsageCollection(
1167+
items=[
1168+
oci.jms.models.JreUsage(
1169+
id="jre1",
1170+
fleet_id="fleet1",
1171+
version=None,
1172+
vendor="Oracle",
1173+
distribution="JDK",
1174+
security_status="UP_TO_DATE",
1175+
approximate_installation_count=1,
1176+
)
1177+
]
1178+
)
1179+
usage_response.has_next_page = False
1180+
usage_response.next_page = None
1181+
mock_client.summarize_jre_usage.return_value = usage_response
1182+
1183+
async with Client(mcp) as client:
1184+
await client.call_tool("java_runtime_compliance", {"fleet_id": "fleet1"})
1185+
1186+
mock_client.get_java_release.assert_not_called()
1187+
1188+
@pytest.mark.asyncio
1189+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1190+
async def test_list_fleets_ignores_blank_sort_by(self, mock_get_client):
1191+
# A whitespace-only sort_by must be dropped before reaching the SDK.
1192+
mock_client = MagicMock()
1193+
mock_get_client.return_value = mock_client
1194+
1195+
response = create_autospec(oci.response.Response)
1196+
response.data = oci.jms.models.FleetCollection(items=[])
1197+
response.has_next_page = False
1198+
response.next_page = None
1199+
mock_client.list_fleets.return_value = response
1200+
1201+
async with Client(mcp) as client:
1202+
await client.call_tool(
1203+
"list_fleets",
1204+
{"compartment_id": "ocid1.compartment.oc1..test", "sort_by": " "},
1205+
)
1206+
1207+
assert "sort_by" not in mock_client.list_fleets.call_args.kwargs
1208+
1209+
@pytest.mark.asyncio
1210+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1211+
async def test_list_fleets_truncates_first_page_to_limit(self, mock_get_client):
1212+
# Hitting the limit mid-page must stop iteration and not call the SDK again.
1213+
mock_client = MagicMock()
1214+
mock_get_client.return_value = mock_client
1215+
1216+
response = create_autospec(oci.response.Response)
1217+
response.data = oci.jms.models.FleetCollection(
1218+
items=[
1219+
oci.jms.models.FleetSummary(id=f"fleet{i}", display_name=f"Fleet {i}")
1220+
for i in range(5)
1221+
]
1222+
)
1223+
response.has_next_page = True
1224+
response.next_page = "token"
1225+
mock_client.list_fleets.return_value = response
1226+
1227+
async with Client(mcp) as client:
1228+
result = (
1229+
await client.call_tool(
1230+
"list_fleets",
1231+
{"compartment_id": "ocid1.compartment.oc1..test", "limit": 2},
1232+
)
1233+
).structured_content["result"]
1234+
1235+
assert len(result) == 2
1236+
assert mock_client.list_fleets.call_count == 1
1237+
1238+
@pytest.mark.asyncio
1239+
@patch("oracle.oci_jms_mcp_server.server.get_jms_client")
1240+
async def test_java_runtime_compliance_aggregates_upgrade_required_and_caches_release(
1241+
self, mock_get_client
1242+
):
1243+
# Two rows sharing a version should produce exactly one get_java_release call
1244+
# and accumulate runtimes_requiring_upgrade for the UPGRADE_REQUIRED bucket.
1245+
mock_client = MagicMock()
1246+
mock_get_client.return_value = mock_client
1247+
1248+
usage_response = create_autospec(oci.response.Response)
1249+
usage_response.data = oci.jms.models.JreUsageCollection(
1250+
items=[
1251+
oci.jms.models.JreUsage(
1252+
id="jre-upgrade",
1253+
fleet_id="fleet1",
1254+
version="17.0.1",
1255+
vendor="Oracle",
1256+
distribution="JDK",
1257+
security_status="UPGRADE_REQUIRED",
1258+
approximate_installation_count=3,
1259+
),
1260+
oci.jms.models.JreUsage(
1261+
id="jre-update",
1262+
fleet_id="fleet1",
1263+
version="17.0.1",
1264+
vendor="Oracle",
1265+
distribution="JDK",
1266+
security_status="UPDATE_REQUIRED",
1267+
approximate_installation_count=2,
1268+
),
1269+
]
1270+
)
1271+
usage_response.has_next_page = False
1272+
usage_response.next_page = None
1273+
mock_client.summarize_jre_usage.return_value = usage_response
1274+
1275+
release_response = create_autospec(oci.response.Response)
1276+
release_response.data = MagicMock(
1277+
release_date=None,
1278+
days_under_security_baseline=None,
1279+
license_type="ORACLE",
1280+
release_type="LTS",
1281+
release_notes_url=None,
1282+
)
1283+
mock_client.get_java_release.return_value = release_response
1284+
1285+
sites_response = create_autospec(oci.response.Response)
1286+
sites_response.data = oci.jms.models.InstallationSiteCollection(items=[])
1287+
sites_response.has_next_page = False
1288+
sites_response.next_page = None
1289+
mock_client.list_installation_sites.return_value = sites_response
1290+
1291+
async with Client(mcp) as client:
1292+
result = (
1293+
await client.call_tool("java_runtime_compliance", {"fleet_id": "fleet1"})
1294+
).structured_content
1295+
1296+
assert result["runtimes_requiring_upgrade"] == 3
1297+
assert result["runtimes_requiring_update"] == 2
1298+
assert mock_client.get_java_release.call_count == 1
1299+
10521300

10531301
class TestServerMain:
10541302
@patch("oracle.oci_jms_mcp_server.server.mcp.run")

0 commit comments

Comments
 (0)