Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [scout] adding unit tests (#204567) #204980

Merged
merged 1 commit into from
Dec 19, 2024
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
6 changes: 6 additions & 0 deletions packages/kbn-scout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions packages/kbn-scout/src/common/services/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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',
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/kbn-scout/src/common/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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'));

Expand Down
4 changes: 2 additions & 2 deletions packages/kbn-scout/src/common/services/kibana_url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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'));
Expand Down
8 changes: 4 additions & 4 deletions packages/kbn-scout/src/common/services/saml_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,7 +42,7 @@ const createKibanaHostOptions = (config: ScoutServerConfig): HostOptions => {
};

export const createSamlSessionManager = (
config: ScoutServerConfig,
config: ScoutTestConfig,
log: ToolingLog
): SamlSessionManager => {
const resourceDirPath = getResourceDirPath(config);
Expand Down
128 changes: 128 additions & 0 deletions packages/kbn-scout/src/config/config.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
11 changes: 6 additions & 5 deletions packages/kbn-scout/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down Expand Up @@ -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({
Expand Down
5 changes: 2 additions & 3 deletions packages/kbn-scout/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 10 additions & 0 deletions packages/kbn-scout/src/config/loader/index.ts
Original file line number Diff line number Diff line change
@@ -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';
83 changes: 83 additions & 0 deletions packages/kbn-scout/src/config/loader/read_config_file.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}`);
}
Expand Down
Loading
Loading