Skip to content

Commit

Permalink
Merge pull request #1523 from DataDog/teodor.todorov/SYNTH-17721/impo…
Browse files Browse the repository at this point in the history
…rt-ltd-from-a-main-test

[SYNTH-17721] Import LTD from a main test
  • Loading branch information
teodor2312 authored Jan 29, 2025
2 parents f21cfa3 + 79d81ed commit b84ad42
Show file tree
Hide file tree
Showing 11 changed files with 713 additions and 19 deletions.
119 changes: 119 additions & 0 deletions src/commands/synthetics/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {toBoolean, toNumber, toStringMap} from '../../../helpers/env'
import * as ciUtils from '../../../helpers/utils'

import * as api from '../api'
import {DEFAULT_IMPORT_TESTS_COMMAND_CONFIG, ImportTestsCommand} from '../import-tests-command'
import {
CookiesObject,
ExecutionRule,
ImportTestsCommandConfig,
RunTestsCommandConfig,
ServerTest,
UploadApplicationCommandConfig,
Expand Down Expand Up @@ -1592,3 +1594,120 @@ describe('upload-application', () => {
})
})
})

describe('import-tests', () => {
beforeEach(() => {
process.env = {}
jest.restoreAllMocks()
})

describe('resolveConfig', () => {
beforeEach(() => {
process.env = {}
})

test('override from ENV', async () => {
const overrideEnv = {
DATADOG_API_KEY: 'fake_api_key',
DATADOG_APP_KEY: 'fake_app_key',
DATADOG_SYNTHETICS_CONFIG_PATH: 'path/to/config.json',
DATADOG_SITE: 'datadoghq.eu',
DATADOG_SYNTHETICS_FILES: 'test-file1;test-file2;test-file3',
DATADOG_SYNTHETICS_PUBLIC_IDS: 'a-public-id;another-public-id',
DATADOG_SYNTHETICS_TEST_SEARCH_QUERY: 'a-search-query',
}

process.env = overrideEnv
const command = createCommand(ImportTestsCommand)

await command['resolveConfig']()
expect(command['config']).toEqual({
...DEFAULT_IMPORT_TESTS_COMMAND_CONFIG,
apiKey: overrideEnv.DATADOG_API_KEY,
appKey: overrideEnv.DATADOG_APP_KEY,
configPath: overrideEnv.DATADOG_SYNTHETICS_CONFIG_PATH,
datadogSite: overrideEnv.DATADOG_SITE,
files: overrideEnv.DATADOG_SYNTHETICS_FILES?.split(';'),
publicIds: overrideEnv.DATADOG_SYNTHETICS_PUBLIC_IDS?.split(';'),
testSearchQuery: overrideEnv.DATADOG_SYNTHETICS_TEST_SEARCH_QUERY,
})
})

test('override from config file', async () => {
const expectedConfig: ImportTestsCommandConfig = {
apiKey: 'fake_api_key',
appKey: 'fake_app_key',
configPath: 'src/commands/synthetics/__tests__/config-fixtures/import-tests-config-with-all-keys.json',
datadogSite: 'datadoghq.eu',
files: ['my-new-file'],
proxy: {protocol: 'http'},
publicIds: ['ran-dom-id1'],
testSearchQuery: 'a-search-query',
}

const command = createCommand(ImportTestsCommand)
command['configPath'] = 'src/commands/synthetics/__tests__/config-fixtures/import-tests-config-with-all-keys.json'

await command['resolveConfig']()
expect(command['config']).toEqual(expectedConfig)
})

test('override from CLI', async () => {
const overrideCLI: Omit<ImportTestsCommandConfig, 'proxy'> = {
apiKey: 'fake_api_key_cli',
appKey: 'fake_app_key_cli',
configPath: 'src/commands/synthetics/__tests__/config-fixtures/empty-config-file.json',
datadogSite: 'datadoghq.cli',
files: ['new-file'],
publicIds: ['ran-dom-id2'],
testSearchQuery: 'a-search-query',
}

const command = createCommand(ImportTestsCommand)
command['apiKey'] = overrideCLI.apiKey
command['appKey'] = overrideCLI.appKey
command['configPath'] = overrideCLI.configPath
command['datadogSite'] = overrideCLI.datadogSite
command['files'] = overrideCLI.files
command['publicIds'] = overrideCLI.publicIds
command['testSearchQuery'] = overrideCLI.testSearchQuery

await command['resolveConfig']()
expect(command['config']).toEqual({
...DEFAULT_IMPORT_TESTS_COMMAND_CONFIG,
apiKey: 'fake_api_key_cli',
appKey: 'fake_app_key_cli',
configPath: 'src/commands/synthetics/__tests__/config-fixtures/empty-config-file.json',
datadogSite: 'datadoghq.cli',
files: ['new-file'],
publicIds: ['ran-dom-id2'],
testSearchQuery: 'a-search-query',
})
})

test('override from config file < ENV < CLI', async () => {
jest.spyOn(ciUtils, 'resolveConfigFromFile').mockImplementationOnce(async <T>(baseConfig: T) => ({
...baseConfig,
apiKey: 'api_key_config_file',
appKey: 'app_key_config_file',
datadogSite: 'us5.datadoghq.com',
}))

process.env = {
DATADOG_API_KEY: 'api_key_env',
DATADOG_APP_KEY: 'app_key_env',
}

const command = createCommand(ImportTestsCommand)
command['apiKey'] = 'api_key_cli'

await command['resolveConfig']()
expect(command['config']).toEqual({
...DEFAULT_IMPORT_TESTS_COMMAND_CONFIG,
apiKey: 'api_key_cli',
appKey: 'app_key_env',
datadogSite: 'us5.datadoghq.com',
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"apiKey": "fake_api_key",
"appKey": "fake_app_key",
"configPath": "fake-datadog-ci.json",
"datadogSite": "datadoghq.eu",
"files": [
"my-new-file"
],
"publicIds": [
"ran-dom-id1"
],
"testSearchQuery": "a-search-query"
}
1 change: 1 addition & 0 deletions src/commands/synthetics/__tests__/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ export const mockApi = (override?: Partial<APIHelper>): APIHelper => {
getBatch: jest.fn(),
getMobileApplicationPresignedURLs: jest.fn(),
getTest: jest.fn(),
getTestWithType: jest.fn(),
getSyntheticsOrgSettings: jest.fn(),
getTunnelPresignedURL: jest.fn(),
pollResults: jest.fn(),
Expand Down
213 changes: 213 additions & 0 deletions src/commands/synthetics/__tests__/import-tests-lib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
jest.mock('fs/promises')
import * as fsPromises from 'fs/promises'

import * as api from '../api'
import {DEFAULT_IMPORT_TESTS_COMMAND_CONFIG} from '../import-tests-command'
import {importTests} from '../import-tests-lib'
import {TriggerConfig} from '../interfaces'
import * as tests from '../test'

import {getApiTest, getBrowserTest, mockApi, mockReporter} from './fixtures'

describe('import-tests', () => {
beforeEach(() => {
jest.restoreAllMocks()
jest.mock('fs/promises', () => ({
writeFile: jest.fn().mockResolvedValue(undefined),
}))
process.env = {}
})

describe('importTests', () => {
test('we write imported test to file', async () => {
const filePath = 'test.synthetics.json'
const config = DEFAULT_IMPORT_TESTS_COMMAND_CONFIG
config['files'] = [filePath]
config['publicIds'] = ['123-456-789']

const mockTest = getApiTest(config['publicIds'][0])
// eslint-disable-next-line @typescript-eslint/naming-convention
const {message, monitor_id, status, tags, ...mockTestWithoutUnsupportedFields} = mockTest
const mockLTD = {
tests: [
{
local_test_definition: {
...mockTestWithoutUnsupportedFields,
},
},
],
}
// eslint-disable-next-line no-null/no-null
const jsonData = JSON.stringify(mockLTD, null, 2)

const apiHelper = mockApi({
getTest: jest.fn(() => {
return Promise.resolve(mockTest)
}),
})
jest.spyOn(api, 'getApiHelper').mockImplementation(() => apiHelper as any)
jest.spyOn(tests, 'getTestConfigs').mockImplementation(async () => [])

await importTests(mockReporter, config)

expect(apiHelper.getTest).toHaveBeenCalledTimes(1)
expect(tests.getTestConfigs).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledWith(filePath, jsonData, 'utf8')
})

test('we can fetch multiple public_ids', async () => {
const filePath = 'test.synthetics.json'
const config = DEFAULT_IMPORT_TESTS_COMMAND_CONFIG
config['files'] = [filePath]
config['publicIds'] = ['123-456-789', '987-654-321']

const mockTest = getApiTest(config['publicIds'][0])
// eslint-disable-next-line @typescript-eslint/naming-convention
const {message, monitor_id, status, tags, ...mockTestWithoutUnsupportedFields} = mockTest
const mockLTD = {
tests: [
{
local_test_definition: {
...mockTestWithoutUnsupportedFields,
public_id: config['publicIds'][0],
},
},
{
local_test_definition: {
...mockTestWithoutUnsupportedFields,
public_id: config['publicIds'][1],
},
},
],
}
// eslint-disable-next-line no-null/no-null
const expectedJsonData = JSON.stringify(mockLTD, null, 2)

const apiHelper = mockApi({
getTest: jest
.fn()
.mockReturnValueOnce(Promise.resolve(mockTest))
.mockReturnValueOnce(Promise.resolve({...mockTest, public_id: config['publicIds'][1]})),
})
jest.spyOn(api, 'getApiHelper').mockImplementation(() => apiHelper as any)
jest.spyOn(tests, 'getTestConfigs').mockImplementation(async () => [])

await importTests(mockReporter, config)

expect(apiHelper.getTest).toHaveBeenCalledTimes(2)
expect(tests.getTestConfigs).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledWith(filePath, expectedJsonData, 'utf8')
})

test('we write browser test', async () => {
const filePath = 'test.synthetics.json'
const config = DEFAULT_IMPORT_TESTS_COMMAND_CONFIG
config['files'] = [filePath]
config['publicIds'] = ['123-456-789']

const mockTest = getBrowserTest(config['publicIds'][0])
// eslint-disable-next-line @typescript-eslint/naming-convention
const {message, monitor_id, status, tags, ...mockTestWithoutUnsupportedFields} = mockTest
const mockLTD = {
tests: [
{
local_test_definition: {
...mockTestWithoutUnsupportedFields,
},
},
],
}
// eslint-disable-next-line no-null/no-null
const jsonData = JSON.stringify(mockLTD, null, 2)

const apiHelper = mockApi({
getTest: jest.fn(() => {
return Promise.resolve(mockTest)
}),
getTestWithType: jest.fn(() => {
return Promise.resolve(mockTest)
}),
})
jest.spyOn(api, 'getApiHelper').mockImplementation(() => apiHelper as any)
jest.spyOn(tests, 'getTestConfigs').mockImplementation(async () => [])

await importTests(mockReporter, config)

expect(apiHelper.getTest).toHaveBeenCalledTimes(1)
expect(apiHelper.getTestWithType).toHaveBeenCalledTimes(1)
expect(tests.getTestConfigs).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledWith(filePath, jsonData, 'utf8')
})

test('we write imported test to already existing file', async () => {
const filePath = 'test.synthetics.json'
const config = DEFAULT_IMPORT_TESTS_COMMAND_CONFIG
config['files'] = [filePath]
config['publicIds'] = ['123-456-789', '987-654-321']

const mockTest = getApiTest(config['publicIds'][0])

const mockTriggerConfig: TriggerConfig[] = [
{
local_test_definition: {
...mockTest,
public_id: 'abc-def-ghi',
},
},
{
local_test_definition: {
...mockTest,
public_id: config['publicIds'][0],
},
},
]

// eslint-disable-next-line @typescript-eslint/naming-convention
const {message, monitor_id, status, tags, ...mockTestWithoutUnsupportedFields} = mockTest
const expectedLTD = {
tests: [
{
local_test_definition: {
...mockTest,
public_id: 'abc-def-ghi',
},
},
{
local_test_definition: {
...mockTestWithoutUnsupportedFields,
public_id: config['publicIds'][0],
name: 'Some other name',
},
},
{
local_test_definition: {
...mockTestWithoutUnsupportedFields,
public_id: config['publicIds'][1],
},
},
],
}
// eslint-disable-next-line no-null/no-null
const expectedJsonData = JSON.stringify(expectedLTD, null, 2)

const apiHelper = mockApi({
getTest: jest
.fn()
.mockReturnValueOnce(Promise.resolve({...mockTest, name: 'Some other name'}))
.mockReturnValueOnce(Promise.resolve({...mockTest, public_id: config['publicIds'][1]})),
})
jest.spyOn(api, 'getApiHelper').mockImplementation(() => apiHelper as any)
jest.spyOn(tests, 'getTestConfigs').mockResolvedValue(mockTriggerConfig)

await importTests(mockReporter, config)

expect(apiHelper.getTest).toHaveBeenCalledTimes(2)
expect(tests.getTestConfigs).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledTimes(1)
expect(fsPromises.writeFile).toHaveBeenCalledWith(filePath, expectedJsonData, 'utf8')
})
})
})
16 changes: 16 additions & 0 deletions src/commands/synthetics/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ const getTest = (request: (args: AxiosRequestConfig) => AxiosPromise<ServerTest>
return resp.data
}

const getTestWithType = (request: (args: AxiosRequestConfig) => AxiosPromise<ServerTest>) => async (
testId: string,
testType: string
) => {
const resp = await retryRequest(
{
url: `/synthetics/tests/${testType}/${testId}`,
},
request,
{retryOn429: true}
)

return resp.data
}

const searchTests = (request: (args: AxiosRequestConfig) => AxiosPromise<TestSearchResult>) => async (
query: string
) => {
Expand Down Expand Up @@ -353,6 +368,7 @@ export const apiConstructor = (configuration: APIConfiguration) => {
getBatch: getBatch(request),
getMobileApplicationPresignedURLs: getMobileApplicationPresignedURLs(requestUnstable),
getTest: getTest(request),
getTestWithType: getTestWithType(request),
getSyntheticsOrgSettings: getSyntheticsOrgSettings(request),
getTunnelPresignedURL: getTunnelPresignedURL(requestIntake),
pollResults: pollResults(request),
Expand Down
Loading

0 comments on commit b84ad42

Please sign in to comment.