Skip to content

Commit da6f83a

Browse files
authored
add mock cypress test for guardails (opendatahub-io#6159)
1 parent bd1fbc0 commit da6f83a

8 files changed

Lines changed: 569 additions & 2 deletions

File tree

packages/gen-ai/frontend/src/__tests__/cypress/cypress/__mocks__/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './mockMCPServers';
33
export * from './mockEmptyResponse';
44
export * from './mockMCPResponses';
55
export * from './mockAAModels';
6+
export * from './mockGuardrails';
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* eslint-disable camelcase */
2+
import type { GuardrailModelConfig, SafetyConfigResponse } from '~/app/types';
3+
4+
/**
5+
* Mock a single guardrail model configuration
6+
*
7+
* @param overrides - Partial properties to override defaults
8+
* @returns GuardrailModelConfig object
9+
*/
10+
export const mockGuardrailModelConfig = (
11+
overrides?: Partial<GuardrailModelConfig>,
12+
): GuardrailModelConfig => ({
13+
model_name: 'llama-guard-3',
14+
input_shield_id: 'trustyai_input',
15+
output_shield_id: 'trustyai_output',
16+
...overrides,
17+
});
18+
19+
/**
20+
* Mock safety config response with guardrail models
21+
*
22+
* @param models - Array of guardrail model configs (optional)
23+
* @returns SafetyConfigResponse object
24+
*/
25+
export const mockSafetyConfig = (
26+
models?: Partial<GuardrailModelConfig>[],
27+
): SafetyConfigResponse => {
28+
if (!models || models.length === 0) {
29+
return {
30+
guardrail_models: [mockGuardrailModelConfig()],
31+
};
32+
}
33+
34+
return {
35+
guardrail_models: models.map((model) => mockGuardrailModelConfig(model)),
36+
};
37+
};
38+
39+
/**
40+
* Mock an empty safety config response (no guardrails available)
41+
*
42+
* @returns SafetyConfigResponse with empty guardrail_models array
43+
*/
44+
export const mockEmptySafetyConfig = (): SafetyConfigResponse => ({
45+
guardrail_models: [],
46+
});
47+
48+
/**
49+
* Mock a guardrail violation response for input
50+
*
51+
* @param responseId - Optional response ID (default: 'resp_guardrail_123')
52+
* @param model - Optional model name (default: 'llama-3.2-1b')
53+
* @returns Mocked chat response with input violation
54+
*/
55+
export const mockInputGuardrailViolation = (
56+
responseId = 'resp_guardrail_123',
57+
model = 'llama-3.2-1b',
58+
): Record<string, unknown> => ({
59+
id: responseId,
60+
model,
61+
status: 'completed',
62+
created_at: Date.now(),
63+
output: [
64+
{
65+
id: 'msg_guardrail',
66+
type: 'message',
67+
role: 'assistant',
68+
status: 'completed',
69+
content: [
70+
{
71+
type: 'refusal',
72+
refusal:
73+
'I cannot process that request as it conflicts with my active safety guidelines. Please review your input for prompt manipulation, harmful content, or sensitive data (PII).',
74+
},
75+
],
76+
},
77+
],
78+
});
79+
80+
/**
81+
* Mock a guardrail violation response for output
82+
*
83+
* @param responseId - Optional response ID (default: 'resp_guardrail_456')
84+
* @param model - Optional model name (default: 'llama-3.2-1b')
85+
* @returns Mocked chat response with output violation
86+
*/
87+
export const mockOutputGuardrailViolation = (
88+
responseId = 'resp_guardrail_456',
89+
model = 'llama-3.2-1b',
90+
): Record<string, unknown> => ({
91+
id: responseId,
92+
model,
93+
status: 'completed',
94+
created_at: Date.now(),
95+
output: [
96+
{
97+
id: 'msg_guardrail_output',
98+
type: 'message',
99+
role: 'assistant',
100+
status: 'completed',
101+
content: [
102+
{
103+
type: 'refusal',
104+
refusal:
105+
'The response to your request was intercepted by safety guardrails. The output was found to contain potential harmful content or sensitive data (PII).',
106+
},
107+
],
108+
},
109+
],
110+
});

packages/gen-ai/frontend/src/__tests__/cypress/cypress/pages/appChrome.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class AppChrome {
22
visit(featureFlags?: string[]): void {
33
const flags = featureFlags || ['genAiStudio'];
4-
const flagsParam = flags.map((f) => `${encodeURIComponent(f)}=true`).join('&');
4+
const flagsParam = flags.map((f) => `${f}=true`).join(',');
55
const url = flagsParam ? `/?devFeatureFlags=${flagsParam}` : '/';
66
cy.visit(url);
77
this.waitForPageLoad();

packages/gen-ai/frontend/src/__tests__/cypress/cypress/pages/chatbotPage.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class ChatbotPage {
5050
this.findMCPTab().click();
5151
}
5252

53+
clickGuardrailsTab(): void {
54+
this.findGuardrailsTab().click();
55+
}
56+
5357
clickKnowledgeTab(): void {
5458
this.findKnowledgeTab().click();
5559
}
@@ -284,6 +288,10 @@ class ChatbotPage {
284288
this.findBotMessages().should('contain.text', text);
285289
}
286290

291+
verifyLastBotResponseContains(text: string): void {
292+
this.findBotMessages().last().should('contain.text', text);
293+
}
294+
287295
verifyStopButtonVisible(): void {
288296
this.findStopButton().should('be.visible');
289297
}
@@ -322,6 +330,53 @@ class ChatbotPage {
322330
this.toggleStreaming(true);
323331
}
324332

333+
// Guardrails Section (inside Guardrails tab)
334+
findGuardrailsSection(): Cypress.Chainable<JQuery<HTMLElement>> {
335+
// Click Guardrails tab first to show guardrails section
336+
this.clickGuardrailsTab();
337+
return cy.findByTestId('guardrails-section-title').parent();
338+
}
339+
340+
findGuardrailsEmptyState(): Cypress.Chainable<JQuery<HTMLElement>> {
341+
this.clickGuardrailsTab();
342+
return cy.findByTestId('guardrails-empty-state');
343+
}
344+
345+
findGuardrailModelToggle(): Cypress.Chainable<JQuery<HTMLElement>> {
346+
return cy.findByTestId('guardrail-model-toggle');
347+
}
348+
349+
findUserInputGuardrailsSwitch(): Cypress.Chainable<JQuery<HTMLElement>> {
350+
return cy.findByTestId('user-input-guardrails-switch');
351+
}
352+
353+
findModelOutputGuardrailsSwitch(): Cypress.Chainable<JQuery<HTMLElement>> {
354+
return cy.findByTestId('model-output-guardrails-switch');
355+
}
356+
357+
selectGuardrailModel(modelName: string): void {
358+
this.findGuardrailModelToggle().click();
359+
cy.findByRole('option', { name: modelName }).click();
360+
}
361+
362+
toggleUserInputGuardrails(enable: boolean): void {
363+
this.findUserInputGuardrailsSwitch().then(($toggle) => {
364+
const isChecked = $toggle.is(':checked');
365+
if ((enable && !isChecked) || (!enable && isChecked)) {
366+
this.findUserInputGuardrailsSwitch().click({ force: true });
367+
}
368+
});
369+
}
370+
371+
toggleModelOutputGuardrails(enable: boolean): void {
372+
this.findModelOutputGuardrailsSwitch().then(($toggle) => {
373+
const isChecked = $toggle.is(':checked');
374+
if ((enable && !isChecked) || (!enable && isChecked)) {
375+
this.findModelOutputGuardrailsSwitch().click({ force: true });
376+
}
377+
});
378+
}
379+
325380
// Metrics Section
326381
findMetricsToggle(): Cypress.Chainable<JQuery<HTMLElement>> {
327382
return cy.contains('button', /Show metrics|Hide metrics/i) as unknown as Cypress.Chainable<

0 commit comments

Comments
 (0)