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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -1697,7 +1697,6 @@ x-pack/solutions/observability/plugins/observability/server/lib/esql_extensions
/x-pack/solutions/observability/test/serverless/functional/test_suites/dataset_quality @elastic/obs-onboarding-team
/x-pack/solutions/observability/plugins/observability/public/pages/landing @elastic/obs-onboarding-team
/x-pack/solutions/observability/plugins/observability/test/scout/ui/tests/landing.spec.ts @elastic/obs-onboarding-team
/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/onboarding/ @elastic/obs-onboarding-team

# Observability-ui
/x-pack/solutions/observability/test/serverless/api_integration/configs/index.ts @elastic/observability-ui
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ dependsOn:
- '@kbn/apm-plugin'
- '@kbn/react-kibana-mount'
- '@kbn/react-query'
- '@kbn/scout-oblt'
- '@kbn/scout-synthtrace'
- '@kbn/synthtrace-client'
tags:
- plugin
- prod
Expand All @@ -65,6 +68,7 @@ fileGroups:
- public/**/*.json
- server/**/*
- e2e/**/*
- test/scout/**/*
- '!target/**/*'
tasks:
jest:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ node scripts/scout.js start-server --arch serverless --domain [search|observabil

Then you can run the tests in another terminal:

### API tests (sequential)

```bash
// ESS
npx playwright test --config x-pack/solutions/observability/plugins/observability_onboarding/test/scout/api/playwright.config.ts --project=local --grep stateful-classic

// Serverless
npx playwright test --config x-pack/solutions/observability/plugins/observability_onboarding/test/scout/api/playwright.config.ts --project=local --grep serverless-observability_complete
```

### UI tests

Some tests are designed to run sequentially:

```bash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.
*/

export const ONBOARDING_COMMON_HEADERS = {
'kbn-xsrf': 'scout',
'x-elastic-internal-origin': 'kibana',
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { apiTest as baseApiTest, mergeTests } from '@kbn/scout-oblt';
import { synthtraceFixture } from '@kbn/scout-synthtrace';

export const apiTest = mergeTests(baseApiTest, synthtraceFixture);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { createPlaywrightConfig } from '@kbn/scout-oblt';

export default createPlaywrightConfig({
testDir: './tests',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* 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 { log, timerange } from '@kbn/synthtrace-client';
import { expect } from '@kbn/scout-oblt/api';
import { tags } from '@kbn/scout-oblt';
import type { KibanaRole } from '@kbn/scout-oblt';
import { ONBOARDING_COMMON_HEADERS } from '../fixtures/constants';
import { apiTest } from '../fixtures';

const NO_ACCESS_USER_ROLE: KibanaRole = {
elasticsearch: {
cluster: [],
indices: [],
},
kibana: [],
};

apiTest.describe(
'Observability onboarding GET flow progress',
{ tag: [...tags.stateful.classic, ...tags.serverless.observability.complete] },
() => {
const datasetName = 'api-tests';
const namespace = 'default';
const agentId = 'my-agent-id';

/** Cookie session: create flow runs `authc.apiKeys.create`, which fails for API-key-authenticated requests. */
let adminInteractiveHeaders: Record<string, string>;
let onboardingId: string;

apiTest.beforeAll(async ({ apiClient, samlAuth }) => {
const { cookieHeader } = await samlAuth.asInteractiveUser('admin');
adminInteractiveHeaders = {
...ONBOARDING_COMMON_HEADERS,
...cookieHeader,
};
const createFlowResponse = await apiClient.post('internal/observability_onboarding/flow', {
headers: adminInteractiveHeaders,
responseType: 'json',
});
expect(createFlowResponse).toHaveStatusCode(200);
onboardingId = (createFlowResponse.body as { onboardingFlow: { id: string } }).onboardingFlow
.id;
});

apiTest('returns 404 when onboardingId does not exist', async ({ apiClient }) => {
const response = await apiClient.get(
'internal/observability_onboarding/flow/test-onboarding-id/progress',
{
headers: adminInteractiveHeaders,
responseType: 'json',
}
);
expect(response).toHaveStatusCode(404);
expect((response.body as { message?: string }).message).toContain(
'onboarding session not found'
);
});

apiTest(
'returns 404 for user without access to the onboarding session',
async ({ apiClient, samlAuth }) => {
const { cookieHeader } = await samlAuth.asInteractiveUser(NO_ACCESS_USER_ROLE);
const response = await apiClient.get(
`internal/observability_onboarding/flow/${onboardingId}/progress`,
{
headers: {
...ONBOARDING_COMMON_HEADERS,
...cookieHeader,
},
responseType: 'json',
}
);
expect(response).toHaveStatusCode(404);
expect((response.body as { message?: string }).message).toContain(
'onboarding session not found'
);
}
);

apiTest(
'logs-ingest progress after ea-status complete',
async ({ apiClient, logsSynthtraceEsClient }) => {
await apiTest.step('post ea-status complete', async () => {
const stepResponse = await apiClient.post(
`internal/observability_onboarding/flow/${onboardingId}/step/ea-status`,
{
headers: adminInteractiveHeaders,
responseType: 'json',
body: {
status: 'complete',
payload: {
agentId,
},
},
}
);
expect(stepResponse).toHaveStatusCode(200);
});

await apiTest.step('log-ingest is loading when no logs ingested', async () => {
const response = await apiClient.get(
`internal/observability_onboarding/flow/${onboardingId}/progress`,
{
headers: adminInteractiveHeaders,
responseType: 'json',
}
);
expect(response).toHaveStatusCode(200);
const progress = (response.body as { progress: Record<string, { status: string }> })
.progress;
expect(progress['logs-ingest']).toMatchObject({ status: 'loading' });
});

await apiTest.step(
'log-ingest stays loading when logs use a different agent id',
async () => {
await logsSynthtraceEsClient.index(
timerange('2023-11-20T10:00:00.000Z', '2023-11-20T10:01:00.000Z')
.interval('1m')
.rate(1)
.generator((timestamp) =>
log
.create()
.message('This is a log message')
.timestamp(timestamp)
.dataset(datasetName)
.namespace(namespace)
.service('my-service')
.defaults({
'agent.id': 'another-agent-id',
'log.file.path': '/my-service.log',
})
)
);

const response = await apiClient.get(
`internal/observability_onboarding/flow/${onboardingId}/progress`,
{
headers: adminInteractiveHeaders,
responseType: 'json',
}
);
expect(response).toHaveStatusCode(200);
const progress = (response.body as { progress: Record<string, { status: string }> })
.progress;
expect(progress['logs-ingest']).toMatchObject({ status: 'loading' });

await logsSynthtraceEsClient.clean();
}
);

await apiTest.step('log-ingest is complete when logs match expected agent id', async () => {
await logsSynthtraceEsClient.index(
timerange('2023-11-20T10:00:00.000Z', '2023-11-20T10:01:00.000Z')
.interval('1m')
.rate(1)
.generator((timestamp) =>
log
.create()
.message('This is a log message')
.timestamp(timestamp)
.dataset(datasetName)
.namespace(namespace)
.service('my-service')
.defaults({
'agent.id': agentId,
'log.file.path': '/my-service.log',
})
)
);

const response = await apiClient.get(
`internal/observability_onboarding/flow/${onboardingId}/progress`,
{
headers: adminInteractiveHeaders,
responseType: 'json',
}
);
expect(response).toHaveStatusCode(200);
const progress = (response.body as { progress: Record<string, { status: string }> })
.progress;
expect(progress['logs-ingest']).toMatchObject({ status: 'complete' });

await logsSynthtraceEsClient.clean();
});
}
);
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { expect } from '@kbn/scout-oblt/api';
import { tags } from '@kbn/scout-oblt';
import { ONBOARDING_COMMON_HEADERS } from '../fixtures/constants';
import { apiTest } from '../fixtures';

apiTest.describe(
'Observability onboarding POST flow step — missing session',
{ tag: [...tags.stateful.classic, ...tags.serverless.observability.complete] },
() => {
apiTest('returns 404 when onboardingId does not exist', async ({ apiClient, samlAuth }) => {
const { cookieHeader } = await samlAuth.asInteractiveUser('admin');
const adminHeaders = {
...ONBOARDING_COMMON_HEADERS,
...cookieHeader,
};
const response = await apiClient.post(
'internal/observability_onboarding/flow/test-onboarding-id/step/ea-download',
{
headers: {
...adminHeaders,
},
responseType: 'json',
body: {
status: 'complete',
},
}
);
expect(response).toHaveStatusCode(404);
expect((response.body as { message?: string }).message).toContain(
'onboarding session not found'
);
});
}
);
Loading
Loading