@@ -1214,6 +1214,268 @@ test.describe('Agent Detail Page', () => {
12141214 ) ;
12151215 } ) ;
12161216
1217+ test ( 'full JSON editor filters $schema and duplicate properties from suggestions' , async ( {
1218+ mockedPage,
1219+ } ) => {
1220+ const compositeControl : Control = {
1221+ id : 84 ,
1222+ name : 'Property Filter Test' ,
1223+ control : {
1224+ description : 'Test property filtering' ,
1225+ enabled : true ,
1226+ execution : 'server' ,
1227+ condition : {
1228+ selector : { path : 'output' } ,
1229+ evaluator : { name : 'regex' , config : { pattern : '.*' } } ,
1230+ } ,
1231+ action : { decision : 'deny' } ,
1232+ tags : [ ] ,
1233+ } ,
1234+ } ;
1235+
1236+ await mockRoutes . agent ( mockedPage , {
1237+ controls : { data : { controls : [ compositeControl ] } } ,
1238+ agent : { data : mockData . agentWithSteps } ,
1239+ } ) ;
1240+ await mockedPage . route ( '**/api/v1/controls/84/data' , async ( route ) => {
1241+ await route . fulfill ( {
1242+ status : 200 ,
1243+ contentType : 'application/json' ,
1244+ body : JSON . stringify ( { data : compositeControl . control } ) ,
1245+ } ) ;
1246+ } ) ;
1247+
1248+ await mockedPage . goto (
1249+ getAgentControlsUrl ( { modal : 'edit' , controlId : 84 } )
1250+ ) ;
1251+
1252+ const modal = mockedPage . getByRole ( 'dialog' , { name : 'Edit Control' } ) ;
1253+ await expect ( modal ) . toBeVisible ( ) ;
1254+ await modal . getByText ( 'Full JSON' , { exact : true } ) . click ( ) ;
1255+
1256+ // Suggestions at root level inside a populated object should NOT include
1257+ // existing properties or $schema
1258+ const suggestions = await getJsonEditorSuggestions (
1259+ mockedPage ,
1260+ 'control-json-textarea' ,
1261+ 2 ,
1262+ 3
1263+ ) ;
1264+ const labels = suggestions . map ( ( item ) => item . label ) ;
1265+
1266+ // $schema should be filtered
1267+ expect ( labels ) . not . toContain ( '$schema' ) ;
1268+ // Already-present properties should be filtered
1269+ expect ( labels ) . not . toContain ( 'description' ) ;
1270+ expect ( labels ) . not . toContain ( 'enabled' ) ;
1271+ expect ( labels ) . not . toContain ( 'condition' ) ;
1272+ expect ( labels ) . not . toContain ( 'action' ) ;
1273+ } ) ;
1274+
1275+ test ( 'full JSON editor suggests evaluator names inside evaluator name field' , async ( {
1276+ mockedPage,
1277+ } ) => {
1278+ const compositeControl : Control = {
1279+ id : 85 ,
1280+ name : 'Evaluator Name Suggestions' ,
1281+ control : {
1282+ description : 'Test evaluator name suggestions' ,
1283+ enabled : true ,
1284+ execution : 'server' ,
1285+ condition : {
1286+ selector : { path : 'output' } ,
1287+ evaluator : { name : 'regex' , config : { pattern : '.*' } } ,
1288+ } ,
1289+ action : { decision : 'deny' } ,
1290+ tags : [ ] ,
1291+ } ,
1292+ } ;
1293+
1294+ await mockRoutes . agent ( mockedPage , {
1295+ controls : { data : { controls : [ compositeControl ] } } ,
1296+ agent : { data : mockData . agentWithSteps } ,
1297+ } ) ;
1298+ await mockedPage . route ( '**/api/v1/controls/85/data' , async ( route ) => {
1299+ await route . fulfill ( {
1300+ status : 200 ,
1301+ contentType : 'application/json' ,
1302+ body : JSON . stringify ( { data : compositeControl . control } ) ,
1303+ } ) ;
1304+ } ) ;
1305+
1306+ await mockedPage . goto (
1307+ getAgentControlsUrl ( { modal : 'edit' , controlId : 85 } )
1308+ ) ;
1309+
1310+ const modal = mockedPage . getByRole ( 'dialog' , { name : 'Edit Control' } ) ;
1311+ await expect ( modal ) . toBeVisible ( ) ;
1312+ await modal . getByText ( 'Full JSON' , { exact : true } ) . click ( ) ;
1313+
1314+ // Find the evaluator name line and get suggestions inside it
1315+ await setJsonEditorValue (
1316+ mockedPage ,
1317+ 'control-json-textarea' ,
1318+ `{
1319+ "condition": {
1320+ "selector": { "path": "output" },
1321+ "evaluator": {
1322+ "name": "",
1323+ "config": {}
1324+ }
1325+ },
1326+ "action": { "decision": "deny" },
1327+ "execution": "server",
1328+ "enabled": true
1329+ }`
1330+ ) ;
1331+
1332+ const suggestions = await getJsonEditorSuggestions (
1333+ mockedPage ,
1334+ 'control-json-textarea' ,
1335+ 5 ,
1336+ 15
1337+ ) ;
1338+ const labels = suggestions . map ( ( item ) => item . label ) ;
1339+
1340+ // Should include available evaluator names
1341+ expect ( labels ) . toContain ( 'regex' ) ;
1342+ expect ( labels ) . toContain ( 'list' ) ;
1343+ expect ( labels ) . toContain ( 'json' ) ;
1344+ expect ( labels ) . toContain ( 'sql' ) ;
1345+ } ) ;
1346+
1347+ test ( 'full JSON editor suggests selector paths inside path field' , async ( {
1348+ mockedPage,
1349+ } ) => {
1350+ const compositeControl : Control = {
1351+ id : 86 ,
1352+ name : 'Selector Path Suggestions' ,
1353+ control : {
1354+ description : 'Test selector path suggestions' ,
1355+ enabled : true ,
1356+ execution : 'server' ,
1357+ condition : {
1358+ selector : { path : '' } ,
1359+ evaluator : { name : 'regex' , config : { pattern : '.*' } } ,
1360+ } ,
1361+ action : { decision : 'deny' } ,
1362+ tags : [ ] ,
1363+ } ,
1364+ } ;
1365+
1366+ await mockRoutes . agent ( mockedPage , {
1367+ controls : { data : { controls : [ compositeControl ] } } ,
1368+ agent : { data : mockData . agentWithSteps } ,
1369+ } ) ;
1370+ await mockedPage . route ( '**/api/v1/controls/86/data' , async ( route ) => {
1371+ await route . fulfill ( {
1372+ status : 200 ,
1373+ contentType : 'application/json' ,
1374+ body : JSON . stringify ( { data : compositeControl . control } ) ,
1375+ } ) ;
1376+ } ) ;
1377+
1378+ await mockedPage . goto (
1379+ getAgentControlsUrl ( { modal : 'edit' , controlId : 86 } )
1380+ ) ;
1381+
1382+ const modal = mockedPage . getByRole ( 'dialog' , { name : 'Edit Control' } ) ;
1383+ await expect ( modal ) . toBeVisible ( ) ;
1384+ await modal . getByText ( 'Full JSON' , { exact : true } ) . click ( ) ;
1385+
1386+ await setJsonEditorValue (
1387+ mockedPage ,
1388+ 'control-json-textarea' ,
1389+ `{
1390+ "condition": {
1391+ "selector": { "path": "" },
1392+ "evaluator": { "name": "regex", "config": {} }
1393+ },
1394+ "action": { "decision": "deny" },
1395+ "execution": "server",
1396+ "enabled": true
1397+ }`
1398+ ) ;
1399+
1400+ const suggestions = await getJsonEditorSuggestions (
1401+ mockedPage ,
1402+ 'control-json-textarea' ,
1403+ 3 ,
1404+ 28
1405+ ) ;
1406+ const labels = suggestions . map ( ( item ) => item . label ) ;
1407+
1408+ expect ( labels ) . toContain ( '*' ) ;
1409+ expect ( labels ) . toContain ( 'input' ) ;
1410+ expect ( labels ) . toContain ( 'output' ) ;
1411+ expect ( labels ) . toContain ( 'context' ) ;
1412+ } ) ;
1413+
1414+ test ( 'full JSON editor suggests all root properties in empty object' , async ( {
1415+ mockedPage,
1416+ } ) => {
1417+ const compositeControl : Control = {
1418+ id : 87 ,
1419+ name : 'Root Property Suggestions' ,
1420+ control : {
1421+ description : 'Test root suggestions' ,
1422+ enabled : true ,
1423+ execution : 'server' ,
1424+ condition : {
1425+ selector : { path : 'output' } ,
1426+ evaluator : { name : 'regex' , config : { pattern : '.*' } } ,
1427+ } ,
1428+ action : { decision : 'deny' } ,
1429+ tags : [ ] ,
1430+ } ,
1431+ } ;
1432+
1433+ await mockRoutes . agent ( mockedPage , {
1434+ controls : { data : { controls : [ compositeControl ] } } ,
1435+ agent : { data : mockData . agentWithSteps } ,
1436+ } ) ;
1437+ await mockedPage . route ( '**/api/v1/controls/87/data' , async ( route ) => {
1438+ await route . fulfill ( {
1439+ status : 200 ,
1440+ contentType : 'application/json' ,
1441+ body : JSON . stringify ( { data : compositeControl . control } ) ,
1442+ } ) ;
1443+ } ) ;
1444+
1445+ await mockedPage . goto (
1446+ getAgentControlsUrl ( { modal : 'edit' , controlId : 87 } )
1447+ ) ;
1448+
1449+ const modal = mockedPage . getByRole ( 'dialog' , { name : 'Edit Control' } ) ;
1450+ await expect ( modal ) . toBeVisible ( ) ;
1451+ await modal . getByText ( 'Full JSON' , { exact : true } ) . click ( ) ;
1452+
1453+ await setJsonEditorValue (
1454+ mockedPage ,
1455+ 'control-json-textarea' ,
1456+ `{
1457+
1458+ }`
1459+ ) ;
1460+
1461+ const suggestions = await getJsonEditorSuggestions (
1462+ mockedPage ,
1463+ 'control-json-textarea' ,
1464+ 2 ,
1465+ 3
1466+ ) ;
1467+ const labels = suggestions . map ( ( item ) => item . label ) ;
1468+
1469+ expect ( labels ) . toContain ( 'description' ) ;
1470+ expect ( labels ) . toContain ( 'enabled' ) ;
1471+ expect ( labels ) . toContain ( 'execution' ) ;
1472+ expect ( labels ) . toContain ( 'scope' ) ;
1473+ expect ( labels ) . toContain ( 'condition' ) ;
1474+ expect ( labels ) . toContain ( 'action' ) ;
1475+ expect ( labels ) . toContain ( 'tags' ) ;
1476+ expect ( labels ) . not . toContain ( '$schema' ) ;
1477+ } ) ;
1478+
12171479 test ( 'disables Form switch when JSON is invalid' , async ( { mockedPage } ) => {
12181480 await mockedPage . goto ( agentUrl ) ;
12191481
0 commit comments