[Obs AI Assistant] unskip kb user instructions test in serverless (#205519)

## Summary
Resolves https://github.com/elastic/kibana/issues/192886

- Unskips `knowledge_base_user_instructions.spec.ts` in serverless
  - stays skipped in mki due to proxy usage issue
  - Duplicates new changes from non serverless
- Removes `skipInMKI` tags for:
`knowledge_base_setup.spec.ts`
`knowledge_base.spec.ts `
`knowledge_base_status.spec.ts`


If https://github.com/elastic/kibana/issues/192718 is merged before
this, I will move `knowledge_base_user_instructions.spec.ts` to
deployment agnostic. Otherwise It can be done in that PR or another.
This commit is contained in:
Sandra G 2025-01-04 16:32:32 -05:00 committed by GitHub
parent 6049493e4a
commit b17b10eea1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 118 additions and 65 deletions

View file

@ -22,8 +22,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
describe('Knowledge base', function () { describe('Knowledge base', function () {
// TODO: https://github.com/elastic/kibana/issues/192886 kb/setup error
this.tags(['skipMKI']);
before(async () => { before(async () => {
await createKnowledgeBaseModel(ml); await createKnowledgeBaseModel(ml);

View file

@ -23,9 +23,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
describe('/internal/observability_ai_assistant/kb/setup', function () { describe('/internal/observability_ai_assistant/kb/setup', function () {
// TODO: https://github.com/elastic/kibana/issues/192886 kb/setup error
this.tags(['skipMKI']);
before(async () => { before(async () => {
await deleteKnowledgeBaseModel(ml).catch(() => {}); await deleteKnowledgeBaseModel(ml).catch(() => {});
await deleteInferenceEndpoint({ es }).catch(() => {}); await deleteInferenceEndpoint({ es }).catch(() => {});

View file

@ -24,8 +24,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
describe('/internal/observability_ai_assistant/kb/status', function () { describe('/internal/observability_ai_assistant/kb/status', function () {
this.tags(['skipMKI']);
before(async () => { before(async () => {
await createKnowledgeBaseModel(ml); await createKnowledgeBaseModel(ml);
await observabilityAIAssistantAPIClient await observabilityAIAssistantAPIClient

View file

@ -9,12 +9,14 @@ import expect from '@kbn/expect';
import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
import { Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/common'; import { Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/common';
import { CONTEXT_FUNCTION_NAME } from '@kbn/observability-ai-assistant-plugin/server/functions/context'; import { CONTEXT_FUNCTION_NAME } from '@kbn/observability-ai-assistant-plugin/server/functions/context';
import { Instruction } from '@kbn/observability-ai-assistant-plugin/common/types';
import { import {
clearConversations, clearConversations,
clearKnowledgeBase, clearKnowledgeBase,
createKnowledgeBaseModel, createKnowledgeBaseModel,
deleteInferenceEndpoint, deleteInferenceEndpoint,
deleteKnowledgeBaseModel, deleteKnowledgeBaseModel,
TINY_ELSER,
} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers'; } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers';
import { getConversationCreatedEvent } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/conversations/helpers'; import { getConversationCreatedEvent } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/conversations/helpers';
import { import {
@ -33,8 +35,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const log = getService('log'); const log = getService('log');
const svlUserManager = getService('svlUserManager'); const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi'); const svlCommonApi = getService('svlCommonApi');
const retry = getService('retry');
describe.skip('Knowledge base user instructions', function () { describe('Knowledge base user instructions', function () {
// TODO: https://github.com/elastic/kibana/issues/192751
this.tags(['skipMKI']); this.tags(['skipMKI']);
let editorRoleAuthc: RoleCredentials; let editorRoleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader; let internalReqHeader: InternalRequestHeader;
@ -45,8 +49,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await createKnowledgeBaseModel(ml); await createKnowledgeBaseModel(ml);
await observabilityAIAssistantAPIClient await observabilityAIAssistantAPIClient
.slsEditor({ .slsAdmin({
endpoint: 'POST /internal/observability_ai_assistant/kb/setup', endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
params: {
query: {
model_id: TINY_ELSER.id,
},
},
}) })
.expect(200); .expect(200);
}); });
@ -64,10 +73,22 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await clearKnowledgeBase(es); await clearKnowledgeBase(es);
const promises = [ const promises = [
{ username: 'editor', isPublic: true }, {
{ username: 'editor', isPublic: false }, username: 'editor' as const,
{ username: 'john', isPublic: true }, isPublic: true,
{ username: 'john', isPublic: false }, },
{
username: 'editor' as const,
isPublic: false,
},
{
username: 'secondary_editor' as const,
isPublic: true,
},
{
username: 'secondary_editor' as const,
isPublic: false,
},
].map(async ({ username, isPublic }) => { ].map(async ({ username, isPublic }) => {
const visibility = isPublic ? 'Public' : 'Private'; const visibility = isPublic ? 'Public' : 'Private';
const user = username === 'editor' ? 'slsEditor' : 'slsAdmin'; const user = username === 'editor' ? 'slsEditor' : 'slsAdmin';
@ -88,63 +109,71 @@ export default function ApiTest({ getService }: FtrProviderContext) {
}); });
it('"editor" can retrieve their own private instructions and the public instruction', async () => { it('"editor" can retrieve their own private instructions and the public instruction', async () => {
await retry.try(async () => {
const res = await observabilityAIAssistantAPIClient.slsEditor({ const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions',
}); });
const instructions = res.body.userInstructions; const instructions = res.body.userInstructions;
// TODO: gets 4 in serverless, bufferFlush event?
expect(instructions).to.have.length(3);
const sortByDocId = (data: any) => sortBy(data, 'doc_id'); const sortById = (data: Array<Instruction & { public?: boolean }>) => sortBy(data, 'id');
expect(sortByDocId(instructions)).to.eql( expect(sortById(instructions)).to.eql(
sortByDocId([ sortById([
{ {
doc_id: 'private-doc-from-editor', id: 'private-doc-from-editor',
public: false, public: false,
text: 'Private user instruction from "editor"', text: 'Private user instruction from "editor"',
}, },
{ {
doc_id: 'public-doc-from-editor', id: 'public-doc-from-editor',
public: true, public: true,
text: 'Public user instruction from "editor"', text: 'Public user instruction from "editor"',
}, },
{ {
doc_id: 'public-doc-from-john', id: 'public-doc-from-secondary_editor',
public: true, public: true,
text: 'Public user instruction from "john"', text: 'Public user instruction from "secondary_editor"',
}, },
]) ])
); );
}); });
});
it('"john" can retrieve their own private instructions and the public instruction', async () => { it('"secondaryEditor" can retrieve their own private instructions and the public instruction', async () => {
await retry.try(async () => {
const res = await observabilityAIAssistantAPIClient.slsAdmin({ const res = await observabilityAIAssistantAPIClient.slsAdmin({
endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions',
}); });
const instructions = res.body.userInstructions; const instructions = res.body.userInstructions;
expect(instructions).to.have.length(3);
const sortByDocId = (data: any) => sortBy(data, 'doc_id'); const sortById = (data: Array<Instruction & { public?: boolean }>) => sortBy(data, 'id');
expect(sortByDocId(instructions)).to.eql(
sortByDocId([ expect(sortById(instructions)).to.eql(
sortById([
{ {
doc_id: 'public-doc-from-editor', id: 'public-doc-from-editor',
public: true, public: true,
text: 'Public user instruction from "editor"', text: 'Public user instruction from "editor"',
}, },
{ {
doc_id: 'public-doc-from-john', id: 'public-doc-from-secondary_editor',
public: true, public: true,
text: 'Public user instruction from "john"', text: 'Public user instruction from "secondary_editor"',
}, },
{ {
doc_id: 'private-doc-from-john', id: 'private-doc-from-secondary_editor',
public: false, public: false,
text: 'Private user instruction from "john"', text: 'Private user instruction from "secondary_editor"',
}, },
]) ])
); );
}); });
}); });
});
describe('when updating an existing user instructions', () => { describe('when updating an existing user instructions', () => {
before(async () => { before(async () => {
@ -186,7 +215,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(instructions).to.eql([ expect(instructions).to.eql([
{ {
doc_id: 'doc-to-update', id: 'doc-to-update',
text: 'Updated text', text: 'Updated text',
public: false, public: false,
}, },
@ -330,6 +359,37 @@ export default function ApiTest({ getService }: FtrProviderContext) {
}); });
}); });
describe('Instructions can be saved and cleared again', () => {
async function updateInstruction(text: string) {
await observabilityAIAssistantAPIClient
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions',
params: {
body: {
id: 'my-instruction-that-will-be-cleared',
text,
public: false,
},
},
})
.expect(200);
const res = await observabilityAIAssistantAPIClient
.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions' })
.expect(200);
return res.body.userInstructions[0].text;
}
it('can clear the instruction', async () => {
const res1 = await updateInstruction('This is a user instruction that will be cleared');
expect(res1).to.be('This is a user instruction that will be cleared');
const res2 = await updateInstruction('');
expect(res2).to.be('');
});
});
describe('security roles and access privileges', () => { describe('security roles and access privileges', () => {
describe('should deny access for users without the ai_assistant privilege', () => { describe('should deny access for users without the ai_assistant privilege', () => {
it('PUT /internal/observability_ai_assistant/kb/user_instructions', async () => { it('PUT /internal/observability_ai_assistant/kb/user_instructions', async () => {