Skip to content

Commit 8a89f02

Browse files
committed
test(ui): add CI tests for JSON editor suggestion quality
- Test $schema and duplicate properties are filtered from suggestions - Test evaluator names appear inside evaluator name field - Test selector paths appear inside path field - Test all 7 root properties appear in empty object - All tests use __getJsonEditorSuggestions helper (works reliably in headless)
1 parent 291eb70 commit 8a89f02

1 file changed

Lines changed: 262 additions & 0 deletions

File tree

‎ui/tests/agent-detail.spec.ts‎

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)