mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[scout] adding unit tests (#204567)
## Summary Adding tests and making adjustments/fixes based on the findings. Note: no integration tests were added to verify servers start as it is mostly equal to `@kbn-test` functionality that has jest integration tests. We can add it later, when Scout has specific logic. How to run: `node scripts/jest --config packages/kbn-scout/jest.config.js` Scope: ``` PASS packages/kbn-scout/src/config/config.test.ts PASS packages/kbn-scout/src/config/loader/read_config_file.test.ts PASS packages/kbn-scout/src/config/utils/get_config_file.test.ts PASS packages/kbn-scout/src/config/utils/load_servers_config.test.ts PASS packages/kbn-scout/src/config/utils/save_scout_test_config.test.ts PASS packages/kbn-scout/src/playwright/config/create_config.test.ts PASS packages/kbn-scout/src/playwright/runner/config_validator.test.ts PASS packages/kbn-scout/src/playwright/runner/flags.test.ts PASS packages/kbn-scout/src/playwright/utils/runner_utils.test.ts PASS packages/kbn-scout/src/servers/flags.test.ts ```
This commit is contained in:
parent
87d15ba2e1
commit
2ba3247526
43 changed files with 1234 additions and 227 deletions
|
@ -193,6 +193,12 @@ npx playwright test --config <plugin-path>/ui_tests/playwright.config.ts
|
|||
|
||||
We welcome contributions to improve and extend `kbn-scout`. This guide will help you get started, add new features, and align with existing project standards.
|
||||
|
||||
Make sure to run unit tests before opening the PR:
|
||||
|
||||
```bash
|
||||
node scripts/jest --config packages/kbn-scout/jest.config.js
|
||||
```
|
||||
|
||||
#### Setting Up the Environment
|
||||
|
||||
Ensure you have the latest local copy of the Kibana repository.
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import { KbnClient, createEsClientForTesting } from '@kbn/test';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { ScoutTestConfig } from '../../types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
interface ClientOptions {
|
||||
|
@ -29,7 +29,7 @@ function createClientUrlWithAuth({ serviceName, url, username, password, log }:
|
|||
return clientUrl.toString();
|
||||
}
|
||||
|
||||
export function createEsClient(config: ScoutServerConfig, log: ToolingLog) {
|
||||
export function createEsClient(config: ScoutTestConfig, log: ToolingLog) {
|
||||
const { username, password } = config.auth;
|
||||
const elasticsearchUrl = createClientUrlWithAuth({
|
||||
serviceName: 'Es',
|
||||
|
@ -45,7 +45,7 @@ export function createEsClient(config: ScoutServerConfig, log: ToolingLog) {
|
|||
});
|
||||
}
|
||||
|
||||
export function createKbnClient(config: ScoutServerConfig, log: ToolingLog) {
|
||||
export function createKbnClient(config: ScoutTestConfig, log: ToolingLog) {
|
||||
const kibanaUrl = createClientUrlWithAuth({
|
||||
serviceName: 'Kbn',
|
||||
url: config.hosts.kibana,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { ScoutTestConfig } from '../../types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
export function createScoutConfig(configDir: string, configName: string, log: ToolingLog) {
|
||||
|
@ -21,7 +21,7 @@ export function createScoutConfig(configDir: string, configName: string, log: To
|
|||
const configPath = path.join(configDir, `${configName}.json`);
|
||||
log.info(`Reading test servers confiuration from file: ${configPath}`);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as ScoutServerConfig;
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as ScoutTestConfig;
|
||||
|
||||
log.debug(serviceLoadedMsg('config'));
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { ScoutTestConfig } from '../../types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
export interface PathOptions {
|
||||
|
@ -64,7 +64,7 @@ export class KibanaUrl {
|
|||
}
|
||||
}
|
||||
|
||||
export function createKbnUrl(scoutConfig: ScoutServerConfig, log: ToolingLog) {
|
||||
export function createKbnUrl(scoutConfig: ScoutTestConfig, log: ToolingLog) {
|
||||
const kbnUrl = new KibanaUrl(new URL(scoutConfig.hosts.kibana));
|
||||
|
||||
log.debug(serviceLoadedMsg('kbnUrl'));
|
||||
|
|
|
@ -17,17 +17,17 @@ import {
|
|||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { HostOptions, SamlSessionManager } from '@kbn/test';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { ScoutTestConfig } from '../../types';
|
||||
import { Protocol } from '../../playwright/types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
const getResourceDirPath = (config: ScoutServerConfig) => {
|
||||
const getResourceDirPath = (config: ScoutTestConfig) => {
|
||||
return config.serverless
|
||||
? path.resolve(SERVERLESS_ROLES_ROOT_PATH, config.projectType!)
|
||||
: path.resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH);
|
||||
};
|
||||
|
||||
const createKibanaHostOptions = (config: ScoutServerConfig): HostOptions => {
|
||||
const createKibanaHostOptions = (config: ScoutTestConfig): HostOptions => {
|
||||
const kibanaUrl = new URL(config.hosts.kibana);
|
||||
kibanaUrl.username = config.auth.username;
|
||||
kibanaUrl.password = config.auth.password;
|
||||
|
@ -42,7 +42,7 @@ const createKibanaHostOptions = (config: ScoutServerConfig): HostOptions => {
|
|||
};
|
||||
|
||||
export const createSamlSessionManager = (
|
||||
config: ScoutServerConfig,
|
||||
config: ScoutTestConfig,
|
||||
log: ToolingLog
|
||||
): SamlSessionManager => {
|
||||
const resourceDirPath = getResourceDirPath(config);
|
||||
|
|
128
packages/kbn-scout/src/config/config.test.ts
Normal file
128
packages/kbn-scout/src/config/config.test.ts
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Config } from './config';
|
||||
|
||||
describe('Config.getScoutTestConfig', () => {
|
||||
it(`should return a properly structured 'ScoutTestConfig' object for 'stateful'`, async () => {
|
||||
const config = new Config({
|
||||
servers: {
|
||||
elasticsearch: {
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 9220,
|
||||
username: 'kibana_system',
|
||||
password: 'changeme',
|
||||
},
|
||||
kibana: {
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 5620,
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
},
|
||||
},
|
||||
dockerServers: {},
|
||||
esTestCluster: {
|
||||
from: 'snapshot',
|
||||
files: [],
|
||||
serverArgs: [],
|
||||
ssl: false,
|
||||
},
|
||||
kbnTestServer: {
|
||||
buildArgs: [],
|
||||
env: {},
|
||||
sourceArgs: [],
|
||||
serverArgs: [],
|
||||
},
|
||||
});
|
||||
|
||||
const scoutConfig = config.getScoutTestConfig();
|
||||
|
||||
const expectedConfig = {
|
||||
serverless: false,
|
||||
projectType: undefined,
|
||||
isCloud: false,
|
||||
license: 'trial',
|
||||
cloudUsersFilePath: expect.stringContaining('.ftr/role_users.json'),
|
||||
hosts: {
|
||||
kibana: 'http://localhost:5620',
|
||||
elasticsearch: 'http://localhost:9220',
|
||||
},
|
||||
auth: {
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
},
|
||||
metadata: {
|
||||
generatedOn: expect.any(String),
|
||||
config: expect.any(Object),
|
||||
},
|
||||
};
|
||||
|
||||
expect(scoutConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
||||
it(`should return a properly structured 'ScoutTestConfig' object for 'serverless=es'`, async () => {
|
||||
const config = new Config({
|
||||
serverless: true,
|
||||
servers: {
|
||||
elasticsearch: {
|
||||
protocol: 'https',
|
||||
hostname: 'localhost',
|
||||
port: 9220,
|
||||
username: 'elastic_serverless',
|
||||
password: 'changeme',
|
||||
},
|
||||
kibana: {
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 5620,
|
||||
username: 'elastic_serverless',
|
||||
password: 'changeme',
|
||||
},
|
||||
},
|
||||
dockerServers: {},
|
||||
esTestCluster: {
|
||||
from: 'serverless',
|
||||
files: [],
|
||||
serverArgs: [],
|
||||
ssl: true,
|
||||
},
|
||||
kbnTestServer: {
|
||||
buildArgs: [],
|
||||
env: {},
|
||||
sourceArgs: [],
|
||||
serverArgs: ['--serverless=es'],
|
||||
},
|
||||
});
|
||||
|
||||
const scoutConfig = config.getScoutTestConfig();
|
||||
const expectedConfig = {
|
||||
serverless: true,
|
||||
projectType: 'es',
|
||||
isCloud: false,
|
||||
license: 'trial',
|
||||
cloudUsersFilePath: expect.stringContaining('.ftr/role_users.json'),
|
||||
hosts: {
|
||||
kibana: 'http://localhost:5620',
|
||||
elasticsearch: 'https://localhost:9220',
|
||||
},
|
||||
auth: {
|
||||
username: 'elastic_serverless',
|
||||
password: 'changeme',
|
||||
},
|
||||
metadata: {
|
||||
generatedOn: expect.any(String),
|
||||
config: expect.any(Object),
|
||||
},
|
||||
};
|
||||
|
||||
expect(scoutConfig).toEqual(expectedConfig);
|
||||
});
|
||||
});
|
|
@ -13,15 +13,15 @@ import Path from 'path';
|
|||
import { cloneDeepWith, get, has, toPath } from 'lodash';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { schema } from './schema';
|
||||
import { ScoutServerConfig } from '../types';
|
||||
import { formatCurrentDate, getProjectType } from './utils';
|
||||
import { ScoutServerConfig, ScoutTestConfig } from '../types';
|
||||
import { formatCurrentDate, getProjectType } from './utils/utils';
|
||||
|
||||
const $values = Symbol('values');
|
||||
|
||||
export class Config {
|
||||
private [$values]: Record<string, any>;
|
||||
private [$values]: ScoutServerConfig;
|
||||
|
||||
constructor(data: Record<string, any>) {
|
||||
constructor(data: ScoutServerConfig) {
|
||||
const { error, value } = schema.validate(data, {
|
||||
abortEarly: false,
|
||||
});
|
||||
|
@ -104,13 +104,14 @@ export class Config {
|
|||
});
|
||||
}
|
||||
|
||||
public getTestServersConfig(): ScoutServerConfig {
|
||||
public getScoutTestConfig(): ScoutTestConfig {
|
||||
return {
|
||||
serverless: this.get('serverless'),
|
||||
projectType: this.get('serverless')
|
||||
? getProjectType(this.get('kbnTestServer.serverArgs'))
|
||||
: undefined,
|
||||
isCloud: false,
|
||||
license: this.get('esTestCluster.license'),
|
||||
cloudUsersFilePath: Path.resolve(REPO_ROOT, '.ftr', 'role_users.json'),
|
||||
hosts: {
|
||||
kibana: Url.format({
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { loadConfig } from './loader/config_load';
|
||||
export { getConfigFilePath } from './get_config_file';
|
||||
export { loadServersConfig } from './utils';
|
||||
export { readConfigFile } from './loader';
|
||||
export { getConfigFilePath, loadServersConfig } from './utils';
|
||||
export type { Config } from './config';
|
||||
|
|
10
packages/kbn-scout/src/config/loader/index.ts
Normal file
10
packages/kbn-scout/src/config/loader/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { readConfigFile } from './read_config_file';
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { Config } from '../config';
|
||||
import { readConfigFile } from './read_config_file';
|
||||
|
||||
jest.mock('path', () => ({
|
||||
resolve: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../config', () => ({
|
||||
Config: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('readConfigFile', () => {
|
||||
const configPath = '/mock/config/path';
|
||||
const resolvedPath = '/resolved/config/path';
|
||||
const mockPathResolve = path.resolve as jest.Mock;
|
||||
const mockConfigConstructor = Config as jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it(`should load and return a valid 'Config' instance when the config file exports 'servers'`, async () => {
|
||||
const mockConfigModule = { servers: { host: 'localhost', port: 5601 } };
|
||||
|
||||
mockPathResolve.mockReturnValueOnce(resolvedPath);
|
||||
|
||||
jest.isolateModules(async () => {
|
||||
jest.mock(resolvedPath, () => mockConfigModule, { virtual: true });
|
||||
mockConfigConstructor.mockImplementation((servers) => ({ servers }));
|
||||
|
||||
const result = await readConfigFile(configPath);
|
||||
|
||||
expect(path.resolve).toHaveBeenCalledWith(configPath);
|
||||
expect(result).toEqual({ servers: mockConfigModule.servers });
|
||||
});
|
||||
});
|
||||
|
||||
it(`should throw an error if the config file does not export 'servers'`, async () => {
|
||||
const mockConfigModule = { otherProperty: 'value' };
|
||||
|
||||
mockPathResolve.mockReturnValueOnce(resolvedPath);
|
||||
|
||||
jest.isolateModules(async () => {
|
||||
jest.mock(resolvedPath, () => mockConfigModule, { virtual: true });
|
||||
|
||||
await expect(readConfigFile(configPath)).rejects.toThrow(
|
||||
`No 'servers' found in the config file at path: ${resolvedPath}`
|
||||
);
|
||||
expect(path.resolve).toHaveBeenCalledWith(configPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the config file cannot be loaded', async () => {
|
||||
mockPathResolve.mockReturnValueOnce(resolvedPath);
|
||||
|
||||
jest.isolateModules(async () => {
|
||||
const message = 'Module not found';
|
||||
jest.mock(
|
||||
resolvedPath,
|
||||
() => {
|
||||
throw new Error(message);
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
await expect(readConfigFile(configPath)).rejects.toThrow(
|
||||
`Failed to load config from ${configPath}: ${message}`
|
||||
);
|
||||
expect(path.resolve).toHaveBeenCalledWith(configPath);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import path from 'path';
|
||||
import { Config } from '../config';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
|
||||
/**
|
||||
* Dynamically loads server configuration file in the "kbn-scout" framework. It reads
|
||||
|
@ -17,13 +18,13 @@ import { Config } from '../config';
|
|||
* @param configPath Path to the configuration file to be loaded.
|
||||
* @returns Config instance that is used to start local servers
|
||||
*/
|
||||
export const loadConfig = async (configPath: string): Promise<Config> => {
|
||||
export const readConfigFile = async (configPath: string): Promise<Config> => {
|
||||
try {
|
||||
const absolutePath = path.resolve(configPath);
|
||||
const configModule = await import(absolutePath);
|
||||
|
||||
if (configModule.servers) {
|
||||
return new Config(configModule.servers);
|
||||
return new Config(configModule.servers as ScoutServerConfig);
|
||||
} else {
|
||||
throw new Error(`No 'servers' found in the config file at path: ${absolutePath}`);
|
||||
}
|
|
@ -75,7 +75,7 @@ export const schema = Joi.object()
|
|||
|
||||
esTestCluster: Joi.object()
|
||||
.keys({
|
||||
license: Joi.valid('basic', 'trial', 'gold').default('basic'),
|
||||
license: Joi.valid('basic', 'trial', 'gold').default('trial'),
|
||||
from: Joi.string().default('snapshot'),
|
||||
serverArgs: Joi.array().items(Joi.string()).default([]),
|
||||
esJavaOpts: Joi.string(),
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { defaultConfig } from './serverless.base.config';
|
||||
|
||||
export const servers: ScoutLoaderConfig = {
|
||||
export const servers: ScoutServerConfig = {
|
||||
...defaultConfig,
|
||||
esTestCluster: {
|
||||
...defaultConfig.esTestCluster,
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
*/
|
||||
|
||||
import { defaultConfig } from './serverless.base.config';
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
|
||||
export const servers: ScoutLoaderConfig = {
|
||||
export const servers: ScoutServerConfig = {
|
||||
...defaultConfig,
|
||||
esTestCluster: {
|
||||
...defaultConfig.esTestCluster,
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { defaultConfig } from './serverless.base.config';
|
||||
|
||||
export const servers: ScoutLoaderConfig = {
|
||||
export const servers: ScoutServerConfig = {
|
||||
...defaultConfig,
|
||||
esTestCluster: {
|
||||
...defaultConfig.esTestCluster,
|
||||
|
|
|
@ -17,7 +17,7 @@ import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
|||
|
||||
import { dockerImage } from '@kbn/test-suites-xpack/fleet_api_integration/config.base';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { SAML_IDP_PLUGIN_PATH, SERVERLESS_IDP_METADATA_PATH, JWKS_PATH } from '../constants';
|
||||
|
||||
const packageRegistryConfig = join(__dirname, './package_registry_config.yml');
|
||||
|
@ -49,7 +49,7 @@ const servers = {
|
|||
},
|
||||
};
|
||||
|
||||
export const defaultConfig: ScoutLoaderConfig = {
|
||||
export const defaultConfig: ScoutServerConfig = {
|
||||
serverless: true,
|
||||
servers,
|
||||
dockerServers: defineDockerServersConfig({
|
||||
|
|
|
@ -25,7 +25,7 @@ import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
|||
import { dockerImage } from '@kbn/test-suites-xpack/fleet_api_integration/config.base';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { STATEFUL_ROLES_ROOT_PATH } from '@kbn/es';
|
||||
import type { ScoutLoaderConfig } from '../../types';
|
||||
import type { ScoutServerConfig } from '../../types';
|
||||
import { SAML_IDP_PLUGIN_PATH, STATEFUL_IDP_METADATA_PATH } from '../constants';
|
||||
|
||||
const packageRegistryConfig = join(__dirname, './package_registry_config.yml');
|
||||
|
@ -61,7 +61,7 @@ const servers = {
|
|||
|
||||
const kbnUrl = `${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`;
|
||||
|
||||
export const defaultConfig: ScoutLoaderConfig = {
|
||||
export const defaultConfig: ScoutServerConfig = {
|
||||
servers,
|
||||
dockerServers: defineDockerServersConfig({
|
||||
registry: {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { defaultConfig } from './base.config';
|
||||
|
||||
export const servers: ScoutLoaderConfig = defaultConfig;
|
||||
export const servers: ScoutServerConfig = defaultConfig;
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import * as Fs from 'fs';
|
||||
import getopts from 'getopts';
|
||||
import path from 'path';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ServerlessProjectType } from '@kbn/es';
|
||||
import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info';
|
||||
import { CliSupportedServerModes, ScoutServerConfig } from '../types';
|
||||
import { getConfigFilePath } from './get_config_file';
|
||||
import { loadConfig } from './loader/config_load';
|
||||
import type { Config } from './config';
|
||||
|
||||
export const formatCurrentDate = () => {
|
||||
const now = new Date();
|
||||
|
||||
const format = (num: number, length: number) => String(num).padStart(length, '0');
|
||||
|
||||
return (
|
||||
`${format(now.getDate(), 2)}/${format(now.getMonth() + 1, 2)}/${now.getFullYear()} ` +
|
||||
`${format(now.getHours(), 2)}:${format(now.getMinutes(), 2)}:${format(now.getSeconds(), 2)}.` +
|
||||
`${format(now.getMilliseconds(), 3)}`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves Scout server configuration to the disk.
|
||||
* @param testServersConfig configuration to be saved
|
||||
* @param log Logger instance to report errors or debug information.
|
||||
*/
|
||||
const saveTestServersConfigOnDisk = (testServersConfig: ScoutServerConfig, log: ToolingLog) => {
|
||||
const configFilePath = path.join(SCOUT_SERVERS_ROOT, `local.json`);
|
||||
|
||||
try {
|
||||
const jsonData = JSON.stringify(testServersConfig, null, 2);
|
||||
|
||||
if (!Fs.existsSync(SCOUT_SERVERS_ROOT)) {
|
||||
log.debug(`scout: creating configuration directory: ${SCOUT_SERVERS_ROOT}`);
|
||||
Fs.mkdirSync(SCOUT_SERVERS_ROOT, { recursive: true });
|
||||
}
|
||||
|
||||
Fs.writeFileSync(configFilePath, jsonData, 'utf-8');
|
||||
log.info(`scout: Test server configuration saved at ${configFilePath}`);
|
||||
} catch (error) {
|
||||
log.error(`scout: Failed to save test server configuration - ${error.message}`);
|
||||
throw new Error(`Failed to save test server configuration at ${configFilePath}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads server configuration based on the mode, creates "kbn-test" compatible Config
|
||||
* instance, that can be used to start local servers and saves its "Scout"-format copy
|
||||
* to the disk.
|
||||
* @param mode server local run mode
|
||||
* @param log Logger instance to report errors or debug information.
|
||||
* @returns "kbn-test" compatible Config instance
|
||||
*/
|
||||
export async function loadServersConfig(
|
||||
mode: CliSupportedServerModes,
|
||||
log: ToolingLog
|
||||
): Promise<Config> {
|
||||
// get path to one of the predefined config files
|
||||
const configPath = getConfigFilePath(mode);
|
||||
// load config that is compatible with kbn-test input format
|
||||
const config = await loadConfig(configPath);
|
||||
// construct config for Playwright Test
|
||||
const scoutServerConfig = config.getTestServersConfig();
|
||||
// save test config to the file
|
||||
saveTestServersConfigOnDisk(scoutServerConfig, log);
|
||||
return config;
|
||||
}
|
||||
|
||||
export const getProjectType = (kbnServerArgs: string[]) => {
|
||||
const options = getopts(kbnServerArgs);
|
||||
return options.serverless as ServerlessProjectType;
|
||||
};
|
35
packages/kbn-scout/src/config/utils/get_config_file.test.ts
Normal file
35
packages/kbn-scout/src/config/utils/get_config_file.test.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { getConfigFilePath } from './get_config_file';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
|
||||
// Not mocking to validate the actual path to the config file
|
||||
const CONFIG_ROOT = path.join(REPO_ROOT, 'packages', 'kbn-scout', 'src', 'config');
|
||||
|
||||
describe('getConfigFilePath', () => {
|
||||
it('should return the correct path for stateful config', () => {
|
||||
const config = 'stateful';
|
||||
const expectedPath = path.join(CONFIG_ROOT, 'stateful', 'stateful.config.ts');
|
||||
|
||||
const result = getConfigFilePath(config);
|
||||
|
||||
expect(result).toBe(expectedPath);
|
||||
});
|
||||
|
||||
it('should return the correct path for serverless config with a valid type', () => {
|
||||
const config = 'serverless=oblt';
|
||||
const expectedPath = path.join(CONFIG_ROOT, 'serverless', 'oblt.serverless.config.ts');
|
||||
|
||||
const result = getConfigFilePath(config);
|
||||
|
||||
expect(result).toBe(expectedPath);
|
||||
});
|
||||
});
|
|
@ -8,19 +8,22 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { CliSupportedServerModes } from '../types';
|
||||
import { CliSupportedServerModes } from '../../types';
|
||||
|
||||
export const getConfigFilePath = (config: CliSupportedServerModes): string => {
|
||||
const baseDir = path.join(__dirname, '..'); // config base directory
|
||||
|
||||
if (config === 'stateful') {
|
||||
return path.join(__dirname, 'stateful', 'stateful.config.ts');
|
||||
return path.join(baseDir, 'stateful', 'stateful.config.ts');
|
||||
}
|
||||
|
||||
const [mode, type] = config.split('=');
|
||||
|
||||
if (mode !== 'serverless' || !type) {
|
||||
throw new Error(
|
||||
`Invalid config format: ${config}. Expected "stateful" or "serverless=<type>".`
|
||||
`Invalid config format: "${config}". Expected "stateful" or "serverless=<type>".`
|
||||
);
|
||||
}
|
||||
|
||||
return path.join(__dirname, 'serverless', `${type}.serverless.config.ts`);
|
||||
return path.join(baseDir, 'serverless', `${type}.serverless.config.ts`);
|
||||
};
|
12
packages/kbn-scout/src/config/utils/index.ts
Normal file
12
packages/kbn-scout/src/config/utils/index.ts
Normal file
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { getConfigFilePath } from './get_config_file';
|
||||
export { loadServersConfig } from './load_servers_config';
|
||||
export { formatCurrentDate, getProjectType } from './utils';
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { getConfigFilePath } from './get_config_file';
|
||||
import { readConfigFile } from '../loader';
|
||||
import { loadServersConfig } from '..';
|
||||
import { saveScoutTestConfigOnDisk } from './save_scout_test_config';
|
||||
import { CliSupportedServerModes, ScoutTestConfig } from '../../types';
|
||||
|
||||
jest.mock('./get_config_file', () => ({
|
||||
getConfigFilePath: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../loader', () => ({
|
||||
readConfigFile: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./save_scout_test_config', () => ({
|
||||
saveScoutTestConfigOnDisk: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockScoutTestConfig: ScoutTestConfig = {
|
||||
hosts: {
|
||||
kibana: 'http://localhost:5601',
|
||||
elasticsearch: 'http://localhost:9220',
|
||||
},
|
||||
auth: {
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
},
|
||||
serverless: true,
|
||||
projectType: 'oblt',
|
||||
isCloud: true,
|
||||
license: 'trial',
|
||||
cloudUsersFilePath: '/path/to/users',
|
||||
};
|
||||
|
||||
describe('loadServersConfig', () => {
|
||||
let mockLog: ToolingLog;
|
||||
|
||||
const mockMode = `serverless=${mockScoutTestConfig.projectType}` as CliSupportedServerModes;
|
||||
const mockConfigPath = '/mock/config/path.ts';
|
||||
|
||||
const mockClusterConfig = {
|
||||
getScoutTestConfig: jest.fn().mockReturnValue(mockScoutTestConfig),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockLog = {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as unknown as ToolingLog;
|
||||
});
|
||||
|
||||
it('should load, save, and return cluster configuration', async () => {
|
||||
(getConfigFilePath as jest.Mock).mockReturnValue(mockConfigPath);
|
||||
(readConfigFile as jest.Mock).mockResolvedValue(mockClusterConfig);
|
||||
|
||||
const result = await loadServersConfig(mockMode, mockLog);
|
||||
|
||||
expect(getConfigFilePath).toHaveBeenCalledWith(mockMode);
|
||||
expect(readConfigFile).toHaveBeenCalledWith(mockConfigPath);
|
||||
expect(mockClusterConfig.getScoutTestConfig).toHaveBeenCalled();
|
||||
expect(saveScoutTestConfigOnDisk).toHaveBeenCalledWith(mockScoutTestConfig, mockLog);
|
||||
expect(result).toBe(mockClusterConfig);
|
||||
|
||||
// no errors should be logged
|
||||
expect(mockLog.info).not.toHaveBeenCalledWith(expect.stringContaining('error'));
|
||||
});
|
||||
|
||||
it('should throw an error if readConfigFile fails', async () => {
|
||||
const errorMessage = 'Failed to read config file';
|
||||
(getConfigFilePath as jest.Mock).mockReturnValue(mockConfigPath);
|
||||
(readConfigFile as jest.Mock).mockRejectedValue(new Error(errorMessage));
|
||||
|
||||
await expect(loadServersConfig(mockMode, mockLog)).rejects.toThrow(errorMessage);
|
||||
|
||||
expect(getConfigFilePath).toHaveBeenCalledWith(mockMode);
|
||||
expect(readConfigFile).toHaveBeenCalledWith(mockConfigPath);
|
||||
expect(saveScoutTestConfigOnDisk).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
38
packages/kbn-scout/src/config/utils/load_servers_config.ts
Normal file
38
packages/kbn-scout/src/config/utils/load_servers_config.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { CliSupportedServerModes } from '../../types';
|
||||
import { getConfigFilePath } from './get_config_file';
|
||||
import { readConfigFile } from '../loader';
|
||||
import type { Config } from '../config';
|
||||
import { saveScoutTestConfigOnDisk } from './save_scout_test_config';
|
||||
|
||||
/**
|
||||
* Loads server configuration based on the mode, creates "kbn-test" compatible Config
|
||||
* instance, that can be used to start local servers and saves its "Scout"-format copy
|
||||
* to the disk.
|
||||
* @param mode server local run mode
|
||||
* @param log Logger instance to report errors or debug information.
|
||||
* @returns "kbn-test" compatible Config instance
|
||||
*/
|
||||
export async function loadServersConfig(
|
||||
mode: CliSupportedServerModes,
|
||||
log: ToolingLog
|
||||
): Promise<Config> {
|
||||
// get path to one of the predefined config files
|
||||
const configPath = getConfigFilePath(mode);
|
||||
// load config that is compatible with kbn-test input format
|
||||
const clusterConfig = await readConfigFile(configPath);
|
||||
// construct config for Playwright Test
|
||||
const scoutServerConfig = clusterConfig.getScoutTestConfig();
|
||||
// save test config to the file
|
||||
saveScoutTestConfigOnDisk(scoutServerConfig, log);
|
||||
return clusterConfig;
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import Fs from 'fs';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { saveScoutTestConfigOnDisk } from './save_scout_test_config';
|
||||
|
||||
const MOCKED_SCOUT_SERVERS_ROOT = '/mock/repo/root/scout/servers';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
jest.mock('@kbn/repo-info', () => ({
|
||||
REPO_ROOT: '/mock/repo/root',
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/scout-info', () => ({
|
||||
SCOUT_SERVERS_ROOT: '/mock/repo/root/scout/servers',
|
||||
}));
|
||||
|
||||
const testServersConfig = {
|
||||
hosts: {
|
||||
kibana: 'http://localhost:5601',
|
||||
elasticsearch: 'http://localhost:9220',
|
||||
},
|
||||
auth: {
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
},
|
||||
serverless: true,
|
||||
isCloud: true,
|
||||
license: 'trial',
|
||||
cloudUsersFilePath: '/path/to/users',
|
||||
};
|
||||
|
||||
jest.mock('path', () => ({
|
||||
...jest.requireActual('path'),
|
||||
join: jest.fn((...args) => args.join('/')),
|
||||
}));
|
||||
|
||||
describe('saveScoutTestConfigOnDisk', () => {
|
||||
let mockLog: ToolingLog;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockLog = {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as unknown as ToolingLog;
|
||||
});
|
||||
|
||||
it('should save configuration to disk successfully', () => {
|
||||
const mockConfigFilePath = `${MOCKED_SCOUT_SERVERS_ROOT}/local.json`;
|
||||
|
||||
// Mock path.join to return a fixed file path
|
||||
(path.join as jest.Mock).mockReturnValueOnce(mockConfigFilePath);
|
||||
|
||||
// Mock Fs.existsSync to return true
|
||||
(Fs.existsSync as jest.Mock).mockReturnValueOnce(true);
|
||||
|
||||
// Mock Fs.writeFileSync to do nothing
|
||||
const writeFileSyncMock = jest.spyOn(Fs, 'writeFileSync');
|
||||
|
||||
saveScoutTestConfigOnDisk(testServersConfig, mockLog);
|
||||
|
||||
expect(Fs.existsSync).toHaveBeenCalledWith(MOCKED_SCOUT_SERVERS_ROOT);
|
||||
expect(writeFileSyncMock).toHaveBeenCalledWith(
|
||||
mockConfigFilePath,
|
||||
JSON.stringify(testServersConfig, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
expect(mockLog.info).toHaveBeenCalledWith(
|
||||
`scout: Test server configuration saved at ${mockConfigFilePath}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if writing to file fails', () => {
|
||||
const mockConfigFilePath = `${MOCKED_SCOUT_SERVERS_ROOT}/local.json`;
|
||||
|
||||
(path.join as jest.Mock).mockReturnValueOnce(mockConfigFilePath);
|
||||
(Fs.existsSync as jest.Mock).mockReturnValueOnce(true);
|
||||
|
||||
// Mock writeFileSync to throw an error
|
||||
(Fs.writeFileSync as jest.Mock).mockImplementationOnce(() => {
|
||||
throw new Error('Disk is full');
|
||||
});
|
||||
|
||||
expect(() => saveScoutTestConfigOnDisk(testServersConfig, mockLog)).toThrow(
|
||||
`Failed to save test server configuration at ${mockConfigFilePath}`
|
||||
);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
`scout: Failed to save test server configuration - Disk is full`
|
||||
);
|
||||
});
|
||||
|
||||
it('should create configuration directory if it does not exist', () => {
|
||||
const mockConfigFilePath = `${MOCKED_SCOUT_SERVERS_ROOT}/local.json`;
|
||||
|
||||
(path.join as jest.Mock).mockReturnValueOnce(mockConfigFilePath);
|
||||
|
||||
// Mock existsSync to simulate non-existent directory
|
||||
(Fs.existsSync as jest.Mock).mockReturnValueOnce(false);
|
||||
|
||||
const mkdirSyncMock = jest.spyOn(Fs, 'mkdirSync');
|
||||
const writeFileSyncMock = jest.spyOn(Fs, 'writeFileSync');
|
||||
|
||||
saveScoutTestConfigOnDisk(testServersConfig, mockLog);
|
||||
|
||||
expect(Fs.existsSync).toHaveBeenCalledWith(MOCKED_SCOUT_SERVERS_ROOT);
|
||||
expect(mkdirSyncMock).toHaveBeenCalledWith(MOCKED_SCOUT_SERVERS_ROOT, { recursive: true });
|
||||
expect(writeFileSyncMock).toHaveBeenCalledWith(
|
||||
mockConfigFilePath,
|
||||
JSON.stringify(testServersConfig, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
expect(mockLog.debug).toHaveBeenCalledWith(
|
||||
`scout: creating configuration directory: ${MOCKED_SCOUT_SERVERS_ROOT}`
|
||||
);
|
||||
expect(mockLog.info).toHaveBeenCalledWith(
|
||||
`scout: Test server configuration saved at ${mockConfigFilePath}`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import * as Fs from 'fs';
|
||||
import path from 'path';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info';
|
||||
import { ScoutTestConfig } from '../../types';
|
||||
|
||||
/**
|
||||
* Saves Scout server configuration to the disk.
|
||||
* @param testServersConfig configuration to be saved
|
||||
* @param log Logger instance to report errors or debug information.
|
||||
*/
|
||||
export const saveScoutTestConfigOnDisk = (testServersConfig: ScoutTestConfig, log: ToolingLog) => {
|
||||
const configFilePath = path.join(SCOUT_SERVERS_ROOT, `local.json`);
|
||||
|
||||
try {
|
||||
const jsonData = JSON.stringify(testServersConfig, null, 2);
|
||||
|
||||
if (!Fs.existsSync(SCOUT_SERVERS_ROOT)) {
|
||||
log.debug(`scout: creating configuration directory: ${SCOUT_SERVERS_ROOT}`);
|
||||
Fs.mkdirSync(SCOUT_SERVERS_ROOT, { recursive: true });
|
||||
}
|
||||
|
||||
Fs.writeFileSync(configFilePath, jsonData, 'utf-8');
|
||||
log.info(`scout: Test server configuration saved at ${configFilePath}`);
|
||||
} catch (error) {
|
||||
log.error(`scout: Failed to save test server configuration - ${error.message}`);
|
||||
throw new Error(`Failed to save test server configuration at ${configFilePath}`);
|
||||
}
|
||||
};
|
28
packages/kbn-scout/src/config/utils/utils.ts
Normal file
28
packages/kbn-scout/src/config/utils/utils.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import getopts from 'getopts';
|
||||
import { ServerlessProjectType } from '@kbn/es';
|
||||
|
||||
export const formatCurrentDate = () => {
|
||||
const now = new Date();
|
||||
|
||||
const format = (num: number, length: number) => String(num).padStart(length, '0');
|
||||
|
||||
return (
|
||||
`${format(now.getDate(), 2)}/${format(now.getMonth() + 1, 2)}/${now.getFullYear()} ` +
|
||||
`${format(now.getHours(), 2)}:${format(now.getMinutes(), 2)}:${format(now.getSeconds(), 2)}.` +
|
||||
`${format(now.getMilliseconds(), 3)}`
|
||||
);
|
||||
};
|
||||
|
||||
export const getProjectType = (kbnServerArgs: string[]) => {
|
||||
const options = getopts(kbnServerArgs);
|
||||
return options.serverless as ServerlessProjectType;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info';
|
||||
import { createPlaywrightConfig } from './create_config';
|
||||
import { VALID_CONFIG_MARKER } from '../types';
|
||||
|
||||
describe('createPlaywrightConfig', () => {
|
||||
it('should return a valid default Playwright configuration', () => {
|
||||
const testDir = './my_tests';
|
||||
const config = createPlaywrightConfig({ testDir });
|
||||
|
||||
expect(config.testDir).toBe(testDir);
|
||||
expect(config.workers).toBe(1);
|
||||
expect(config.fullyParallel).toBe(false);
|
||||
expect(config.use).toEqual({
|
||||
serversConfigDir: SCOUT_SERVERS_ROOT,
|
||||
[VALID_CONFIG_MARKER]: true,
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
});
|
||||
expect(config.globalSetup).toBeUndefined();
|
||||
expect(config.globalTeardown).toBeUndefined();
|
||||
expect(config.reporter).toEqual([
|
||||
['html', { open: 'never', outputFolder: './output/reports' }],
|
||||
['json', { outputFile: './output/reports/test-results.json' }],
|
||||
['@kbn/scout-reporting/src/reporting/playwright.ts', { name: 'scout-playwright' }],
|
||||
]);
|
||||
expect(config.timeout).toBe(60000);
|
||||
expect(config.expect?.timeout).toBe(10000);
|
||||
expect(config.outputDir).toBe('./output/test-artifacts');
|
||||
expect(config.projects![0].name).toEqual('chromium');
|
||||
});
|
||||
|
||||
it(`should override 'workers' count in Playwright configuration`, () => {
|
||||
const testDir = './my_tests';
|
||||
const workers = 2;
|
||||
|
||||
const config = createPlaywrightConfig({ testDir, workers });
|
||||
expect(config.workers).toBe(workers);
|
||||
});
|
||||
});
|
76
packages/kbn-scout/src/playwright/config/create_config.ts
Normal file
76
packages/kbn-scout/src/playwright/config/create_config.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { defineConfig, PlaywrightTestConfig, devices } from '@playwright/test';
|
||||
import { scoutPlaywrightReporter } from '@kbn/scout-reporting';
|
||||
import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info';
|
||||
import { ScoutPlaywrightOptions, ScoutTestOptions, VALID_CONFIG_MARKER } from '../types';
|
||||
|
||||
export function createPlaywrightConfig(options: ScoutPlaywrightOptions): PlaywrightTestConfig {
|
||||
return defineConfig<ScoutTestOptions>({
|
||||
testDir: options.testDir,
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: options.workers ?? 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['html', { outputFolder: './output/reports', open: 'never' }], // HTML report configuration
|
||||
['json', { outputFile: './output/reports/test-results.json' }], // JSON report
|
||||
scoutPlaywrightReporter({ name: 'scout-playwright' }), // Scout report
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
serversConfigDir: SCOUT_SERVERS_ROOT,
|
||||
[VALID_CONFIG_MARKER]: true,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
// video: 'retain-on-failure',
|
||||
// storageState: './output/reports/state.json', // Store session state (like cookies)
|
||||
},
|
||||
|
||||
// Timeout for each test, includes test, hooks and fixtures
|
||||
timeout: 60000,
|
||||
|
||||
// Timeout for each assertion
|
||||
expect: {
|
||||
timeout: 10000,
|
||||
},
|
||||
|
||||
outputDir: './output/test-artifacts', // For other test artifacts (screenshots, videos, traces)
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
}
|
|
@ -7,70 +7,4 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { defineConfig, PlaywrightTestConfig, devices } from '@playwright/test';
|
||||
import { scoutPlaywrightReporter } from '@kbn/scout-reporting';
|
||||
import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info';
|
||||
import { ScoutPlaywrightOptions, ScoutTestOptions, VALID_CONFIG_MARKER } from '../types';
|
||||
|
||||
export function createPlaywrightConfig(options: ScoutPlaywrightOptions): PlaywrightTestConfig {
|
||||
return defineConfig<ScoutTestOptions>({
|
||||
testDir: options.testDir,
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: options.workers ?? 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['html', { outputFolder: './output/reports', open: 'never' }], // HTML report configuration
|
||||
['json', { outputFile: './output/reports/test-results.json' }], // JSON report
|
||||
scoutPlaywrightReporter({ name: 'scout-playwright' }), // Scout report
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
serversConfigDir: SCOUT_SERVERS_ROOT,
|
||||
[VALID_CONFIG_MARKER]: true,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
// video: 'retain-on-failure',
|
||||
// storageState: './output/reports/state.json', // Store session state (like cookies)
|
||||
},
|
||||
|
||||
// Timeout for each test, includes test, hooks and fixtures
|
||||
timeout: 60000,
|
||||
|
||||
// Timeout for each assertion
|
||||
expect: {
|
||||
timeout: 10000,
|
||||
},
|
||||
|
||||
outputDir: './output/test-artifacts', // For other test artifacts (screenshots, videos, traces)
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
}
|
||||
export { createPlaywrightConfig } from './create_config';
|
||||
|
|
|
@ -14,7 +14,7 @@ import { LoadActionPerfOptions } from '@kbn/es-archiver';
|
|||
import { IndexStats } from '@kbn/es-archiver/src/lib/stats';
|
||||
import type { UiSettingValues } from '@kbn/test/src/kbn_client/kbn_client_ui_settings';
|
||||
|
||||
import { ScoutServerConfig } from '../../../types';
|
||||
import { ScoutTestConfig } from '../../../types';
|
||||
import { KibanaUrl } from '../../../common/services/kibana_url';
|
||||
|
||||
export interface EsArchiverFixture {
|
||||
|
@ -58,7 +58,7 @@ export interface UiSettingsFixture {
|
|||
*/
|
||||
export interface ScoutWorkerFixtures {
|
||||
log: ToolingLog;
|
||||
config: ScoutServerConfig;
|
||||
config: ScoutTestConfig;
|
||||
kbnUrl: KibanaUrl;
|
||||
esClient: Client;
|
||||
kbnClient: KbnClient;
|
||||
|
|
17
packages/kbn-scout/src/playwright/runner/config_loader.ts
Normal file
17
packages/kbn-scout/src/playwright/runner/config_loader.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loads the config module dynamically
|
||||
* @param configPath config absolute path
|
||||
* @returns
|
||||
*/
|
||||
export async function loadConfigModule(configPath: string) {
|
||||
return import(configPath);
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { validatePlaywrightConfig } from './config_validator';
|
||||
import * as configLoader from './config_loader';
|
||||
import Fs from 'fs';
|
||||
import { VALID_CONFIG_MARKER } from '../types';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
const existsSyncMock = jest.spyOn(Fs, 'existsSync');
|
||||
const loadConfigModuleMock = jest.spyOn(configLoader, 'loadConfigModule');
|
||||
|
||||
describe('validatePlaywrightConfig', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should pass validation for a valid config file', async () => {
|
||||
const configPath = 'valid/path/config.ts';
|
||||
existsSyncMock.mockReturnValue(true);
|
||||
loadConfigModuleMock.mockResolvedValue({
|
||||
default: {
|
||||
use: { [VALID_CONFIG_MARKER]: true },
|
||||
testDir: './tests',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(validatePlaywrightConfig(configPath)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw an error if the config file does not have the valid marker', async () => {
|
||||
const configPath = 'valid/path/config.ts';
|
||||
existsSyncMock.mockReturnValue(true);
|
||||
loadConfigModuleMock.mockResolvedValue({
|
||||
default: {
|
||||
use: {},
|
||||
testDir: './tests',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(validatePlaywrightConfig(configPath)).rejects.toThrow(
|
||||
`The config file at "${configPath}" must be created with "createPlaywrightConfig" from '@kbn/scout' package:`
|
||||
);
|
||||
});
|
||||
|
||||
it(`should throw an error if the config file does not have a 'testDir'`, async () => {
|
||||
const configPath = 'valid/path/config.ts';
|
||||
existsSyncMock.mockReturnValue(true);
|
||||
loadConfigModuleMock.mockResolvedValue({
|
||||
default: {
|
||||
use: { [VALID_CONFIG_MARKER]: true },
|
||||
},
|
||||
});
|
||||
|
||||
await expect(validatePlaywrightConfig(configPath)).rejects.toThrow(
|
||||
`The config file at "${configPath}" must export a valid Playwright configuration with "testDir" property.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the config file does not have a default export', async () => {
|
||||
const configPath = 'valid/path/config.ts';
|
||||
existsSyncMock.mockReturnValue(true);
|
||||
loadConfigModuleMock.mockResolvedValue({
|
||||
test: {
|
||||
use: {},
|
||||
testDir: './tests',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(validatePlaywrightConfig(configPath)).rejects.toThrow(
|
||||
`The config file at "${configPath}" must export default function`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the path does not exist', async () => {
|
||||
const configPath = 'invalid/path/to/config.ts';
|
||||
existsSyncMock.mockReturnValue(false);
|
||||
|
||||
await expect(validatePlaywrightConfig(configPath)).rejects.toThrow(
|
||||
`Path to a valid TypeScript config file is required: --config <relative path to .ts file>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the file does not have a .ts extension', async () => {
|
||||
const configPath = 'config.js';
|
||||
existsSyncMock.mockReturnValue(true);
|
||||
|
||||
await expect(validatePlaywrightConfig(configPath)).rejects.toThrow(
|
||||
`Path to a valid TypeScript config file is required: --config <relative path to .ts file>`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -7,34 +7,35 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import * as Fs from 'fs';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import Fs from 'fs';
|
||||
import { PlaywrightTestConfig } from 'playwright/test';
|
||||
import path from 'path';
|
||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||
import { ScoutTestOptions, VALID_CONFIG_MARKER } from '../types';
|
||||
import { loadConfigModule } from './config_loader';
|
||||
|
||||
export async function validatePlaywrightConfig(configPath: string) {
|
||||
const fullPath = path.resolve(REPO_ROOT, configPath);
|
||||
|
||||
// Check if the path exists and has a .ts extension
|
||||
if (!configPath || !Fs.existsSync(fullPath) || !configPath.endsWith('.ts')) {
|
||||
if (!configPath || !Fs.existsSync(configPath) || !configPath.endsWith('.ts')) {
|
||||
throw createFlagError(
|
||||
`Path to a valid TypeScript config file is required: --config <relative path to .ts file>`
|
||||
);
|
||||
}
|
||||
|
||||
// Dynamically import the file to check for a default export
|
||||
const configModule = await import(fullPath);
|
||||
const configModule = await loadConfigModule(configPath);
|
||||
// Check for a default export
|
||||
const config = configModule.default as PlaywrightTestConfig<ScoutTestOptions>;
|
||||
|
||||
// Check if the config's 'use' property has the valid marker
|
||||
if (config === undefined) {
|
||||
throw createFlagError(`The config file at "${configPath}" must export default function`);
|
||||
}
|
||||
|
||||
if (!config?.use?.[VALID_CONFIG_MARKER]) {
|
||||
// Check if the config's 'use' property has the valid marker
|
||||
throw createFlagError(
|
||||
`The config file at "${configPath}" must be created with "createPlaywrightConfig" from '@kbn/scout' package:\n
|
||||
export default createPlaywrightConfig({
|
||||
testDir: './tests',
|
||||
});`
|
||||
export default createPlaywrightConfig({
|
||||
testDir: './tests',
|
||||
});`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
102
packages/kbn-scout/src/playwright/runner/flags.test.ts
Normal file
102
packages/kbn-scout/src/playwright/runner/flags.test.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { parseTestFlags } from './flags';
|
||||
import { FlagsReader } from '@kbn/dev-cli-runner';
|
||||
import * as configValidator from './config_validator';
|
||||
|
||||
const validatePlaywrightConfigMock = jest.spyOn(configValidator, 'validatePlaywrightConfig');
|
||||
|
||||
describe('parseTestFlags', () => {
|
||||
it(`should throw an error without 'config' flag`, async () => {
|
||||
const flags = new FlagsReader({
|
||||
stateful: true,
|
||||
logToFile: false,
|
||||
headed: false,
|
||||
});
|
||||
|
||||
await expect(parseTestFlags(flags)).rejects.toThrow(
|
||||
'Path to playwright config is required: --config <file path>'
|
||||
);
|
||||
});
|
||||
|
||||
it(`should throw an error with '--stateful' flag as string value`, async () => {
|
||||
const flags = new FlagsReader({
|
||||
stateful: 'true',
|
||||
logToFile: false,
|
||||
headed: false,
|
||||
});
|
||||
|
||||
await expect(parseTestFlags(flags)).rejects.toThrow('expected --stateful to be a boolean');
|
||||
});
|
||||
|
||||
it(`should throw an error with '--serverless' flag as boolean`, async () => {
|
||||
const flags = new FlagsReader({
|
||||
serverless: true,
|
||||
logToFile: false,
|
||||
headed: false,
|
||||
});
|
||||
|
||||
await expect(parseTestFlags(flags)).rejects.toThrow('expected --serverless to be a string');
|
||||
});
|
||||
|
||||
it(`should throw an error with incorrect '--serverless' flag`, async () => {
|
||||
const flags = new FlagsReader({
|
||||
serverless: 'a',
|
||||
logToFile: false,
|
||||
headed: false,
|
||||
});
|
||||
|
||||
await expect(parseTestFlags(flags)).rejects.toThrow(
|
||||
'invalid --serverless, expected one of "es", "oblt", "security"'
|
||||
);
|
||||
});
|
||||
|
||||
it(`should parse with correct config and serverless flags`, async () => {
|
||||
const flags = new FlagsReader({
|
||||
config: '/path/to/config',
|
||||
stateful: false,
|
||||
serverless: 'oblt',
|
||||
logToFile: false,
|
||||
headed: false,
|
||||
});
|
||||
validatePlaywrightConfigMock.mockResolvedValueOnce();
|
||||
const result = await parseTestFlags(flags);
|
||||
|
||||
expect(result).toEqual({
|
||||
mode: 'serverless=oblt',
|
||||
configPath: '/path/to/config',
|
||||
headed: false,
|
||||
esFrom: undefined,
|
||||
installDir: undefined,
|
||||
logsDir: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it(`should parse with correct config and stateful flags`, async () => {
|
||||
const flags = new FlagsReader({
|
||||
config: '/path/to/config',
|
||||
stateful: true,
|
||||
logToFile: false,
|
||||
headed: true,
|
||||
esFrom: 'snapshot',
|
||||
});
|
||||
validatePlaywrightConfigMock.mockResolvedValueOnce();
|
||||
const result = await parseTestFlags(flags);
|
||||
|
||||
expect(result).toEqual({
|
||||
mode: 'stateful',
|
||||
configPath: '/path/to/config',
|
||||
headed: true,
|
||||
esFrom: 'snapshot',
|
||||
installDir: undefined,
|
||||
logsDir: undefined,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,6 +7,8 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import path from 'path';
|
||||
import { FlagOptions, FlagsReader } from '@kbn/dev-cli-runner';
|
||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||
import { SERVER_FLAG_OPTIONS, parseServerFlags } from '../../servers';
|
||||
|
@ -42,7 +44,8 @@ export async function parseTestFlags(flags: FlagsReader) {
|
|||
throw createFlagError(`Path to playwright config is required: --config <file path>`);
|
||||
}
|
||||
|
||||
await validatePlaywrightConfig(configPath);
|
||||
const configFullPath = path.resolve(REPO_ROOT, configPath);
|
||||
await validatePlaywrightConfig(configFullPath);
|
||||
|
||||
return {
|
||||
...options,
|
||||
|
|
|
@ -7,23 +7,4 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { Config } from '../../config';
|
||||
import { tagsByMode } from '../tags';
|
||||
|
||||
export const serviceLoadedMsg = (name: string) => `scout service loaded: ${name}`;
|
||||
|
||||
export const isValidUTCDate = (date: string): boolean => {
|
||||
return !isNaN(Date.parse(date)) && new Date(date).toISOString() === date;
|
||||
};
|
||||
|
||||
export function formatTime(date: string, fmt: string = 'MMM D, YYYY @ HH:mm:ss.SSS') {
|
||||
return moment.utc(date, fmt).format();
|
||||
}
|
||||
|
||||
export const getPlaywrightGrepTag = (config: Config): string => {
|
||||
const serversConfig = config.getTestServersConfig();
|
||||
return serversConfig.serverless
|
||||
? tagsByMode.serverless[serversConfig.projectType!]
|
||||
: tagsByMode.stateful;
|
||||
};
|
||||
export { serviceLoadedMsg, isValidUTCDate, formatTime, getPlaywrightGrepTag } from './runner_utils';
|
||||
|
|
108
packages/kbn-scout/src/playwright/utils/runner_utils.test.ts
Normal file
108
packages/kbn-scout/src/playwright/utils/runner_utils.test.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { isValidUTCDate, formatTime, getPlaywrightGrepTag } from './runner_utils';
|
||||
import moment from 'moment';
|
||||
jest.mock('moment', () => {
|
||||
const actualMoment = jest.requireActual('moment');
|
||||
return {
|
||||
...actualMoment,
|
||||
utc: jest.fn((date, fmt) => actualMoment(date, fmt)),
|
||||
};
|
||||
});
|
||||
|
||||
describe('isValidUTCDate', () => {
|
||||
it('should return true for valid UTC date strings', () => {
|
||||
expect(isValidUTCDate('2019-04-27T23:56:51.374Z')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid date strings', () => {
|
||||
expect(isValidUTCDate('invalid-date')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for valid non-UTC date strings', () => {
|
||||
expect(isValidUTCDate('2015-09-19T06:31:44')).toBe(false);
|
||||
expect(isValidUTCDate('Sep 19, 2015 @ 06:31:44.000')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatTime', () => {
|
||||
it('should format the time using the default format', () => {
|
||||
const mockDate = '2024-12-16T12:00:00.000Z';
|
||||
const mockFormat = 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
(moment.utc as jest.Mock).mockReturnValue({ format: () => 'Dec 16, 2024 @ 12:00:00.000' });
|
||||
|
||||
const result = formatTime(mockDate);
|
||||
|
||||
expect(moment.utc).toHaveBeenCalledWith(mockDate, mockFormat);
|
||||
expect(result).toBe('Dec 16, 2024 @ 12:00:00.000');
|
||||
});
|
||||
|
||||
it('should format the time using a custom format', () => {
|
||||
const mockDate = '2024-12-16T12:00:00.000Z';
|
||||
const customFormat = 'YYYY-MM-DD';
|
||||
(moment.utc as jest.Mock).mockReturnValue({ format: () => '2024-12-16' });
|
||||
|
||||
const result = formatTime(mockDate, customFormat);
|
||||
|
||||
expect(moment.utc).toHaveBeenCalledWith(mockDate, customFormat);
|
||||
expect(result).toBe('2024-12-16');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPlaywrightGrepTag', () => {
|
||||
const mockConfig = {
|
||||
getScoutTestConfig: jest.fn(),
|
||||
};
|
||||
|
||||
it('should return the correct tag for serverless mode', () => {
|
||||
mockConfig.getScoutTestConfig.mockReturnValue({
|
||||
serverless: true,
|
||||
projectType: 'oblt',
|
||||
});
|
||||
|
||||
const result = getPlaywrightGrepTag(mockConfig as any);
|
||||
|
||||
expect(mockConfig.getScoutTestConfig).toHaveBeenCalled();
|
||||
expect(result).toBe('@svlOblt');
|
||||
});
|
||||
|
||||
it('should return the correct tag for stateful mode', () => {
|
||||
mockConfig.getScoutTestConfig.mockReturnValue({
|
||||
serverless: false,
|
||||
});
|
||||
|
||||
const result = getPlaywrightGrepTag(mockConfig as any);
|
||||
|
||||
expect(mockConfig.getScoutTestConfig).toHaveBeenCalled();
|
||||
expect(result).toBe('@ess');
|
||||
});
|
||||
|
||||
it('should throw an error if projectType is missing in serverless mode', () => {
|
||||
mockConfig.getScoutTestConfig.mockReturnValue({
|
||||
serverless: true,
|
||||
projectType: undefined,
|
||||
});
|
||||
|
||||
expect(() => getPlaywrightGrepTag(mockConfig as any)).toThrow(
|
||||
`'projectType' is required to determine tags for 'serverless' mode.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if unknown projectType is set in serverless mode', () => {
|
||||
mockConfig.getScoutTestConfig.mockReturnValue({
|
||||
serverless: true,
|
||||
projectType: 'a',
|
||||
});
|
||||
|
||||
expect(() => getPlaywrightGrepTag(mockConfig as any)).toThrow(
|
||||
`No tags found for projectType: 'a'.`
|
||||
);
|
||||
});
|
||||
});
|
43
packages/kbn-scout/src/playwright/utils/runner_utils.ts
Normal file
43
packages/kbn-scout/src/playwright/utils/runner_utils.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { Config } from '../../config';
|
||||
import { tagsByMode } from '../tags';
|
||||
|
||||
export const serviceLoadedMsg = (name: string) => `scout service loaded: ${name}`;
|
||||
|
||||
export const isValidUTCDate = (date: string): boolean => {
|
||||
return !isNaN(Date.parse(date)) && new Date(date).toISOString() === date;
|
||||
};
|
||||
|
||||
export function formatTime(date: string, fmt: string = 'MMM D, YYYY @ HH:mm:ss.SSS') {
|
||||
return moment.utc(date, fmt).format();
|
||||
}
|
||||
|
||||
export const getPlaywrightGrepTag = (config: Config): string => {
|
||||
const serversConfig = config.getScoutTestConfig();
|
||||
|
||||
if (serversConfig.serverless) {
|
||||
const { projectType } = serversConfig;
|
||||
|
||||
if (!projectType) {
|
||||
throw new Error(`'projectType' is required to determine tags for 'serverless' mode.`);
|
||||
}
|
||||
|
||||
const tag = tagsByMode.serverless[projectType];
|
||||
if (!tag) {
|
||||
throw new Error(`No tags found for projectType: '${projectType}'.`);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
return tagsByMode.stateful;
|
||||
};
|
74
packages/kbn-scout/src/servers/flags.test.ts
Normal file
74
packages/kbn-scout/src/servers/flags.test.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { parseServerFlags } from './flags';
|
||||
import { FlagsReader } from '@kbn/dev-cli-runner';
|
||||
|
||||
describe('parseServerFlags', () => {
|
||||
it(`should throw an error with '--stateful' flag as string value`, () => {
|
||||
const flags = new FlagsReader({
|
||||
stateful: 'true',
|
||||
logToFile: false,
|
||||
});
|
||||
|
||||
expect(() => parseServerFlags(flags)).toThrow('expected --stateful to be a boolean');
|
||||
});
|
||||
|
||||
it(`should throw an error with '--serverless' flag as boolean`, () => {
|
||||
const flags = new FlagsReader({
|
||||
serverless: true,
|
||||
logToFile: false,
|
||||
});
|
||||
|
||||
expect(() => parseServerFlags(flags)).toThrow('expected --serverless to be a string');
|
||||
});
|
||||
|
||||
it(`should throw an error with incorrect '--serverless' flag`, () => {
|
||||
const flags = new FlagsReader({
|
||||
serverless: 'a',
|
||||
logToFile: false,
|
||||
});
|
||||
|
||||
expect(() => parseServerFlags(flags)).toThrow(
|
||||
'invalid --serverless, expected one of "es", "oblt", "security"'
|
||||
);
|
||||
});
|
||||
|
||||
it(`should parse with correct config and serverless flags`, () => {
|
||||
const flags = new FlagsReader({
|
||||
stateful: false,
|
||||
serverless: 'oblt',
|
||||
logToFile: false,
|
||||
});
|
||||
const result = parseServerFlags(flags);
|
||||
|
||||
expect(result).toEqual({
|
||||
mode: 'serverless=oblt',
|
||||
esFrom: undefined,
|
||||
installDir: undefined,
|
||||
logsDir: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it(`should parse with correct config and stateful flags`, () => {
|
||||
const flags = new FlagsReader({
|
||||
stateful: true,
|
||||
logToFile: false,
|
||||
esFrom: 'snapshot',
|
||||
});
|
||||
const result = parseServerFlags(flags);
|
||||
|
||||
expect(result).toEqual({
|
||||
mode: 'stateful',
|
||||
esFrom: 'snapshot',
|
||||
installDir: undefined,
|
||||
logsDir: undefined,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './config';
|
||||
export * from './server_config';
|
||||
export * from './cli';
|
||||
export * from './servers';
|
||||
export * from './test_config';
|
||||
export * from './services';
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import { UrlParts } from '@kbn/test';
|
||||
|
||||
export interface ScoutLoaderConfig {
|
||||
export interface ScoutServerConfig {
|
||||
serverless?: boolean;
|
||||
servers: {
|
||||
kibana: UrlParts;
|
|
@ -9,10 +9,11 @@
|
|||
|
||||
import { ServerlessProjectType } from '@kbn/es';
|
||||
|
||||
export interface ScoutServerConfig {
|
||||
export interface ScoutTestConfig {
|
||||
serverless: boolean;
|
||||
projectType?: ServerlessProjectType;
|
||||
isCloud: boolean;
|
||||
license: string;
|
||||
cloudUsersFilePath: string;
|
||||
hosts: {
|
||||
kibana: string;
|
Loading…
Add table
Add a link
Reference in a new issue