-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Observability AI Assistant Tests Deployment Agnostic #205194
base: main
Are you sure you want to change the base?
Observability AI Assistant Tests Deployment Agnostic #205194
Conversation
Pinging @elastic/obs-ai-assistant (Team:Obs AI Assistant) |
🤖 GitHub commentsExpand to view the GitHub comments
Just comment with:
|
|
||
it('Returns a 2xx for enterprise license', async () => { | ||
const { status } = await observabilityAIAssistantAPIClient.editor({ | ||
endpoint: `GET ${CONNECTOR_API_URL}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I suggest not using variables here. It's harder to read and search for. Also, I'm not sure the typescript intellisense for parameters and typed responses works when using variables
endpoint: `GET ${CONNECTOR_API_URL}`, | |
endpoint: `GET /internal/observability_ai_assistant/connectors`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to make it DRY I suggest adding a helper and re-use that instead:
const getConnectors = () =>
observabilityAIAssistantAPIClient.editor({
endpoint: `GET /internal/observability_ai_assistant/connectors`,
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Now that I’m moving many of these tests, I won’t use a variable and will ensure they follow a consistent pattern since we currently have a mix.
} | ||
} | ||
|
||
async function createProxyActionConnector({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a shared helper for creating the proxy action connector? It's already defined at least 3 times:
kibana/x-pack/test/observability_ai_assistant_api_integration/common/action_connectors.ts
Line 31 in c4cf9fe
export async function createProxyActionConnector({ |
Line 40 in c4cf9fe
export async function createProxyActionConnector({ |
export async function createConnector(proxy: LlmProxy, supertest: SuperTestAgent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will keep only one, I will get rid of the duplicate functions.
await observabilityAIAssistantAPIClient | ||
.slsEditor({ | ||
try { | ||
await observabilityAIAssistantAPIClient.editor({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels good getting rid of the serverless-specific client. Is the plan to get rid of x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts
entirely?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove it entirely if we get all the tests running in deployment agnostic, unless there is some case where we want a serverless only test because of some serverless only feature. Probably wouldn't hurt to leave it.
...tion/deployment_agnostic/apis/observability/ai_assistant/conversations/conversations.spec.ts
Outdated
Show resolved
Hide resolved
@@ -46,212 +46,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { | |||
}); | |||
|
|||
describe('Conversations', () => { | |||
describe('without conversations', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file should be safe to move over to deployment agnostic and not exist in stateful tests. I'm assuming you decided not to move it was because of the security roles and access privileges
test suite which doesn't exist in serverless, but it soon will: #205210
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly. I only moved the common tests between stateful and serverless. I’ll move the security roles and access privaleges
to deployment-agnostic and remove it from stateful and serverless.
@@ -8,13 +8,11 @@ | |||
import expect from '@kbn/expect'; | |||
import type { Agent as SuperTestAgent } from 'supertest'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1264,7 +1264,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql | |||
/x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant | |||
/x-pack/test_serverless/**/test_suites/observability/ai_assistant @elastic/obs-ai-assistant | |||
/x-pack/test/functional/es_archives/observability/ai_assistant @elastic/obs-ai-assistant | |||
|
|||
/x-pack/test/api_integration/deployment_agnostic/apis/observability/ai_assistant @elastic/obs-ai-assistant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing that out, @sorenlouv. I checked, and the file is now valid in this branch as well. It might have been a temporary issue or something resolved during recent changes(I did a rebase recently).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one comment about CODEOWNERS file
c1c4339
to
1dc1987
Compare
…o deployment-agnostic
…eployment-agnostic
…ostic tests and remove unused supertest import from chat.spec.ts
throw new ForbiddenApiError( | ||
'Expected unauthorizedUser() to throw a 403 Forbidden error' | ||
); | ||
} catch (e) { | ||
expect(e.status).to.be(403); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right. By manually throwing ForbiddenApiError
you are ensuring that the test will pass even if observabilityAIAssistantAPIClient.unauthorizedUser(..)
does not throw (eg if it receives a 200).
Meaning, this test will pass if an unauthorized user is able to successfully call the given endpoint. Is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sorenlouv TBH, in the first iteration, I just moved the serverless/stateful test to the deployment-agnostic test and made them work without rewriting the test implementation. Later, I also noticed the same issue, that manually throwing ForbiddenApiError
undermines the purpose of validating that observabilityAIAssistantAPIClient.unauthorizedUser(..)
behaves as expected.
I’ve already addressed this in the latest commit of the file in the PR. Let me know if you have any further feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was updated this way based on this comment - #202817 (comment)
cc: @arturoliduena @sorenlouv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was updated this way based on this comment - #202817 (comment)
@viduni94 What I suggested was to throw a generic error (that did not have a status=403
) and would thus not pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah got it. Throwing a generic error would always fail the test because we are expecting the status to be 403.
We can remove the try catch block and directly check for expect(status).to.be(403);
in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove the try catch block and directly expect(status).to.be(403); in that case.
I can't recall if observabilityAIAssistantAPIClient
throws on 403. If it doesn't, we can remove the try/catch as you suggest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sorenlouv and @viduni94.
I’ve already addressed by removing the manual throw and the try catch block. Now, it directly calls observabilityAIAssistantAPIClient.unauthorizedUser(...) and verifies the status like this:
const { status } = await observabilityAIAssistantAPIClient.unauthorizedUser({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
conversationId: createResponse.body.conversation.id,
},
},
});
expect(status).to.be(403);
This ensures the test properly validates the behavior without relying on manual errors, I changed it in all the test I already migrated to deployment agnostic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better! Thanks!
@@ -201,6 +201,7 @@ export function ObservabilityAIAssistantApiProvider(context: DeploymentAgnosticF | |||
admin: observabilityAIAssistantApiClient.makeInternalApiRequest('admin'), | |||
viewer: observabilityAIAssistantApiClient.makeInternalApiRequest('viewer'), | |||
editor: observabilityAIAssistantApiClient.makeInternalApiRequest('editor'), | |||
unauthorizedUser: observabilityAIAssistantApiClient.makeInternalApiRequest('viewer'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we simply use the existing viewer
? Also, what do you expect to happen if unauthorizedUser
is used to call an endpoint that viewer
is allowed to access?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @arturoliduena
You might not be able to use the viewer
role for unauthorizedUser
in this manner for DA tests. It was done for serverless this way, because we force the user to be unauthorized by not passing the cookie header.
However, since the cookie is always included in makeInternalApiRequest
, this user will be a viewer with authorization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @sorenlouv and @viduni94,
We can simulate an unauthorized user by omitting credentials entirely with a function like makeInternalApiRequestWithoutCredentials
:
function makeInternalApiRequest(role: string) {
return async <TEndpoint extends InternalEndpoint<APIEndpoint>>(
options: Options<TEndpoint>
): Promise<SupertestReturnType<TEndpoint>> => {
const headers: Record<string, string> = {
...samlAuth.getInternalRequestHeader(),
...(await samlAuth.getM2MApiCookieCredentialsWithRoleScope(role)),
};
return makeApiRequest({
options,
headers,
});
};
}
function makeInternalApiRequestWithoutCredentials() {
return async <TEndpoint extends InternalEndpoint<APIEndpoint>>(
options: Options<TEndpoint>
): Promise<SupertestReturnType<TEndpoint>> => {
const headers: Record<string, string> = {
...samlAuth.getInternalRequestHeader(),
};
return makeApiRequest({
options,
headers,
});
};
}
However, the problem with this approach is that omitting credentials will result in a 401 Unauthorized
response instead of a 403 Forbidden
. This doesn’t align with the intention of the test, which is to verify whether a user with insufficient permissions can access specific endpoints (DELETE, POST, PUT).
To properly simulate this, the user needs to be authenticated (avoiding 401 Unauthorized
) but lack the necessary permissions, resulting in a 403 Forbidden
. This is why I think it makes sense to use the viewer
role here.
I agree with Søren that adding an additional unauthorizedUser role like this:
unauthorizedUser: observabilityAIAssistantApiClient.makeInternalApiRequest('viewer')
is unnecessary. Instead, we can simply use the existing viewer
role:
viewer: observabilityAIAssistantApiClient.makeInternalApiRequest('viewer')
This keeps the implementation clean and ensures that we’re testing for insufficient permissions correctly. Let me know if you agree
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read that correctly, you'll authenticate as the viewer
user. That sounds good to me.
|
||
type PublicEndpoint<T extends APIEndpoint> = T extends `${string} /api/${string}` ? T : never; | ||
|
||
function createObservabilityAIAssistantApiClient({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fyi: @dgieselaar is extracting the API client in https://github.com/elastic/kibana/pull/204309/files#diff-cb59199a241f60514e9ba52c57b587929f6be6bcedb24c5a138d5ba72184fe85.
I don't think this is a blocker but eventually we should use that implementation so we don't have multiple. Afaict we now have three API clients:
- https://github.com/elastic/kibana/blob/main/x-pack/test/observability_ai_assistant_api_integration/common/observability_ai_assistant_api_client.ts
- https://github.com/elastic/kibana/blob/main/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts
💚 Build Succeeded
Metrics [docs]
History
|
Closes #192718
Summary
This PR add a deployment-agnostic testing environment for Observability AI Assistant tests by unifying the duplicated tests for stateful and serverless environments. It create the ObservabilityAIAssistantApiClient to work seamlessly in both environments, enabling a single test to run across stateful, CI, and MKI.
Initial efforts focus on deduplicating the
conversations.spec.ts
andconnectors.spec.ts
files, as these already run in all environments.Move / dedup the tests that exist in stateful and serverless. They run in serverless CI but not MKI and add the skipMki tag.
chat.spec.ts
complete.spec.ts
elasticsearch.spec.ts
public_complete.spec.ts
alerts.spec.ts
Checklist
Check the PR satisfies following conditions.
Reviewers should verify this PR satisfies this list as well.
release_note:breaking
label should be applied in these situations.release_note:*
label is applied per the guidelines