Skip to content

Commit 23c9be4

Browse files
[scout] migrate FTR Core api tests (elastic#263211)
## Summary This PR migrates FTR Core API tests to Scout Scout spec | FTR source(s) | Tests | Tags | Auth | Role | Notable changes -- | -- | -- | -- | -- | -- | -- compression.spec.ts | /core/compression.ts | 2 | deploymentAgnostic | API key | viewer | Combined two near-identical FTR files (serverless + DA) into one spec translations.spec.ts | /core/translations.ts | 2 | deploymentAgnostic | API key | viewer | cache-control/etag assertions gated behind process.env.CI (only valid for distributable builds) capabilities.spec.ts | /core/capabilities.ts | 1 | deploymentAgnostic | API key | viewer | Combined serverless + stateful FTR sources blocked_saved_objects_api.spec.ts | /core/saved_objects.ts | 10 | serverless (search, oblt, security, workplaceai) | API key | viewer | Uses COMMON_HEADERS (no x-elastic-internal-origin) to trigger route-blocking middleware ui_settings_validate.spec.ts | /core/ui_settings.ts (validate section) | 4 | serverless (search, oblt, security, workplaceai) | Cookie (samlAuth) | viewer | Split from nested FTR ui_settings.ts into flat spec ui_settings_crud.spec.ts | /core/ui_settings.ts (CRUD section) | 6 | serverless (search, oblt, security, workplaceai) | Cookie (samlAuth) | admin | Split from nested FTR ui_settings.ts; added afterAll cleanup missing in FTR <img width="1414" height="314" alt="Screenshot 2026-04-16 at 08 20 39" src="https://github.com/user-attachments/assets/7c222285-8b30-48ee-9590-974ba46e48d3" /> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 9ede2db commit 23c9be4

32 files changed

Lines changed: 487 additions & 559 deletions

.buildkite/scout_ci_config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ plugins:
5454

5555
packages:
5656
enabled:
57+
- core
5758
- kbn-scout
5859
- kbn-streamlang-tests
5960
disabled:

src/core/moon.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ dependsOn:
180180
- '@kbn/migrator-test-kit'
181181
- '@kbn/core-user-activity-server-mocks'
182182
- '@kbn/core-user-activity-server-internal'
183+
- '@kbn/scout'
183184
tags:
184185
- core
185186
- package
@@ -192,6 +193,7 @@ fileGroups:
192193
- server/**/*
193194
- types/**/*
194195
- test_helpers/**/*
196+
- test/scout/**/*
195197
- utils/**/*
196198
- index.ts
197199
- '!target/**/*'

src/platform/test/api_integration/apis/core/index.ts renamed to src/core/test/scout/api/fixtures/constants.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import type { FtrProviderContext } from '../../ftr_provider_context';
10+
export const COMMON_HEADERS = {
11+
'kbn-xsrf': 'some-xsrf-token',
12+
} as const;
1113

12-
export default function ({ loadTestFile }: FtrProviderContext) {
13-
describe('core', () => {
14-
loadTestFile(require.resolve('./translations'));
15-
loadTestFile(require.resolve('./capabilities'));
16-
});
17-
}
14+
export const INTERNAL_HEADERS = {
15+
...COMMON_HEADERS,
16+
'x-elastic-internal-origin': 'kibana',
17+
} as const;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
export { COMMON_HEADERS, INTERNAL_HEADERS } from './constants';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { createPlaywrightConfig } from '@kbn/scout';
11+
12+
export default createPlaywrightConfig({
13+
testDir: './tests',
14+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { expect } from '@kbn/scout/api';
11+
import { apiTest, tags } from '@kbn/scout';
12+
import type { RoleApiCredentials } from '@kbn/scout';
13+
import { COMMON_HEADERS } from '../fixtures';
14+
15+
const SERVERLESS_TAGS = [
16+
...tags.serverless.search,
17+
...tags.serverless.observability.complete,
18+
...tags.serverless.security.complete,
19+
...tags.serverless.workplaceai,
20+
];
21+
22+
const BLOCKED_APIS: Array<{ path: string; method: 'get' | 'post' | 'put' | 'delete' }> = [
23+
{ path: '/api/saved_objects/_bulk_create', method: 'post' },
24+
{ path: '/api/saved_objects/_bulk_delete', method: 'post' },
25+
{ path: '/api/saved_objects/_bulk_get', method: 'post' },
26+
{ path: '/api/saved_objects/_bulk_resolve', method: 'post' },
27+
{ path: '/api/saved_objects/_bulk_update', method: 'post' },
28+
{ path: '/api/saved_objects/test/id', method: 'get' },
29+
{ path: '/api/saved_objects/test/id', method: 'post' },
30+
{ path: '/api/saved_objects/test/id', method: 'delete' },
31+
{ path: '/api/saved_objects/_find', method: 'get' },
32+
{ path: '/api/saved_objects/test/id', method: 'put' },
33+
];
34+
35+
apiTest.describe('blocked internal saved objects API', { tag: SERVERLESS_TAGS }, () => {
36+
let credentials: RoleApiCredentials;
37+
38+
apiTest.beforeAll(async ({ requestAuth }) => {
39+
credentials = await requestAuth.getApiKey('viewer');
40+
});
41+
42+
for (const { path, method } of BLOCKED_APIS) {
43+
apiTest(`${method} ${path} returns 400`, async ({ apiClient }) => {
44+
const response = await apiClient[method](path, {
45+
headers: {
46+
...COMMON_HEADERS,
47+
...credentials.apiKeyHeader,
48+
},
49+
});
50+
51+
expect(response).toHaveStatusCode(400);
52+
expect(response.body).toStrictEqual({
53+
statusCode: 400,
54+
error: 'Bad Request',
55+
message: `uri [${path}] with method [${method}] exists but is not available with the current configuration`,
56+
});
57+
});
58+
}
59+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { expect } from '@kbn/scout/api';
11+
import { apiTest, tags } from '@kbn/scout';
12+
import type { RoleApiCredentials } from '@kbn/scout';
13+
import { INTERNAL_HEADERS } from '../fixtures';
14+
15+
apiTest.describe('capabilities', { tag: tags.deploymentAgnostic }, () => {
16+
let credentials: RoleApiCredentials;
17+
18+
apiTest.beforeAll(async ({ requestAuth }) => {
19+
credentials = await requestAuth.getApiKey('viewer');
20+
});
21+
22+
apiTest('returns a 400 when an invalid app id is provided', async ({ apiClient }) => {
23+
const response = await apiClient.post('/api/core/capabilities', {
24+
headers: {
25+
...INTERNAL_HEADERS,
26+
...credentials.apiKeyHeader,
27+
},
28+
body: {
29+
applications: ['dashboard', 'discover', 'bad%app'],
30+
},
31+
});
32+
33+
expect(response).toHaveStatusCode(400);
34+
expect(response.body).toStrictEqual({
35+
statusCode: 400,
36+
error: 'Bad Request',
37+
message: '[request body.applications.2]: Invalid application id: bad%app',
38+
});
39+
});
40+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { expect } from '@kbn/scout/api';
11+
import { apiTest, tags } from '@kbn/scout';
12+
import type { RoleApiCredentials } from '@kbn/scout';
13+
14+
apiTest.describe('compression', { tag: tags.deploymentAgnostic }, () => {
15+
let credentials: RoleApiCredentials;
16+
17+
apiTest.beforeAll(async ({ requestAuth }) => {
18+
credentials = await requestAuth.getApiKey('viewer');
19+
});
20+
21+
apiTest('uses compression when there is no referer', async ({ apiClient }) => {
22+
const response = await apiClient.get('/app/kibana', {
23+
headers: {
24+
'accept-encoding': 'gzip',
25+
...credentials.apiKeyHeader,
26+
},
27+
});
28+
29+
expect(response).toHaveHeaders({ 'content-encoding': 'gzip' });
30+
});
31+
32+
apiTest('uses compression when there is a whitelisted referer', async ({ apiClient }) => {
33+
const response = await apiClient.get('/app/kibana', {
34+
headers: {
35+
'accept-encoding': 'gzip',
36+
referer: 'https://some-host.com',
37+
...credentials.apiKeyHeader,
38+
},
39+
});
40+
41+
expect(response).toHaveHeaders({ 'content-encoding': 'gzip' });
42+
});
43+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { expect } from '@kbn/scout/api';
11+
import { apiTest, tags } from '@kbn/scout';
12+
import type { RoleApiCredentials } from '@kbn/scout';
13+
import { INTERNAL_HEADERS } from '../fixtures';
14+
15+
apiTest.describe('translations', { tag: tags.deploymentAgnostic }, () => {
16+
let credentials: RoleApiCredentials;
17+
18+
apiTest.beforeAll(async ({ requestAuth }) => {
19+
credentials = await requestAuth.getApiKey('viewer');
20+
});
21+
22+
apiTest('returns the translations with the correct headers', async ({ apiClient }) => {
23+
const response = await apiClient.get('/translations/en.json', {
24+
headers: {
25+
...INTERNAL_HEADERS,
26+
...credentials.apiKeyHeader,
27+
},
28+
});
29+
30+
expect(response).toHaveStatusCode(200);
31+
expect(response.body.locale).toBe('en');
32+
expect(response).toHaveHeaders({ 'content-type': 'application/json; charset=utf-8' });
33+
34+
// Distributable builds serve pre-built translations with immutable caching and no etag.
35+
// Local dev servers return `must-revalidate` + etag instead, so we gate on CI.
36+
// TODO: Replace `process.env.CI` with a Scout config flag (e.g. isDistributable) when available.
37+
if (process.env.CI) {
38+
expect(response).toHaveHeaders({
39+
'cache-control': 'public, max-age=31536000, immutable',
40+
});
41+
expect(response.headers.etag).toBeUndefined();
42+
}
43+
});
44+
45+
apiTest('returns a 404 when not using the correct locale', async ({ apiClient }) => {
46+
const response = await apiClient.get('/translations/foo.json', {
47+
headers: {
48+
...INTERNAL_HEADERS,
49+
...credentials.apiKeyHeader,
50+
},
51+
});
52+
53+
expect(response).toHaveStatusCode(404);
54+
});
55+
});

0 commit comments

Comments
 (0)