Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ declare global {
query: string,
errorMessage: string,
): Chainable<Element>;
interceptQueryWithApproval(alias: string, query: string): Chainable<Element>;
interceptToolApproval(alias: string, approved: boolean): Chainable<Element>;
}
}
}
Expand Down Expand Up @@ -178,6 +180,37 @@ Cypress.Commands.add(
},
);

/* eslint-disable camelcase */
const MOCK_STREAMED_RESPONSE_WITH_APPROVAL_BODY = `data: {"event": "start", "data": {"conversation_id": "5f424596-a4f9-4a3a-932b-46a768de3e7c"}}

data: {"event": "token", "data": {"id": 0, "token": "Mock"}}

data: {"event": "token", "data": {"id": 1, "token": " response"}}

data: {"event": "tool_call", "data": {"id": "tool-123", "name": "mock_tool", "args": {"namespace": "default"}}}

data: {"event": "approval_required", "data": {"approval_id": "abc", "tool_name": "mock_tool", "tool_description": "This action will list pods in the cluster.", "tool_args": {"namespace": "default"}}}

data: {"event": "end", "data": {"referenced_documents": [], "truncated": false}}
`;
/* eslint-enable camelcase */

Cypress.Commands.add('interceptQueryWithApproval', (alias: string, query: string) => {
cy.intercept('POST', getApiUrl('/v1/streaming_query'), (request) => {
expect(request.body.media_type).to.equal('application/json');
expect(request.body.query).to.include(query);
request.reply({ body: MOCK_STREAMED_RESPONSE_WITH_APPROVAL_BODY, delay: 500 });
}).as(alias);
});

Cypress.Commands.add('interceptToolApproval', (alias: string, approved: boolean) => {
cy.intercept('POST', getApiUrl('/v1/tool-approvals/decision'), (request) => {
expect(request.body.approval_id).to.equal('abc');
expect(request.body.approved).to.equal(approved);
request.reply({ statusCode: 200, body: {} });
}).as(alias);
});

const USER_FEEDBACK_MOCK_RESPONSE = { body: { message: 'Feedback received' } };

Cypress.Commands.add(
Expand Down
61 changes: 61 additions & 0 deletions tests/tests/lightspeed-install.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const userFeedback = `${popover} .ols-plugin__feedback`;
const userFeedbackInput = `${userFeedback} textarea`;
const userFeedbackSubmit = `${userFeedback} button.pf-m-primary`;
const modal = '.ols-plugin__modal';
const toolApprovalCard = `${popover} .ols-plugin__tool-call`;
const toolLabel = `${popover} .pf-v6-c-label`;

const promptArea = `${popover} .ols-plugin__prompt`;
const attachButton = `${promptArea} .pf-chatbot__button--attach`;
Expand Down Expand Up @@ -454,6 +456,65 @@ spec:
});
});

describe('Tool approval (HITL)', { tags: ['@hitl'] }, () => {
it('Test approval card is shown and tool can be approved', () => {
cy.visit('/search/all-namespaces');
cy.get('h1').contains('Search').should('exist');
cy.get(mainButton).click();

cy.interceptQueryWithApproval('queryWithApproval', PROMPT_SUBMITTED);
cy.interceptToolApproval('approvalStub', true);
cy.get(promptInput).type(`${PROMPT_SUBMITTED}{enter}`);
cy.wait('@queryWithApproval');

cy.get(toolApprovalCard).should('exist');
cy.get(toolApprovalCard).should('contain', 'Review required');
cy.get(toolApprovalCard).should('contain', 'This action will list pods in the cluster.');
cy.get(toolApprovalCard).find('button').contains('Approve').should('exist');
cy.get(toolApprovalCard).find('button').contains('Reject').should('exist');

cy.get(toolApprovalCard).contains('View action details').click();
cy.get(toolApprovalCard).should('contain', 'mock_tool');
cy.get(toolApprovalCard).should('contain', 'namespace');

cy.get(toolApprovalCard).find('button').contains('Approve').click();
cy.wait('@approvalStub');
cy.get(toolApprovalCard).should('not.exist');
cy.get(toolLabel).should('contain', 'mock_tool');

cy.get(toolLabel).contains('mock_tool').click();
cy.get(modal).should('contain', 'Tool output');
cy.get(modal).should('contain', 'mock_tool');
cy.get(modal).should('contain', 'Status');
cy.get(modal).should('contain', 'pending');
cy.get(modal).should('not.contain', 'Tool call rejected');
cy.get(modal).find('button[title="Close"]').click();
});

it('Test tool can be rejected', () => {
cy.visit('/search/all-namespaces');
cy.get('h1').contains('Search').should('exist');
cy.get(mainButton).click();

cy.interceptQueryWithApproval('queryWithApproval', PROMPT_SUBMITTED);
cy.interceptToolApproval('denialStub', false);
cy.get(promptInput).type(`${PROMPT_SUBMITTED}{enter}`);
cy.wait('@queryWithApproval');

cy.get(toolApprovalCard).should('exist');
cy.get(toolApprovalCard).find('button').contains('Reject').click();
cy.wait('@denialStub');
cy.get(toolApprovalCard).should('not.exist');
cy.get(toolLabel).should('contain', 'mock_tool');
cy.get(toolLabel).contains('mock_tool').click();
cy.get(modal).should('contain', 'Tool call rejected');
cy.get(modal).should('contain', 'mock_tool');
cy.get(modal).should('not.contain', 'Status');
cy.get(modal).should('not.contain', 'Content');
cy.get(modal).find('button[title="Close"]').click();
});
});

describe('User feedback', { tags: ['@feedback'] }, () => {
it('Test user feedback form', () => {
cy.visit('/search/all-namespaces');
Expand Down