@@ -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
10531301class TestServerMain :
10541302 @patch ("oracle.oci_jms_mcp_server.server.mcp.run" )
0 commit comments