@@ -1046,27 +1046,278 @@ void search_applications_should_validate_metadataValue_requires_metadataName() {
10461046 }
10471047
10481048 @ Test
1049- void search_applications_should_accept_all_null_parameters () throws IOException {
1050- // Arrange - mock applications cache
1049+ void search_applications_should_return_all_when_no_filters () throws IOException {
1050+ // Arrange
1051+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app1 = mock ();
1052+ when (app1 .getName ()).thenReturn ("App1" );
1053+ when (app1 .getStatus ()).thenReturn ("ACTIVE" );
1054+ when (app1 .getAppId ()).thenReturn ("app-1" );
1055+ when (app1 .getLastSeen ()).thenReturn (1000L );
1056+ when (app1 .getLanguage ()).thenReturn ("Java" );
1057+ when (app1 .getTags ()).thenReturn (List .of ());
1058+ when (app1 .getTechs ()).thenReturn (List .of ());
1059+ when (app1 .getMetadataEntities ()).thenReturn (List .of ());
1060+
1061+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app2 = mock ();
1062+ when (app2 .getName ()).thenReturn ("App2" );
1063+ when (app2 .getStatus ()).thenReturn ("ACTIVE" );
1064+ when (app2 .getAppId ()).thenReturn ("app-2" );
1065+ when (app2 .getLastSeen ()).thenReturn (2000L );
1066+ when (app2 .getLanguage ()).thenReturn ("Python" );
1067+ when (app2 .getTags ()).thenReturn (List .of ());
1068+ when (app2 .getTechs ()).thenReturn (List .of ());
1069+ when (app2 .getMetadataEntities ()).thenReturn (List .of ());
1070+
1071+ mockedSDKHelper
1072+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1073+ .thenReturn (List .of (app1 , app2 ));
1074+
1075+ // Act - no filters provided
1076+ var result = assessService .search_applications (null , null , null , null );
1077+
1078+ // Assert - returns all applications
1079+ assertThat (result ).hasSize (2 );
1080+ assertThat (result .get (0 ).name ()).isEqualTo ("App1" );
1081+ assertThat (result .get (1 ).name ()).isEqualTo ("App2" );
1082+ }
1083+
1084+ @ Test
1085+ void search_applications_should_filter_by_name_partial_case_insensitive () throws IOException {
1086+ // Arrange
1087+ // app1 matches name filter, so ALL fields needed for result object
1088+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app1 = mock ();
1089+ when (app1 .getName ()).thenReturn ("MyProductionApp" );
1090+ when (app1 .getStatus ()).thenReturn ("ACTIVE" );
1091+ when (app1 .getAppId ()).thenReturn ("app-1" );
1092+ when (app1 .getLastSeen ()).thenReturn (1000L );
1093+ when (app1 .getLanguage ()).thenReturn ("Java" );
1094+ when (app1 .getTags ()).thenReturn (List .of ());
1095+ when (app1 .getTechs ()).thenReturn (List .of ());
1096+ when (app1 .getMetadataEntities ()).thenReturn (List .of ());
1097+
1098+ // app2 doesn't match name filter, only getName() is checked
1099+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app2 = mock ();
1100+ when (app2 .getName ()).thenReturn ("TestingApp" );
1101+
1102+ mockedSDKHelper
1103+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1104+ .thenReturn (List .of (app1 , app2 ));
1105+
1106+ // Act - search with lowercase "prod" should match "MyProductionApp"
1107+ var result = assessService .search_applications ("prod" , null , null , null );
1108+
1109+ // Assert - partial, case-insensitive match
1110+ assertThat (result ).hasSize (1 );
1111+ assertThat (result .get (0 ).name ()).isEqualTo ("MyProductionApp" );
1112+ }
1113+
1114+ @ Test
1115+ void search_applications_should_filter_by_name_no_matches () throws IOException {
1116+ // Arrange
10511117 com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app = mock ();
1052- when (app .getName ()).thenReturn ("TestApp" );
1053- when (app .getStatus ()).thenReturn ("ACTIVE" );
1054- when (app .getAppId ()).thenReturn ("test-123" );
1055- when (app .getLastSeen ()).thenReturn (1000L );
1056- when (app .getLanguage ()).thenReturn ("Java" );
1057- when (app .getTags ()).thenReturn (List .of ());
1058- when (app .getTechs ()).thenReturn (List .of ());
1059- when (app .getMetadataEntities ()).thenReturn (List .of ());
1118+ when (app .getName ()).thenReturn ("MyApp" );
10601119
10611120 mockedSDKHelper
10621121 .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
10631122 .thenReturn (List .of (app ));
10641123
1065- // Act - call with all null parameters
1066- var result = assessService .search_applications (null , null , null , null );
1124+ // Act - search for non-existent name
1125+ var result = assessService .search_applications ("nonexistent" , null , null , null );
1126+
1127+ // Assert - empty result
1128+ assertThat (result ).isEmpty ();
1129+ }
1130+
1131+ @ Test
1132+ void search_applications_should_filter_by_tag_exact_case_sensitive () throws IOException {
1133+ // Arrange
1134+ // app1 matches tag filter, so ALL fields needed for result object
1135+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app1 = mock ();
1136+ when (app1 .getName ()).thenReturn ("App1" );
1137+ when (app1 .getStatus ()).thenReturn ("ACTIVE" );
1138+ when (app1 .getAppId ()).thenReturn ("app-1" );
1139+ when (app1 .getLastSeen ()).thenReturn (1000L );
1140+ when (app1 .getLanguage ()).thenReturn ("Java" );
1141+ when (app1 .getTags ()).thenReturn (List .of ("Production" ));
1142+ when (app1 .getTechs ()).thenReturn (List .of ());
1143+ when (app1 .getMetadataEntities ()).thenReturn (List .of ());
1144+
1145+ // app2 doesn't match tag filter (case-sensitive), only getTags() is checked
1146+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app2 = mock ();
1147+ when (app2 .getTags ()).thenReturn (List .of ("production" )); // lowercase
1148+
1149+ mockedSDKHelper
1150+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1151+ .thenReturn (List .of (app1 , app2 ));
1152+
1153+ // Act - search with "Production" (capital P)
1154+ var result = assessService .search_applications (null , "Production" , null , null );
1155+
1156+ // Assert - only exact case match (case-sensitive!)
1157+ assertThat (result ).hasSize (1 );
1158+ assertThat (result .get (0 ).name ()).isEqualTo ("App1" );
1159+ }
1160+
1161+ @ Test
1162+ void search_applications_should_filter_by_metadata_name_and_value () throws IOException {
1163+ // Arrange
1164+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Metadata metadata1 = mock ();
1165+ when (metadata1 .getName ()).thenReturn ("Environment" );
1166+ when (metadata1 .getValue ()).thenReturn ("Production" );
1167+
1168+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Metadata metadata2 = mock ();
1169+ when (metadata2 .getName ()).thenReturn ("Environment" );
1170+ when (metadata2 .getValue ()).thenReturn ("Development" );
1171+
1172+ // app1 matches metadata filter, so ALL fields needed for result object
1173+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app1 = mock ();
1174+ when (app1 .getName ()).thenReturn ("ProdApp" );
1175+ when (app1 .getStatus ()).thenReturn ("ACTIVE" );
1176+ when (app1 .getAppId ()).thenReturn ("app-1" );
1177+ when (app1 .getLastSeen ()).thenReturn (1000L );
1178+ when (app1 .getLanguage ()).thenReturn ("Java" );
1179+ when (app1 .getTags ()).thenReturn (List .of ());
1180+ when (app1 .getTechs ()).thenReturn (List .of ());
1181+ when (app1 .getMetadataEntities ()).thenReturn (List .of (metadata1 ));
1182+
1183+ // app2 doesn't match metadata filter, only getMetadataEntities() is checked
1184+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app2 = mock ();
1185+ when (app2 .getMetadataEntities ()).thenReturn (List .of (metadata2 ));
1186+
1187+ mockedSDKHelper
1188+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1189+ .thenReturn (List .of (app1 , app2 ));
1190+
1191+ // Act - search with "environment" and "PRODUCTION" (case-insensitive)
1192+ var result = assessService .search_applications (null , null , "environment" , "PRODUCTION" );
1193+
1194+ // Assert - case-insensitive match for both name and value
1195+ assertThat (result ).hasSize (1 );
1196+ assertThat (result .get (0 ).name ()).isEqualTo ("ProdApp" );
1197+ }
1198+
1199+ @ Test
1200+ void search_applications_should_filter_by_metadata_name_only () throws IOException {
1201+ // Arrange
1202+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Metadata metadata1 = mock ();
1203+ when (metadata1 .getName ()).thenReturn ("Team" );
1204+ when (metadata1 .getValue ()).thenReturn ("Backend" );
1205+
1206+ // metadata2 doesn't match name "team", so getValue() never called
1207+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Metadata metadata2 = mock ();
1208+ when (metadata2 .getName ()).thenReturn ("Owner" );
1209+
1210+ // app1 matches metadata name filter, so ALL fields needed for result object
1211+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app1 = mock ();
1212+ when (app1 .getName ()).thenReturn ("App1" );
1213+ when (app1 .getStatus ()).thenReturn ("ACTIVE" );
1214+ when (app1 .getAppId ()).thenReturn ("app-1" );
1215+ when (app1 .getLastSeen ()).thenReturn (1000L );
1216+ when (app1 .getLanguage ()).thenReturn ("Java" );
1217+ when (app1 .getTags ()).thenReturn (List .of ());
1218+ when (app1 .getTechs ()).thenReturn (List .of ());
1219+ when (app1 .getMetadataEntities ()).thenReturn (List .of (metadata1 ));
1220+
1221+ // app2 doesn't match metadata name filter, only getMetadataEntities() is checked
1222+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app2 = mock ();
1223+ when (app2 .getMetadataEntities ()).thenReturn (List .of (metadata2 ));
1224+
1225+ mockedSDKHelper
1226+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1227+ .thenReturn (List .of (app1 , app2 ));
1228+
1229+ // Act - search by metadata name only (any value)
1230+ var result = assessService .search_applications (null , null , "team" , null );
1231+
1232+ // Assert - matches any app with "team" metadata (case-insensitive)
1233+ assertThat (result ).hasSize (1 );
1234+ assertThat (result .get (0 ).name ()).isEqualTo ("App1" );
1235+ }
1236+
1237+ @ Test
1238+ void search_applications_should_combine_multiple_filters_with_and_logic () throws IOException {
1239+ // Arrange
1240+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Metadata metadata = mock ();
1241+ when (metadata .getName ()).thenReturn ("Environment" );
1242+ when (metadata .getValue ()).thenReturn ("Production" );
1243+
1244+ // app1 matches all filters, so ALL fields needed for result object
1245+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app1 = mock ();
1246+ when (app1 .getName ()).thenReturn ("ProdApp1" );
1247+ when (app1 .getStatus ()).thenReturn ("ACTIVE" );
1248+ when (app1 .getAppId ()).thenReturn ("app-1" );
1249+ when (app1 .getLastSeen ()).thenReturn (1000L );
1250+ when (app1 .getLanguage ()).thenReturn ("Java" );
1251+ when (app1 .getTags ()).thenReturn (List .of ("Production" ));
1252+ when (app1 .getTechs ()).thenReturn (List .of ());
1253+ when (app1 .getMetadataEntities ()).thenReturn (List .of (metadata ));
1254+
1255+ // app2 passes name but fails tag check, only getName() and getTags() checked
1256+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app2 = mock ();
1257+ when (app2 .getName ()).thenReturn ("ProdApp2" );
1258+ when (app2 .getTags ()).thenReturn (List .of ("Development" )); // Wrong tag
1259+
1260+ // app3 fails name check immediately, only getName() checked
1261+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app3 = mock ();
1262+ when (app3 .getName ()).thenReturn ("TestApp" );
1263+
1264+ mockedSDKHelper
1265+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1266+ .thenReturn (List .of (app1 , app2 , app3 ));
1267+
1268+ // Act - combine name, tag, and metadata filters (AND logic)
1269+ var result =
1270+ assessService .search_applications ("prod" , "Production" , "Environment" , "Production" );
1271+
1272+ // Assert - only app1 matches ALL filters
1273+ assertThat (result ).hasSize (1 );
1274+ assertThat (result .get (0 ).name ()).isEqualTo ("ProdApp1" );
1275+ }
10671276
1068- // Assert - should return all applications when no filters specified
1069- assertThat (result ).isNotNull ().hasSize (1 );
1070- assertThat (result .get (0 ).name ()).isEqualTo ("TestApp" );
1277+ @ Test
1278+ void search_applications_should_handle_empty_metadata_list () throws IOException {
1279+ // Arrange - app with empty metadata list
1280+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app = mock ();
1281+ when (app .getMetadataEntities ()).thenReturn (List .of ()); // Empty list - key test scenario
1282+
1283+ mockedSDKHelper
1284+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1285+ .thenReturn (List .of (app ));
1286+
1287+ // Act - search with metadata filter
1288+ var result = assessService .search_applications (null , null , "Environment" , null );
1289+
1290+ // Assert - no match (app has no metadata)
1291+ assertThat (result ).isEmpty ();
1292+ }
1293+
1294+ @ Test
1295+ void search_applications_should_handle_null_metadata_entities () throws IOException {
1296+ // Arrange - app with null metadata entities
1297+ com .contrast .labs .ai .mcp .contrast .sdkextension .data .application .Application app = mock ();
1298+ when (app .getMetadataEntities ()).thenReturn (null ); // Null - key test scenario
1299+
1300+ mockedSDKHelper
1301+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1302+ .thenReturn (List .of (app ));
1303+
1304+ // Act - search with metadata filter
1305+ var result = assessService .search_applications (null , null , "Environment" , null );
1306+
1307+ // Assert - no match and no NPE (defensive coding)
1308+ assertThat (result ).isEmpty ();
1309+ }
1310+
1311+ @ Test
1312+ void search_applications_should_propagate_IOException_from_cache () throws IOException {
1313+ // Arrange
1314+ mockedSDKHelper
1315+ .when (() -> SDKHelper .getApplicationsWithCache (anyString (), any ()))
1316+ .thenThrow (new IOException ("Cache failure" ));
1317+
1318+ // Act & Assert - IOException propagates
1319+ assertThatThrownBy (() -> assessService .search_applications (null , null , null , null ))
1320+ .isInstanceOf (IOException .class )
1321+ .hasMessageContaining ("Failed to search applications" );
10711322 }
10721323}
0 commit comments