From 84c29380326b9c050131901a928b3b2b878a4557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Thu, 16 May 2024 09:19:17 +0200 Subject: [PATCH 1/6] Add scoped privileges to readUser and writeUser --- .../common/config.ts | 21 +++++--- .../common/users/create_users_and_roles.ts | 31 +++++++++++ .../common/users/roles.ts | 52 +++++++++++++++++++ .../common/users/users.ts | 30 +++++++++++ .../tests/index.ts | 6 +++ 5 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts create mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts create mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 351d43f4e30b2..141c07c6e45de 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Config, FtrConfigProviderContext } from '@kbn/test'; +import { Config, FtrConfigProviderContext, kbnTestConfig } from '@kbn/test'; import supertest from 'supertest'; import { format, UrlObject } from 'url'; import { ObservabilityAIAssistantFtrConfigName } from '../configs'; @@ -15,6 +15,7 @@ import { createObservabilityAIAssistantApiClient, ObservabilityAIAssistantAPIClient, } from './observability_ai_assistant_api_client'; +import { editorUser, viewerUser } from './users/users'; export interface ObservabilityAIAssistantFtrConfig { name: ObservabilityAIAssistantFtrConfigName; @@ -22,8 +23,14 @@ export interface ObservabilityAIAssistantFtrConfig { kibanaConfig?: Record; } -async function getObservabilityAIAssistantAPIClient(kibanaServerUrl: string) { - return createObservabilityAIAssistantApiClient(supertest(kibanaServerUrl)); +async function getObservabilityAIAssistantAPIClient(kibanaServer: UrlObject, username: string) { + const { password } = kbnTestConfig.getUrlParts(); + const baseUrlWithAuth = format({ + ...kibanaServer, + auth: `${username}:${password}`, + }); + + return createObservabilityAIAssistantApiClient(supertest(baseUrlWithAuth)); } export type CreateTestConfig = ReturnType; @@ -42,7 +49,7 @@ export interface CreateTest { kbnTestServer: any; } -export function createObservabilityAIAssistantAPIConfig({ +function createObservabilityAIAssistantAPIConfig({ config, license, name, @@ -56,7 +63,7 @@ export function createObservabilityAIAssistantAPIConfig({ const services = config.get('services') as InheritedServices; const servers = config.get('servers'); const kibanaServer = servers.kibana as UrlObject; - const kibanaServerUrl = format(kibanaServer); + // const kibanaServerUrl = format(kibanaServer); const apmSynthtraceKibanaClient = services.apmSynthtraceKibanaClient(); const createTest: Omit = { @@ -68,8 +75,8 @@ export function createObservabilityAIAssistantAPIConfig({ getApmSynthtraceEsClient(context, apmSynthtraceKibanaClient), observabilityAIAssistantAPIClient: async () => { return { - readUser: await getObservabilityAIAssistantAPIClient(kibanaServerUrl), - writeUser: await getObservabilityAIAssistantAPIClient(kibanaServerUrl), + readUser: await getObservabilityAIAssistantAPIClient(kibanaServer, viewerUser.username), + writeUser: await getObservabilityAIAssistantAPIClient(kibanaServer, editorUser.username), }; }, }, diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts new file mode 100644 index 0000000000000..59ec5cbd31492 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InheritedFtrProviderContext } from '../ftr_provider_context'; +import { allUsers } from './users'; +import { allRoles } from './roles'; + +export async function createUsersAndRoles(getService: InheritedFtrProviderContext['getService']) { + const security = getService('security'); + + // create roles + await Promise.all( + allRoles.map(({ name, privileges }) => { + return security.role.create(name, privileges); + }) + ); + + // create users + await Promise.all( + allUsers.map((user) => { + return security.user.create(user.username, { + password: user.password, + roles: user.roles, + }); + }) + ); +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts new file mode 100644 index 0000000000000..ec5c9daac3ea9 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// Example role: +// export const allAccessRole: Role = { +// name: 'all_access', +// privileges: { +// elasticsearch: { +// indices: [ +// { +// names: ['*'], +// privileges: ['all'], +// }, +// ], +// }, +// kibana: [ +// { +// feature: { +// apm: ['all'], +// actions: ['all'], +// }, +// spaces: ['*'], +// }, +// ], +// }, +// }; + +export interface Role { + name: string; + privileges: { + elasticsearch?: { + cluster?: string[]; + indices?: Array<{ + names: string[]; + privileges: string[]; + }>; + }; + kibana?: Array<{ + spaces: string[]; + base?: string[]; + feature?: { + [featureId: string]: string[]; + }; + }>; + }; +} + +export const allRoles = []; diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts new file mode 100644 index 0000000000000..80c05f58990c0 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kbnTestConfig } from '@kbn/test'; + +const password = kbnTestConfig.getUrlParts().password!; + +export const editorUser: User = { + username: 'editor', + password, + roles: ['editor'], +}; + +export const viewerUser: User = { + username: 'viewer', + password, + roles: ['viewer'], +}; + +export interface User { + username: string; + password: string; + roles: string[]; +} + +export const allUsers = [editorUser, viewerUser]; diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts index 20e8338a55a3f..e0312d2f76019 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts @@ -6,6 +6,7 @@ */ import globby from 'globby'; import path from 'path'; +import { createUsersAndRoles } from '../common/users/create_users_and_roles'; import { FtrProviderContext } from '../common/ftr_provider_context'; const cwd = path.join(__dirname); @@ -18,6 +19,11 @@ export default function observabilityAIAssistantApiIntegrationTests({ const filePattern = '**/*.spec.ts'; const tests = globby.sync(filePattern, { cwd }); + // Creates roles and users before running tests + before(async () => { + await createUsersAndRoles(getService); + }); + tests.forEach((testName) => { describe(testName, () => { loadTestFile(require.resolve(`./${testName}`)); From 3615a9110b99e9be7489e41490bb0693ccb07c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Thu, 16 May 2024 10:41:37 +0200 Subject: [PATCH 2/6] Remove unused line Co-authored-by: Milton Hultgren --- .../observability_ai_assistant_api_integration/common/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 141c07c6e45de..6bd2d049ed2bc 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -63,7 +63,6 @@ function createObservabilityAIAssistantAPIConfig({ const services = config.get('services') as InheritedServices; const servers = config.get('servers'); const kibanaServer = servers.kibana as UrlObject; - // const kibanaServerUrl = format(kibanaServer); const apmSynthtraceKibanaClient = services.apmSynthtraceKibanaClient(); const createTest: Omit = { From 9881944cedf001a20ac4ed2bb0cf243f0bcfcca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 21 May 2024 12:44:08 +0200 Subject: [PATCH 3/6] Address feedback --- .../common/config.ts | 10 ++++--- .../common/users/create_users_and_roles.ts | 2 ++ .../common/users/users.ts | 13 +++++----- .../tests/complete/complete.spec.ts | 12 ++++----- .../tests/connectors/connectors.spec.ts | 6 ++--- .../tests/conversations/conversations.spec.ts | 26 +++++++++---------- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 6bd2d049ed2bc..809cea3345339 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -40,8 +40,9 @@ export interface CreateTest { servers: any; services: InheritedServices & { observabilityAIAssistantAPIClient: () => Promise<{ - readUser: ObservabilityAIAssistantAPIClient; - writeUser: ObservabilityAIAssistantAPIClient; + adminUser: ObservabilityAIAssistantAPIClient; + viewerUser: ObservabilityAIAssistantAPIClient; + editorUser: ObservabilityAIAssistantAPIClient; }>; }; junit: { reportName: string }; @@ -74,8 +75,9 @@ function createObservabilityAIAssistantAPIConfig({ getApmSynthtraceEsClient(context, apmSynthtraceKibanaClient), observabilityAIAssistantAPIClient: async () => { return { - readUser: await getObservabilityAIAssistantAPIClient(kibanaServer, viewerUser.username), - writeUser: await getObservabilityAIAssistantAPIClient(kibanaServer, editorUser.username), + adminUser: await getObservabilityAIAssistantAPIClient(kibanaServer, 'elastic'), + viewerUser: await getObservabilityAIAssistantAPIClient(kibanaServer, viewerUser.username), + editorUser: await getObservabilityAIAssistantAPIClient(kibanaServer, editorUser.username), }; }, }, diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts index 59ec5cbd31492..1492fa68114a2 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/create_users_and_roles.ts @@ -11,6 +11,7 @@ import { allRoles } from './roles'; export async function createUsersAndRoles(getService: InheritedFtrProviderContext['getService']) { const security = getService('security'); + const log = getService('log'); // create roles await Promise.all( @@ -22,6 +23,7 @@ export async function createUsersAndRoles(getService: InheritedFtrProviderContex // create users await Promise.all( allUsers.map((user) => { + log.info(`Creating user: ${user.username} with roles: ${user.roles.join(', ')}`); return security.user.create(user.username, { password: user.password, roles: user.roles, diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts index 80c05f58990c0..34e677109d9e8 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts @@ -6,9 +6,14 @@ */ import { kbnTestConfig } from '@kbn/test'; - const password = kbnTestConfig.getUrlParts().password!; +export interface User { + username: string; + password: string; + roles: string[]; +} + export const editorUser: User = { username: 'editor', password, @@ -21,10 +26,4 @@ export const viewerUser: User = { roles: ['viewer'], }; -export interface User { - username: string; - password: string; - roles: string[]; -} - export const allUsers = [editorUser, viewerUser]; diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts index 38303c3a53076..01f6e8cdd7bce 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts @@ -302,7 +302,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { )[0]?.conversation.id; await observabilityAIAssistantAPIClient - .writeUser({ + .adminUser({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -378,7 +378,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ).to.eql(0); const conversations = await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }) .expect(200); @@ -422,7 +422,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .complete(); const createResponse = await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'POST /internal/observability_ai_assistant/chat/complete', params: { body: { @@ -440,7 +440,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { conversationCreatedEvent = getConversationCreatedEvent(createResponse.body); const conversationId = conversationCreatedEvent.conversation.id; - const fullConversation = await observabilityAIAssistantAPIClient.readUser({ + const fullConversation = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -454,7 +454,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .complete(); const updatedResponse = await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'POST /internal/observability_ai_assistant/chat/complete', params: { body: { @@ -484,7 +484,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts index d5e726012c869..872df2d90c9b9 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts @@ -24,14 +24,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('Returns a 2xx for enterprise license', async () => { await observabilityAIAssistantAPIClient - .readUser({ + .editorUser({ endpoint: 'GET /internal/observability_ai_assistant/connectors', }) .expect(200); }); it('returns an empty list of connectors', async () => { - const res = await observabilityAIAssistantAPIClient.readUser({ + const res = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'GET /internal/observability_ai_assistant/connectors', }); @@ -55,7 +55,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }) .expect(200); - const res = await observabilityAIAssistantAPIClient.readUser({ + const res = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'GET /internal/observability_ai_assistant/connectors', }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts index 85c9eb725d47c..91a418b3000ee 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts @@ -48,7 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('without conversations', () => { it('returns no conversations when listing', async () => { const response = await observabilityAIAssistantAPIClient - .readUser({ + .editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }) .expect(200); @@ -58,7 +58,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for updating conversations', async () => { await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -74,7 +74,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for retrieving a conversation', async () => { await observabilityAIAssistantAPIClient - .readUser({ + .editorUser({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -92,7 +92,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { >; before(async () => { createResponse = await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversation', params: { body: { @@ -105,7 +105,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -116,7 +116,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .expect(200); await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -141,14 +141,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { namespace: 'default', public: conversationCreate.public, user: { - name: 'elastic', + name: 'editor', }, }); }); it('returns a 404 for updating a non-existing conversation', async () => { await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -164,7 +164,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for retrieving a non-existing conversation', async () => { await observabilityAIAssistantAPIClient - .readUser({ + .editorUser({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -177,7 +177,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the conversation that was created', async () => { const response = await observabilityAIAssistantAPIClient - .readUser({ + .editorUser({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -192,7 +192,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the created conversation when listing', async () => { const response = await observabilityAIAssistantAPIClient - .readUser({ + .editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }) .expect(200); @@ -210,7 +210,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { updateResponse = await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { @@ -234,7 +234,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the updated conversation after get', async () => { const updateAfterCreateResponse = await observabilityAIAssistantAPIClient - .writeUser({ + .editorUser({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { From c75f8a4cca6cb7f6c5327be86420b983a8a90c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 21 May 2024 14:57:42 +0200 Subject: [PATCH 4/6] Fix functional tests --- .../observability_ai_assistant_api_integration/common/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 809cea3345339..536bc54387f14 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -50,7 +50,7 @@ export interface CreateTest { kbnTestServer: any; } -function createObservabilityAIAssistantAPIConfig({ +export function createObservabilityAIAssistantAPIConfig({ config, license, name, From 025020d87160d28210c33756dac845da848dc578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 21 May 2024 22:25:24 +0200 Subject: [PATCH 5/6] Remove custom test_user role --- .../common/config.ts | 23 +++------- .../observability_ai_assistant_api_client.ts | 14 +++++- .../common/users/users.ts | 2 +- .../common/config.ts | 34 +++++++------- .../common/ui/index.ts | 45 +++++-------------- .../tests/conversations/index.spec.ts | 24 +++++----- .../tests/index.ts | 6 +++ 7 files changed, 65 insertions(+), 83 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 536bc54387f14..559bb5d65dd2a 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { Config, FtrConfigProviderContext, kbnTestConfig } from '@kbn/test'; -import supertest from 'supertest'; -import { format, UrlObject } from 'url'; +import { Config, FtrConfigProviderContext } from '@kbn/test'; +import { UrlObject } from 'url'; import { ObservabilityAIAssistantFtrConfigName } from '../configs'; import { getApmSynthtraceEsClient } from './create_synthtrace_client'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; import { - createObservabilityAIAssistantApiClient, + getScopedApiClient, ObservabilityAIAssistantAPIClient, } from './observability_ai_assistant_api_client'; import { editorUser, viewerUser } from './users/users'; @@ -23,16 +22,6 @@ export interface ObservabilityAIAssistantFtrConfig { kibanaConfig?: Record; } -async function getObservabilityAIAssistantAPIClient(kibanaServer: UrlObject, username: string) { - const { password } = kbnTestConfig.getUrlParts(); - const baseUrlWithAuth = format({ - ...kibanaServer, - auth: `${username}:${password}`, - }); - - return createObservabilityAIAssistantApiClient(supertest(baseUrlWithAuth)); -} - export type CreateTestConfig = ReturnType; export interface CreateTest { @@ -75,9 +64,9 @@ export function createObservabilityAIAssistantAPIConfig({ getApmSynthtraceEsClient(context, apmSynthtraceKibanaClient), observabilityAIAssistantAPIClient: async () => { return { - adminUser: await getObservabilityAIAssistantAPIClient(kibanaServer, 'elastic'), - viewerUser: await getObservabilityAIAssistantAPIClient(kibanaServer, viewerUser.username), - editorUser: await getObservabilityAIAssistantAPIClient(kibanaServer, editorUser.username), + adminUser: await getScopedApiClient(kibanaServer, 'elastic'), + viewerUser: await getScopedApiClient(kibanaServer, viewerUser.username), + editorUser: await getScopedApiClient(kibanaServer, editorUser.username), }; }, }, diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/observability_ai_assistant_api_client.ts b/x-pack/test/observability_ai_assistant_api_integration/common/observability_ai_assistant_api_client.ts index 865620a2d028a..005815b38057a 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/observability_ai_assistant_api_client.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/observability_ai_assistant_api_client.ts @@ -12,8 +12,20 @@ import type { } from '@kbn/observability-ai-assistant-plugin/public'; import { formatRequest } from '@kbn/server-route-repository'; import supertest from 'supertest'; -import { format } from 'url'; import { Subtract } from 'utility-types'; +import { format, UrlObject } from 'url'; +import { kbnTestConfig } from '@kbn/test'; +import { User } from './users/users'; + +export async function getScopedApiClient(kibanaServer: UrlObject, username: User['username']) { + const { password } = kbnTestConfig.getUrlParts(); + const baseUrlWithAuth = format({ + ...kibanaServer, + auth: `${username}:${password}`, + }); + + return createObservabilityAIAssistantApiClient(supertest(baseUrlWithAuth)); +} export function createObservabilityAIAssistantApiClient(st: supertest.Agent) { return ( diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts index 34e677109d9e8..b6fa38e52e60b 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts @@ -9,7 +9,7 @@ import { kbnTestConfig } from '@kbn/test'; const password = kbnTestConfig.getUrlParts().password!; export interface User { - username: string; + username: 'elastic' | 'editor' | 'viewer'; password: string; roles: string[]; } diff --git a/x-pack/test/observability_ai_assistant_functional/common/config.ts b/x-pack/test/observability_ai_assistant_functional/common/config.ts index 35a12f10861c4..e92bf3729cb40 100644 --- a/x-pack/test/observability_ai_assistant_functional/common/config.ts +++ b/x-pack/test/observability_ai_assistant_functional/common/config.ts @@ -7,10 +7,13 @@ import { FtrConfigProviderContext } from '@kbn/test'; import { merge } from 'lodash'; -import supertest from 'supertest'; -import { format, UrlObject } from 'url'; +import { UrlObject } from 'url'; import type { EBTHelpersContract } from '@kbn/analytics-ftr-helpers-plugin/common/types'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { + editorUser, + viewerUser, +} from '../../observability_ai_assistant_api_integration/common/users/users'; import { KibanaEBTServerProvider, KibanaEBTUIProvider, @@ -21,7 +24,7 @@ import { createObservabilityAIAssistantAPIConfig, } from '../../observability_ai_assistant_api_integration/common/config'; import { - createObservabilityAIAssistantApiClient, + getScopedApiClient, ObservabilityAIAssistantAPIClient, } from '../../observability_ai_assistant_api_integration/common/observability_ai_assistant_api_client'; import { InheritedFtrProviderContext, InheritedServices } from '../ftr_provider_context'; @@ -33,11 +36,11 @@ export interface TestConfig extends CreateTestAPI { observabilityAIAssistantUI: ( context: InheritedFtrProviderContext ) => Promise; - observabilityAIAssistantAPIClient: () => Promise< - Awaited> & { - testUser: ObservabilityAIAssistantAPIClient; - } - >; + observabilityAIAssistantAPIClient: () => Promise<{ + adminUser: ObservabilityAIAssistantAPIClient; + viewerUser: ObservabilityAIAssistantAPIClient; + editorUser: ObservabilityAIAssistantAPIClient; + }>; kibana_ebt_server: (context: InheritedFtrProviderContext) => EBTHelpersContract; kibana_ebt_ui: (context: InheritedFtrProviderContext) => EBTHelpersContract; apmSynthtraceEsClient: ( @@ -63,6 +66,8 @@ export function createTestConfig( kibanaConfig, }); + const kibanaServer = baseConfig.servers.kibana as UrlObject; + return merge( { services: testConfig.get('services'), @@ -74,17 +79,10 @@ export function createTestConfig( observabilityAIAssistantUI: (context: InheritedFtrProviderContext) => ObservabilityAIAssistantUIProvider(context), observabilityAIAssistantAPIClient: async (context: InheritedFtrProviderContext) => { - const otherUsers = await baseConfig.services.observabilityAIAssistantAPIClient(); return { - ...otherUsers, - testUser: createObservabilityAIAssistantApiClient( - supertest( - format({ - ...(baseConfig.servers.kibana as UrlObject), - auth: `test_user:changeme`, - }) - ) - ), + adminUser: await getScopedApiClient(kibanaServer, 'elastic'), + viewerUser: await getScopedApiClient(kibanaServer, viewerUser.username), + editorUser: await getScopedApiClient(kibanaServer, editorUser.username), }; }, kibana_ebt_server: KibanaEBTServerProvider, diff --git a/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts b/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts index 17fd5f89c7d17..b7234648c8464 100644 --- a/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts +++ b/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts @@ -6,17 +6,16 @@ */ import type { PathsOf, TypeAsArgs, TypeOf } from '@kbn/typed-react-router-config'; +import { kbnTestConfig } from '@kbn/test'; import type { ObservabilityAIAssistantRoutes } from '@kbn/observability-ai-assistant-app-plugin/public/routes/config'; import qs from 'query-string'; -import type { Role } from '@kbn/security-plugin-types-common'; -import { OBSERVABILITY_AI_ASSISTANT_FEATURE_ID } from '@kbn/observability-ai-assistant-plugin/common/feature'; -import { APM_SERVER_FEATURE_ID } from '@kbn/apm-plugin/server'; +import { User } from '../../../observability_ai_assistant_api_integration/common/users/users'; import type { InheritedFtrProviderContext } from '../../ftr_provider_context'; export interface ObservabilityAIAssistantUIService { pages: typeof pages; auth: { - login: () => Promise; + login: (username: User['username']) => Promise; logout: () => Promise; }; router: { @@ -54,42 +53,20 @@ export async function ObservabilityAIAssistantUIProvider({ getPageObjects, getService, }: InheritedFtrProviderContext): Promise { - const browser = getService('browser'); - const deployment = getService('deployment'); - const security = getService('security'); - const pageObjects = getPageObjects(['common']); - - const roleDefinition: Role = { - name: 'observability-ai-assistant-functional-test-role', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [ - { - spaces: ['*'], - base: [], - feature: { - actions: ['all'], - [APM_SERVER_FEATURE_ID]: ['all'], - [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID]: ['all'], - }, - }, - ], - }; + const pageObjects = getPageObjects(['common', 'security']); return { pages, auth: { - login: async () => { - await browser.navigateTo(deployment.getHostPort()); - await security.role.create(roleDefinition.name, roleDefinition); - await security.testUser.setRoles([roleDefinition.name, 'apm_user', 'viewer']); // performs a page reload + login: async (username: string) => { + const { password } = kbnTestConfig.getUrlParts(); + + await pageObjects.security.login(username, password, { + expectSpaceSelector: false, + }); }, logout: async () => { - await security.role.delete(roleDefinition.name); - await security.testUser.restoreDefaults(); + await pageObjects.security.forceLogout(); }, }, router: { diff --git a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts index 670903591287f..fbe487e1987e7 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts @@ -31,17 +31,17 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte const toasts = getService('toasts'); - const { header } = getPageObjects(['header', 'common']); + const { header, security } = getPageObjects(['header', 'security']); const flyoutService = getService('flyout'); async function deleteConversations() { - const response = await observabilityAIAssistantAPIClient.testUser({ + const response = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }); for (const conversation of response.body.conversations) { - await observabilityAIAssistantAPIClient.testUser({ + await observabilityAIAssistantAPIClient.editorUser({ endpoint: `DELETE /internal/observability_ai_assistant/conversation/{conversationId}`, params: { path: { @@ -53,7 +53,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte } async function deleteConnectors() { - const response = await observabilityAIAssistantAPIClient.testUser({ + const response = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'GET /internal/observability_ai_assistant/connectors', }); @@ -66,7 +66,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte } async function createOldConversation() { - await observabilityAIAssistantAPIClient.testUser({ + await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversation', params: { body: { @@ -150,7 +150,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte proxy = await createLlmProxy(log); - await ui.auth.login(); + await ui.auth.login('editor'); await ui.router.goto('/conversations/new', { path: {}, query: {} }); }); @@ -204,7 +204,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte }); it('creates a connector', async () => { - const response = await observabilityAIAssistantAPIClient.testUser({ + const response = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'GET /internal/observability_ai_assistant/connectors', }); @@ -264,7 +264,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte }); it('creates a conversation and updates the URL', async () => { - const response = await observabilityAIAssistantAPIClient.testUser({ + const response = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }); @@ -331,7 +331,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte }); it('does not create another conversation', async () => { - const response = await observabilityAIAssistantAPIClient.testUser({ + const response = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }); @@ -339,7 +339,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte }); it('appends to the existing one', async () => { - const response = await observabilityAIAssistantAPIClient.testUser({ + const response = await observabilityAIAssistantAPIClient.editorUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }); @@ -398,7 +398,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte expect(conversation.conversation.title).to.eql('My title'); expect(conversation.namespace).to.eql('default'); expect(conversation.public).to.eql(false); - expect(conversation.user?.name).to.eql('test_user'); + expect(conversation.user?.name).to.eql('editor'); const { messages } = conversation; @@ -475,7 +475,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte expect(conversation.conversation.title).to.eql('My old conversation'); expect(conversation.namespace).to.eql('default'); expect(conversation.public).to.eql(false); - expect(conversation.user?.name).to.eql('test_user'); + expect(conversation.user?.name).to.eql('editor'); const { messages } = conversation; diff --git a/x-pack/test/observability_ai_assistant_functional/tests/index.ts b/x-pack/test/observability_ai_assistant_functional/tests/index.ts index 9a2e4902d7366..07e81d9488592 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/index.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/index.ts @@ -7,6 +7,7 @@ import globby from 'globby'; import path from 'path'; +import { createUsersAndRoles } from '../../observability_ai_assistant_api_integration/common/users/create_users_and_roles'; import { FtrProviderContext } from '../../observability_ai_assistant_api_integration/common/ftr_provider_context'; const cwd = path.join(__dirname); @@ -19,6 +20,11 @@ export default function observabilityAIAssistantFunctionalTests({ const filePattern = '**/*.spec.ts'; const tests = globby.sync(filePattern, { cwd }); + // Creates roles and users before running tests + before(async () => { + await createUsersAndRoles(getService); + }); + tests.forEach((testName) => { describe(testName, () => { loadTestFile(require.resolve(`./${testName}`)); From ff6d1e17fb4aff31b29f01f37d9a3f459d44a7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 22 May 2024 07:14:18 +0200 Subject: [PATCH 6/6] Fix linting issues --- .../tests/contextual_insights/index.spec.ts | 2 +- .../tests/conversations/index.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts index b1edc25053cc8..eb1056bbcd04b 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts @@ -101,7 +101,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte await Promise.all([ createSynthtraceErrors(), // create synthtrace - ui.auth.login(), // login + ui.auth.login('editor'), // login ]); }); diff --git a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts index fbe487e1987e7..b7c33db0a4122 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts @@ -31,7 +31,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte const toasts = getService('toasts'); - const { header, security } = getPageObjects(['header', 'security']); + const { header } = getPageObjects(['header', 'security']); const flyoutService = getService('flyout');