[Obs AI Assistant] Use cookie auth for internal APIs in serverless tests (#203275)

## Summary

### Problem
Cookie authentication was introduced in Kibana for serverless internal
API tests via https://github.com/elastic/kibana/pull/192727.
The serverless tests for Obs AI Assistant still uses API key based auth.

### Solution
Change authentication to cookie based auth for internal APIs in
serverless tests.

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Viduni Wickramarachchi 2024-12-09 12:20:47 -05:00 committed by GitHub
parent fdedae07b8
commit 7eb005242c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 184 additions and 282 deletions

View file

@ -15,37 +15,47 @@ import supertest from 'supertest';
import { Subtract } from 'utility-types';
import { format } from 'url';
import { Config } from '@kbn/test';
import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest';
import { InheritedFtrProviderContext } from '../../../../services';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../shared/services';
export function getObservabilityAIAssistantApiClient({
svlSharedConfig,
supertestUserWithCookieCredentials,
}: {
svlSharedConfig: Config;
supertestUserWithCookieCredentials?: SupertestWithRoleScope;
}) {
const kibanaServer = svlSharedConfig.get('servers.kibana');
const cAuthorities = svlSharedConfig.get('servers.kibana.certificateAuthorities');
if (supertestUserWithCookieCredentials) {
return createObservabilityAIAssistantApiClient(supertestUserWithCookieCredentials);
} else {
const kibanaServer = svlSharedConfig.get('servers.kibana');
const cAuthorities = svlSharedConfig.get('servers.kibana.certificateAuthorities');
const url = format({
...kibanaServer,
auth: false, // don't use auth in serverless
});
return createObservabilityAIAssistantApiClient(supertest.agent(url, { ca: cAuthorities }));
const url = format({
...kibanaServer,
auth: false, // don't use auth in serverless
});
return createObservabilityAIAssistantApiClient(supertest.agent(url, { ca: cAuthorities }));
}
}
type ObservabilityAIAssistantApiClientKey = 'slsUser';
type ObservabilityAIAssistantApiClientKey = 'slsAdmin' | 'slsEditor' | 'slsUser';
export type ObservabilityAIAssistantApiClient = Record<
ObservabilityAIAssistantApiClientKey,
Awaited<ReturnType<typeof getObservabilityAIAssistantApiClient>>
>;
export function createObservabilityAIAssistantApiClient(st: supertest.Agent) {
export function createObservabilityAIAssistantApiClient(
st: SupertestWithRoleScope | supertest.Agent
) {
return <TEndpoint extends ObservabilityAIAssistantAPIEndpoint>(
options: {
type?: 'form-data';
endpoint: TEndpoint;
roleAuthc: RoleCredentials;
internalReqHeader: InternalRequestHeader;
roleAuthc?: RoleCredentials;
internalReqHeader?: InternalRequestHeader;
} & ObservabilityAIAssistantAPIClientRequestParamsOf<TEndpoint> & {
params?: { query?: { _inspect?: boolean } };
}
@ -57,7 +67,8 @@ export function createObservabilityAIAssistantApiClient(st: supertest.Agent) {
const { method, pathname, version } = formatRequest(endpoint, params.path);
const url = format({ pathname, query: params?.query });
const headers: Record<string, string> = { ...internalReqHeader, ...roleAuthc.apiKeyHeader };
const headers: Record<string, string> =
roleAuthc && internalReqHeader ? { ...internalReqHeader, ...roleAuthc.apiKeyHeader } : {};
if (version) {
headers['Elastic-Api-Version'] = version;
@ -182,10 +193,34 @@ export async function getObservabilityAIAssistantApiClientService({
getService,
}: InheritedFtrProviderContext): Promise<ObservabilityAIAssistantApiClient> {
const svlSharedConfig = getService('config');
// defaults to elastic_admin user when used without auth
const roleScopedSupertest = getService('roleScopedSupertest');
const supertestAdminWithCookieCredentials: SupertestWithRoleScope =
await roleScopedSupertest.getSupertestWithRoleScope('admin', {
useCookieHeader: true,
withInternalHeaders: true,
});
const supertestEditorWithCookieCredentials: SupertestWithRoleScope =
await roleScopedSupertest.getSupertestWithRoleScope('editor', {
useCookieHeader: true,
withInternalHeaders: true,
});
return {
// defaults to elastic_admin user when used without auth
slsUser: await getObservabilityAIAssistantApiClient({
svlSharedConfig,
}),
// cookie auth for internal apis
slsAdmin: await getObservabilityAIAssistantApiClient({
svlSharedConfig,
supertestUserWithCookieCredentials: supertestAdminWithCookieCredentials,
}),
// cookie auth for internal apis
slsEditor: await getObservabilityAIAssistantApiClient({
svlSharedConfig,
supertestUserWithCookieCredentials: supertestEditorWithCookieCredentials,
}),
};
}

View file

@ -12,6 +12,7 @@ import {
LlmProxy,
createLlmProxy,
} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_llm_proxy';
import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services';
@ -21,6 +22,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const log = getService('log');
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestEditorWithCookieCredentials: SupertestWithRoleScope;
const CHAT_API_URL = `/internal/observability_ai_assistant/chat`;
@ -52,6 +56,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
'editor',
{
useCookieHeader: true,
withInternalHeaders: true,
}
);
proxy = await createLlmProxy(log);
connectorId = await createProxyActionConnector({
supertest: supertestWithoutAuth,
@ -75,10 +88,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
it("returns a 4xx if the connector doesn't exist", async () => {
await supertestWithoutAuth
await supertestEditorWithCookieCredentials
.post(CHAT_API_URL)
.set(roleAuthc.apiKeyHeader)
.set(internalReqHeader)
.send({
name: 'my_api_call',
messages,
@ -104,10 +115,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const receivedChunks: Array<Record<string, any>> = [];
const passThrough = new PassThrough();
supertestWithoutAuth
supertestEditorWithCookieCredentials
.post(CHAT_API_URL)
.set(roleAuthc.apiKeyHeader)
.set(internalReqHeader)
.on('error', reject)
.send({
name: 'my_api_call',

View file

@ -25,6 +25,7 @@ import {
LlmResponseSimulator,
} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_llm_proxy';
import { createOpenAiChunk } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_openai_chunk';
import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
decodeEvents,
@ -39,6 +40,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const log = getService('log');
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestEditorWithCookieCredentials: SupertestWithRoleScope;
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
@ -82,10 +86,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
(body) => !isFunctionTitleRequest(body)
);
const responsePromise = new Promise<Response>((resolve, reject) => {
supertestWithoutAuth
supertestEditorWithCookieCredentials
.post(COMPLETE_API_URL)
.set(roleAuthc.apiKeyHeader)
.set(internalReqHeader)
.send({
messages,
connectorId,
@ -134,6 +136,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
roleAuthc,
internalReqHeader,
});
supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
'editor',
{
useCookieHeader: true,
withInternalHeaders: true,
}
);
});
after(async () => {
@ -155,10 +165,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const passThrough = new PassThrough();
supertestWithoutAuth
supertestEditorWithCookieCredentials
.post(COMPLETE_API_URL)
.set(roleAuthc.apiKeyHeader)
.set(internalReqHeader)
.send({
messages,
connectorId,
@ -254,6 +262,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
});
describe('when creating a new conversation', () => {
let events: StreamingChatResponseEvent[];
@ -273,12 +282,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
content: 'Hello',
},
});
expect(omit(events[1], 'id')).to.eql({
type: StreamingChatResponseEventType.ChatCompletionChunk,
message: {
content: ' again',
},
});
expect(omit(events[2], 'id', 'message.@timestamp')).to.eql({
type: StreamingChatResponseEventType.ChatCompletionMessage,
message: {
@ -329,10 +340,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
)[0]?.conversation.id;
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
roleAuthc,
internalReqHeader,
params: {
path: {
conversationId: createdConversationId,
@ -417,10 +426,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
).to.eql(0);
const conversations = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
roleAuthc,
internalReqHeader,
})
.expect(200);
@ -449,10 +456,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.completeAfterIntercept();
const createResponse = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
roleAuthc,
internalReqHeader,
params: {
body: {
messages,
@ -470,10 +475,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
conversationCreatedEvent = getConversationCreatedEvent(createResponse.body);
const conversationId = conversationCreatedEvent.conversation.id;
const fullConversation = await observabilityAIAssistantAPIClient.slsUser({
const fullConversation = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId,
@ -486,10 +489,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.completeAfterIntercept();
const updatedResponse = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
internalReqHeader,
roleAuthc,
params: {
body: {
messages: [
@ -519,10 +520,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: conversationCreatedEvent.conversation.id,

View file

@ -63,8 +63,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const responseBody = await invokeChatCompleteWithFunctionRequest({
connectorId,
observabilityAIAssistantAPIClient,
internalReqHeader,
roleAuthc,
functionCall: {
name: ELASTICSEARCH_FUNCTION_NAME,
trigger: MessageRole.User,

View file

@ -13,7 +13,6 @@ import {
} from '@kbn/observability-ai-assistant-plugin/common';
import type { AssistantScope } from '@kbn/ai-assistant-common';
import { Readable } from 'stream';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../../shared/services';
import { ObservabilityAIAssistantApiClient } from '../../../common/observability_ai_assistant_api_client';
function decodeEvents(body: Readable | string) {
@ -34,22 +33,16 @@ export async function invokeChatCompleteWithFunctionRequest({
connectorId,
observabilityAIAssistantAPIClient,
functionCall,
roleAuthc,
internalReqHeader,
scopes,
}: {
connectorId: string;
observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient;
functionCall: Message['message']['function_call'];
scopes?: AssistantScope[];
roleAuthc: RoleCredentials;
internalReqHeader: InternalRequestHeader;
}) {
const { body } = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
internalReqHeader,
roleAuthc,
params: {
body: {
messages: [

View file

@ -53,8 +53,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await invokeChatCompleteWithFunctionRequest({
connectorId,
observabilityAIAssistantAPIClient,
internalReqHeader,
roleAuthc,
functionCall: {
name: 'summarize',
trigger: MessageRole.User,
@ -77,10 +75,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
it('persists entry in knowledge base', async () => {
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
internalReqHeader,
roleAuthc,
params: {
query: {
query: '',

View file

@ -24,6 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('List connectors', () => {
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
@ -45,19 +46,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('Returns a 2xx for enterprise license', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
roleAuthc,
internalReqHeader,
})
.expect(200);
});
it('returns an empty list of connectors', async () => {
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
roleAuthc,
internalReqHeader,
});
expect(res.body.length).to.be(0);
@ -72,10 +69,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
roleAuthc,
});
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
internalReqHeader,
roleAuthc,
});
expect(res.body.length).to.be(1);

View file

@ -14,12 +14,9 @@ import {
} from '@kbn/observability-ai-assistant-plugin/common/types';
import type { FtrProviderContext } from '../../common/ftr_provider_context';
import type { SupertestReturnType } from '../../common/observability_ai_assistant_api_client';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services';
export default function ApiTest({ getService }: FtrProviderContext) {
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const conversationCreate: ConversationCreateRequest = {
'@timestamp': new Date().toISOString(),
@ -48,22 +45,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
describe('Conversations', () => {
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
});
after(async () => {
await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});
describe('without conversations', () => {
it('returns no conversations when listing', async () => {
const response = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
internalReqHeader,
roleAuthc,
})
.expect(200);
@ -72,10 +58,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns a 404 for updating conversations', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: 'non-existing-conversation-id',
@ -90,10 +74,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns a 404 for retrieving a conversation', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: 'my-conversation-id',
@ -108,12 +90,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let createResponse: Awaited<
SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'>
>;
before(async () => {
createResponse = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/conversation',
roleAuthc,
internalReqHeader,
params: {
body: {
conversation: conversationCreate,
@ -125,10 +106,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: createResponse.body.conversation.id,
@ -138,10 +117,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.expect(200);
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: createResponse.body.conversation.id,
@ -150,6 +127,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
})
.expect(404);
});
it('returns the conversation', function () {
// delete user from response to avoid comparing it as it will be different in MKI
delete createResponse.body.user;
@ -170,10 +148,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns a 404 for updating a non-existing conversation', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
roleAuthc,
internalReqHeader,
params: {
path: {
conversationId: 'non-existing-conversation-id',
@ -188,10 +164,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns a 404 for retrieving a non-existing conversation', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
roleAuthc,
internalReqHeader,
params: {
path: {
conversationId: 'non-existing-conversation-id',
@ -203,10 +177,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns the conversation that was created', async () => {
const response = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: createResponse.body.conversation.id,
@ -222,10 +194,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns the created conversation when listing', async () => {
const response = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
roleAuthc,
internalReqHeader,
})
.expect(200);
// delete user from response to avoid comparing it as it will be different in MKI
@ -243,10 +213,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(async () => {
updateResponse = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: createResponse.body.conversation.id,
@ -269,10 +237,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns the updated conversation after get', async () => {
const updateAfterCreateResponse = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: createResponse.body.conversation.id,

View file

@ -14,7 +14,6 @@ import {
StreamingChatResponseEventType,
} from '@kbn/observability-ai-assistant-plugin/common/conversation_complete';
import { ObservabilityAIAssistantApiClient } from '../../common/observability_ai_assistant_api_client';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services';
export function decodeEvents(body: Readable | string) {
return String(body)
@ -56,20 +55,14 @@ export function getConversationUpdatedEvent(body: Readable | string) {
export async function deleteAllConversations({
observabilityAIAssistantAPIClient,
internalReqHeader,
roleAuthc,
log,
}: {
observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient;
internalReqHeader: InternalRequestHeader;
roleAuthc: RoleCredentials;
log: ToolingLog;
}) {
const findConversationsResponse = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
internalReqHeader,
roleAuthc,
params: {
body: {
query: '',
@ -87,10 +80,8 @@ export async function deleteAllConversations({
conversations.map(async (conversation) => {
try {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
internalReqHeader,
roleAuthc,
params: {
path: {
conversationId: conversation.conversation.id,

View file

@ -13,59 +13,51 @@ import {
deleteKnowledgeBaseModel,
} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services';
export default function ApiTest({ getService }: FtrProviderContext) {
const ml = getService('ml');
const es = getService('es');
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
// TODO: https://github.com/elastic/kibana/issues/192886
describe.skip('Knowledge base', function () {
this.tags(['skipMKI']);
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
await createKnowledgeBaseModel(ml);
});
after(async () => {
await deleteKnowledgeBaseModel(ml);
await deleteInferenceEndpoint({ es });
await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});
it('returns 200 on knowledge base setup', async () => {
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
roleAuthc,
internalReqHeader,
})
.expect(200);
expect(res.body).to.eql({});
});
describe('when managing a single entry', () => {
const knowledgeBaseEntry = {
id: 'my-doc-id-1',
title: 'My title',
text: 'My content',
};
it('returns 200 on create', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/save',
params: { body: knowledgeBaseEntry },
roleAuthc,
internalReqHeader,
})
.expect(200);
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -74,8 +66,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'asc',
},
},
roleAuthc,
internalReqHeader,
});
const entry = res.body.entries[0];
expect(entry.id).to.equal(knowledgeBaseEntry.id);
@ -84,7 +74,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns 200 on get entries and entry exists', async () => {
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -93,8 +83,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'asc',
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
const entry = res.body.entries[0];
@ -105,18 +93,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns 200 on delete', async () => {
const entryId = 'my-doc-id-1';
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}',
params: {
path: { entryId },
},
roleAuthc,
internalReqHeader,
})
.expect(200);
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -125,8 +111,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'asc',
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
expect(res.body.entries.filter((entry) => entry.id.startsWith('my-doc-id')).length).to.eql(
@ -137,24 +121,25 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns 500 on delete not found', async () => {
const entryId = 'my-doc-id-1';
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}',
params: {
path: { entryId },
},
roleAuthc,
internalReqHeader,
})
.expect(500);
});
});
describe('when managing multiple entries', () => {
before(async () => {
await clearKnowledgeBase(es);
});
afterEach(async () => {
await clearKnowledgeBase(es);
});
const knowledgeBaseEntries = [
{
id: 'my_doc_a',
@ -172,18 +157,17 @@ export default function ApiTest({ getService }: FtrProviderContext) {
text: 'My content c',
},
];
it('returns 200 on create', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import',
params: { body: { entries: knowledgeBaseEntries } },
roleAuthc,
internalReqHeader,
})
.expect(200);
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -192,8 +176,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'asc',
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
expect(res.body.entries.filter((entry) => entry.id.startsWith('my_doc')).length).to.eql(3);
@ -201,16 +183,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('allows sorting', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import',
params: { body: { entries: knowledgeBaseEntries } },
roleAuthc,
internalReqHeader,
})
.expect(200);
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -219,8 +199,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'desc',
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
@ -231,7 +209,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
// asc
const resAsc = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -240,8 +218,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'asc',
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
@ -250,18 +226,17 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(entriesAsc[1].id).to.eql('my_doc_b');
expect(entriesAsc[2].id).to.eql('my_doc_c');
});
it('allows searching', async () => {
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import',
params: { body: { entries: knowledgeBaseEntries } },
roleAuthc,
internalReqHeader,
})
.expect(200);
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: {
@ -270,8 +245,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
sortDirection: 'asc',
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);

View file

@ -14,44 +14,30 @@ import {
} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services';
export default function ApiTest({ getService }: FtrProviderContext) {
const ml = getService('ml');
const es = getService('es');
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
describe('/internal/observability_ai_assistant/kb/setup', function () {
this.tags(['skipMKI']);
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
before(async () => {
await deleteKnowledgeBaseModel(ml).catch(() => {});
await deleteInferenceEndpoint({ es }).catch(() => {});
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
});
after(async () => {
await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});
it('returns empty object when successful', async () => {
await createKnowledgeBaseModel(ml);
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsAdmin({
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
params: {
query: {
model_id: TINY_ELSER.id,
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
@ -64,15 +50,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns bad request if model cannot be installed', async () => {
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsAdmin({
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
params: {
query: {
model_id: TINY_ELSER.id,
},
},
roleAuthc,
internalReqHeader,
})
.expect(500);

View file

@ -14,34 +14,25 @@ import {
} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers';
import { AI_ASSISTANT_KB_INFERENCE_ID } from '@kbn/observability-ai-assistant-plugin/server/service/inference_endpoint';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services';
export default function ApiTest({ getService }: FtrProviderContext) {
const ml = getService('ml');
const es = getService('es');
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
describe('/internal/observability_ai_assistant/kb/status', function () {
this.tags(['skipMKI']);
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
await createKnowledgeBaseModel(ml);
await observabilityAIAssistantAPIClient
.slsUser({
.slsAdmin({
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
params: {
query: {
model_id: TINY_ELSER.id,
},
},
roleAuthc,
internalReqHeader,
})
.expect(200);
});
@ -49,15 +40,12 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
await deleteKnowledgeBaseModel(ml);
await deleteInferenceEndpoint({ es, name: AI_ASSISTANT_KB_INFERENCE_ID }).catch((err) => {});
await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});
it('returns correct status after knowledge base is setup', async () => {
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/status',
roleAuthc,
internalReqHeader,
})
.expect(200);
@ -70,10 +58,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await deleteInferenceEndpoint({ es, name: AI_ASSISTANT_KB_INFERENCE_ID });
const res = await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/status',
roleAuthc,
internalReqHeader,
})
.expect(200);

View file

@ -34,26 +34,19 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
// TODO: https://github.com/elastic/kibana/issues/192711 cannot create custom users in serverless
// trying using built in users by using cookie auth
// TODO: https://github.com/elastic/kibana/issues/192757
describe.skip('Knowledge base user instructions', function () {
this.tags(['skipMKI']);
let editorRoleAuthc: RoleCredentials;
let johnRoleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
before(async () => {
// Create API keys for 'editor' role, simulating different users
johnRoleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
editorRoleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
await createKnowledgeBaseModel(ml);
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
roleAuthc: editorRoleAuthc,
internalReqHeader,
})
.expect(200);
});
@ -63,7 +56,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await deleteInferenceEndpoint({ es });
await clearKnowledgeBase(es);
await clearConversations(es);
await svlUserManager.invalidateM2mApiKeyWithRoleScope(johnRoleAuthc);
await svlUserManager.invalidateM2mApiKeyWithRoleScope(editorRoleAuthc);
});
@ -72,37 +64,34 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await clearKnowledgeBase(es);
const promises = [
{ roleAuthc: editorRoleAuthc, username: 'editor', isPublic: true },
{ roleAuthc: editorRoleAuthc, username: 'editor', isPublic: false },
{ roleAuthc: johnRoleAuthc, username: 'john', isPublic: true },
{ roleAuthc: johnRoleAuthc, username: 'john', isPublic: false },
].map(async ({ roleAuthc, username, isPublic }) => {
{ username: 'editor', isPublic: true },
{ username: 'editor', isPublic: false },
{ username: 'john', isPublic: true },
{ username: 'john', isPublic: false },
].map(async ({ username, isPublic }) => {
const visibility = isPublic ? 'Public' : 'Private';
await observabilityAIAssistantAPIClient
.slsUser({
endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions',
params: {
body: {
id: `${visibility.toLowerCase()}-doc-from-${username}`,
text: `${visibility} user instruction from "${username}"`,
public: isPublic,
},
const user = username === 'editor' ? 'slsEditor' : 'slsAdmin';
await observabilityAIAssistantAPIClient[user]({
endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions',
params: {
body: {
id: `${visibility.toLowerCase()}-doc-from-${username}`,
text: `${visibility} user instruction from "${username}"`,
public: isPublic,
},
roleAuthc,
internalReqHeader,
})
.expect(200);
},
}).expect(200);
});
await Promise.all(promises);
});
it('"editor" can retrieve their own private instructions and the public instruction', async () => {
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions',
roleAuthc: editorRoleAuthc,
internalReqHeader,
});
const instructions = res.body.userInstructions;
const sortByDocId = (data: any) => sortBy(data, 'doc_id');
@ -128,11 +117,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
it('"john" can retrieve their own private instructions and the public instruction', async () => {
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsAdmin({
endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions',
roleAuthc: johnRoleAuthc,
internalReqHeader,
});
const instructions = res.body.userInstructions;
const sortByDocId = (data: any) => sortBy(data, 'doc_id');
@ -163,7 +151,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await clearKnowledgeBase(es);
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions',
params: {
body: {
@ -172,13 +160,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
public: true,
},
},
roleAuthc: editorRoleAuthc,
internalReqHeader,
})
.expect(200);
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions',
params: {
body: {
@ -187,18 +173,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
public: false,
},
},
roleAuthc: editorRoleAuthc,
internalReqHeader,
})
.expect(200);
});
it('updates the user instruction', async () => {
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions',
roleAuthc: editorRoleAuthc,
internalReqHeader,
});
const instructions = res.body.userInstructions;
expect(instructions).to.eql([
@ -218,10 +201,12 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const userInstructionText =
'Be polite and use language that is easy to understand. Never disagree with the user.';
async function getConversationForUser(roleAuthc: RoleCredentials) {
async function getConversationForUser(username: string) {
const user = username === 'editor' ? 'slsEditor' : 'slsAdmin';
// the user instruction is always created by "editor" user
await observabilityAIAssistantAPIClient
.slsUser({
.slsEditor({
endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions',
params: {
body: {
@ -230,8 +215,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
public: false,
},
},
roleAuthc: editorRoleAuthc,
internalReqHeader,
})
.expect(200);
@ -259,36 +242,30 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
];
const createResponse = await observabilityAIAssistantAPIClient
.slsUser({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
params: {
body: {
messages,
connectorId,
persist: true,
screenContexts: [],
scopes: ['observability'],
},
const createResponse = await observabilityAIAssistantAPIClient[user]({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
params: {
body: {
messages,
connectorId,
persist: true,
screenContexts: [],
scopes: ['observability'],
},
roleAuthc,
internalReqHeader,
})
.expect(200);
},
}).expect(200);
await proxy.waitForAllInterceptorsSettled();
const conversationCreatedEvent = getConversationCreatedEvent(createResponse.body);
const conversationId = conversationCreatedEvent.conversation.id;
const res = await observabilityAIAssistantAPIClient.slsUser({
const res = await observabilityAIAssistantAPIClient[user]({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
conversationId,
},
},
roleAuthc,
internalReqHeader,
});
// wait for all interceptors to be settled
@ -321,7 +298,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
it('adds the instruction to the system prompt', async () => {
const conversation = await getConversationForUser(editorRoleAuthc);
const conversation = await getConversationForUser('editor');
const systemMessage = conversation.messages.find(
(message) => message.message.role === MessageRole.System
)!;
@ -329,7 +306,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
it('does not add the instruction to the context', async () => {
const conversation = await getConversationForUser(editorRoleAuthc);
const conversation = await getConversationForUser('editor');
const contextMessage = conversation.messages.find(
(message) => message.message.name === CONTEXT_FUNCTION_NAME
);
@ -343,7 +320,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
it('does not add the instruction conversation for other users', async () => {
const conversation = await getConversationForUser(johnRoleAuthc);
const conversation = await getConversationForUser('john');
const systemMessage = conversation.messages.find(
(message) => message.message.role === MessageRole.System
)!;

View file

@ -48,6 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
},
];
describe('/api/observability_ai_assistant/chat/complete', function () {
// TODO: https://github.com/elastic/kibana/issues/192751
this.tags(['skipMKI']);
@ -108,6 +109,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await titleSimulator.complete();
await conversationSimulator.status(200);
if (conversationSimulatorCallback) {
await conversationSimulatorCallback(conversationSimulator);
}
@ -158,8 +160,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
after(async () => {
await deleteAllConversations({
observabilityAIAssistantAPIClient,
internalReqHeader,
roleAuthc,
log,
});
await deleteActionConnector({ supertest, connectorId, log, roleAuthc, internalReqHeader });