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

[ci-visibility] Show error message on deployment correlate command #1445

Merged
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
24 changes: 23 additions & 1 deletion src/commands/deployment/__tests__/correlate.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Cli} from 'clipanion/lib/advanced'

import {createMockContext} from '../../../helpers/__tests__/fixtures'
import {createMockContext, getAxiosError} from '../../../helpers/__tests__/fixtures'

import {DeploymentCorrelateCommand} from '../correlate'

Expand Down Expand Up @@ -75,4 +75,26 @@ describe('execute', () => {
"CI_JOB_ID": "1"
}`)
})
test('handleError', async () => {
const command = new DeploymentCorrelateCommand()
command['context'] = createMockContext() as any

const axiosError = getAxiosError(400, {
message: 'Request failed with status code 400',
errors: ['Some validation error'],
})

command['handleError'](axiosError)

expect(command['context'].stdout.toString()).toStrictEqual(
`[ERROR] Could not send deployment correlation data: {
"status": 400,
"response": {
"errors": [
"Some validation error"
]
}
}\n`
)
})
})
21 changes: 20 additions & 1 deletion src/commands/deployment/correlate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {isAxiosError} from 'axios'
import chalk from 'chalk'
import {Command, Option} from 'clipanion'
import simpleGit from 'simple-git'
Expand Down Expand Up @@ -148,7 +149,8 @@ export class DeploymentCorrelateCommand extends Command {
retries: 5,
})
} catch (error) {
this.logger.error(`Failed to send deployment correlation data: ${error.message}`)
// TODO: use `coerceError()`
this.handleError(error as Error)
}
}

Expand All @@ -166,4 +168,21 @@ export class DeploymentCorrelateCommand extends Command {

return true
}

private handleError(error: Error) {
this.context.stderr.write(
`${chalk.red.bold('[ERROR]')} Could not send deployment correlation data: ${
isAxiosError(error)
? JSON.stringify(
{
status: error.response?.status,
response: error.response?.data as unknown,
},
undefined,
2
)
: error.message
}\n`
)
}
}
13 changes: 7 additions & 6 deletions src/commands/synthetics/__tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type {AxiosResponse} from 'axios'

import axios, {AxiosError} from 'axios'

import {getAxiosError} from '../../../helpers/__tests__/fixtures'

import {apiConstructor, formatBackendErrors, getApiHelper} from '../api'
import {CriticalError} from '../errors'
import {ExecutionRule, PollResult, ServerResult, TestPayload, Trigger} from '../interfaces'
Expand All @@ -14,7 +16,6 @@ import * as utils from '../utils/public'
import {
ciConfig,
getApiTest,
getAxiosHttpError,
getSyntheticsProxy,
MOBILE_PRESIGNED_URLS_PAYLOAD,
MOBILE_PRESIGNED_UPLOAD_PARTS,
Expand Down Expand Up @@ -478,31 +479,31 @@ describe('getApiHelper', () => {

describe('formatBackendErrors', () => {
test('backend error - no error', () => {
const backendError = getAxiosHttpError(500, {errors: []})
const backendError = getAxiosError(500, {errors: []})
expect(formatBackendErrors(backendError)).toBe('error querying https://app.datadoghq.com/example')
})

test('backend error - single error', () => {
const backendError = getAxiosHttpError(500, {errors: ['single error']})
const backendError = getAxiosError(500, {errors: ['single error']})
expect(formatBackendErrors(backendError)).toBe(
'query on https://app.datadoghq.com/example returned: "single error"'
)
})

test('backend error - multiple errors', () => {
const backendError = getAxiosHttpError(500, {errors: ['error 1', 'error 2']})
const backendError = getAxiosError(500, {errors: ['error 1', 'error 2']})
expect(formatBackendErrors(backendError)).toBe(
'query on https://app.datadoghq.com/example returned:\n - error 1\n - error 2'
)
})

test('not a backend error', () => {
const requestError = getAxiosHttpError(403, {message: 'Forbidden'})
const requestError = getAxiosError(403, {message: 'Forbidden'})
expect(formatBackendErrors(requestError)).toBe('could not query https://app.datadoghq.com/example\nForbidden')
})

test('missing scope error', () => {
const requestError = getAxiosHttpError(403, {errors: ['Forbidden', 'Failed permission authorization checks']})
const requestError = getAxiosError(403, {errors: ['Forbidden', 'Failed permission authorization checks']})
expect(formatBackendErrors(requestError, 'synthetics_default_settings_read')).toBe(
'query on https://app.datadoghq.com/example returned:\n - Forbidden\n - Failed permission authorization checks\nIs the App key granted the synthetics_default_settings_read scope?'
)
Expand Down
22 changes: 11 additions & 11 deletions src/commands/synthetics/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Cli} from 'clipanion/lib/advanced'

import {createCommand} from '../../../helpers/__tests__/fixtures'
import {createCommand, getAxiosError} from '../../../helpers/__tests__/fixtures'
import * as ciUtils from '../../../helpers/utils'

import * as api from '../api'
Expand All @@ -11,12 +11,12 @@ import {
UploadApplicationCommandConfig,
UserConfigOverride,
} from '../interfaces'
import {DEFAULT_COMMAND_CONFIG, DEFAULT_POLLING_TIMEOUT, RunTestsCommand} from '../run-tests-command'
import {DEFAULT_COMMAND_CONFIG, RunTestsCommand} from '../run-tests-command'
import {DEFAULT_UPLOAD_COMMAND_CONFIG, UploadApplicationCommand} from '../upload-application-command'
import {toBoolean, toNumber, toExecutionRule, toStringMap} from '../utils/internal'
import * as utils from '../utils/public'

import {getApiTest, getAxiosHttpError, getTestSuite, mockApi, mockTestTriggerResponse} from './fixtures'
import {getApiTest, getTestSuite, mockApi, mockTestTriggerResponse} from './fixtures'
test('all option flags are supported', async () => {
const options = [
'apiKey',
Expand Down Expand Up @@ -1048,7 +1048,7 @@ describe('run-test', () => {
}

const triggerTests = jest.fn(() => {
throw getAxiosHttpError(502, {message: 'Bad Gateway'})
throw getAxiosError(502, {message: 'Bad Gateway'})
})
const apiHelper = mockApi({
getTest: jest.fn(async () => ({...getApiTest('publicId')})),
Expand Down Expand Up @@ -1228,7 +1228,7 @@ describe('run-test', () => {

const apiHelper = mockApi({
getTest: jest.fn(() => {
throw getAxiosHttpError(404, {errors: ['Test not found']})
throw getAxiosError(404, {errors: ['Test not found']})
}),
})
jest.spyOn(ciUtils, 'resolveConfigFromFile').mockImplementation(async (config, _) => config)
Expand All @@ -1251,7 +1251,7 @@ describe('run-test', () => {

const apiHelper = mockApi({
searchTests: jest.fn(() => {
throw errorCode ? getAxiosHttpError(errorCode, {message: 'Error'}) : new Error('Unknown error')
throw errorCode ? getAxiosError(errorCode, {message: 'Error'}) : new Error('Unknown error')
}),
})
jest.spyOn(api, 'getApiHelper').mockReturnValue(apiHelper)
Expand All @@ -1267,7 +1267,7 @@ describe('run-test', () => {

const apiHelper = mockApi({
getTest: jest.fn(() => {
throw errorCode ? getAxiosHttpError(errorCode, {message: 'Error'}) : new Error('Unknown error')
throw errorCode ? getAxiosError(errorCode, {message: 'Error'}) : new Error('Unknown error')
}),
})
jest.spyOn(ciUtils, 'resolveConfigFromFile').mockImplementation(async (config, __) => config)
Expand All @@ -1285,7 +1285,7 @@ describe('run-test', () => {
const apiHelper = mockApi({
getTest: async () => getApiTest('123-456-789'),
triggerTests: jest.fn(() => {
throw errorCode ? getAxiosHttpError(errorCode, {message: 'Error'}) : new Error('Unknown error')
throw errorCode ? getAxiosError(errorCode, {message: 'Error'}) : new Error('Unknown error')
}),
})
jest.spyOn(ciUtils, 'resolveConfigFromFile').mockImplementation(async (config, __) => config)
Expand All @@ -1304,7 +1304,7 @@ describe('run-test', () => {
getBatch: async () => ({results: [], status: 'passed'}),
getTest: async () => getApiTest('123-456-789'),
pollResults: jest.fn(() => {
throw errorCode ? getAxiosHttpError(errorCode, {message: 'Error'}) : new Error('Unknown error')
throw errorCode ? getAxiosError(errorCode, {message: 'Error'}) : new Error('Unknown error')
}),
triggerTests: async () => mockTestTriggerResponse,
})
Expand Down Expand Up @@ -1336,7 +1336,7 @@ describe('run-test', () => {
const apiHelper = mockApi({
getTest: jest.fn(async (testId: string) => {
if (testId === 'mis-sin-ggg') {
throw getAxiosHttpError(404, {errors: ['Test not found']})
throw getAxiosError(404, {errors: ['Test not found']})
}

return {} as ServerTest
Expand Down Expand Up @@ -1369,7 +1369,7 @@ describe('run-test', () => {
const apiHelper = mockApi({
getTest: jest.fn(async (testId: string) => {
if (testId === 'for-bid-den') {
const serverError = getAxiosHttpError(403, {errors: ['Forbidden']})
const serverError = getAxiosError(403, {errors: ['Forbidden']})
serverError.config.url = 'tests/for-bid-den'
throw serverError
}
Expand Down
13 changes: 0 additions & 13 deletions src/commands/synthetics/__tests__/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import * as http from 'http'
import * as net from 'net'
import {URL} from 'url'

import type {AxiosResponse, InternalAxiosRequestConfig} from 'axios'

import {AxiosError} from 'axios'
import WebSocket, {Server as WebSocketServer} from 'ws'

import {ProxyConfiguration} from '../../../helpers/utils'
Expand Down Expand Up @@ -52,8 +49,6 @@ const mockUser: User = {
name: '',
}

export const MOCK_BASE_URL = 'https://app.datadoghq.com/'

export type MockedReporter = {
[K in keyof MainReporter]: jest.Mock<void, Parameters<MainReporter[K]>>
}
Expand Down Expand Up @@ -94,14 +89,6 @@ export const ciConfig: RunTestsCommandConfig = {
variableStrings: [],
}

export const getAxiosHttpError = (status: number, {errors, message}: {errors?: string[]; message?: string}) => {
const serverError = new AxiosError(message) as AxiosError<any> & {config: InternalAxiosRequestConfig}
serverError.config = {baseURL: MOCK_BASE_URL, url: 'example'} as InternalAxiosRequestConfig
serverError.response = {data: {errors}, status} as AxiosResponse

return serverError
}

export const getApiTest = (publicId = 'abc-def-ghi', opts: Partial<Test> = {}): Test => ({
config: {
assertions: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ jest.unmock('chalk')

import {BaseContext} from 'clipanion/lib/advanced'

import {MOCK_BASE_URL} from '../../../../helpers/__tests__/fixtures'

import {
ExecutionRule,
MainReporter,
Expand All @@ -22,7 +24,6 @@ import {
getIncompleteServerResult,
getSummary,
getTimedOutBrowserResult,
MOCK_BASE_URL,
} from '../fixtures'

/**
Expand Down
3 changes: 2 additions & 1 deletion src/commands/synthetics/__tests__/reporters/junit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {Writable} from 'stream'

import {BaseContext} from 'clipanion/lib/advanced'

import {MOCK_BASE_URL} from '../../../../helpers/__tests__/fixtures'

import {Device, ExecutionRule, Result, Test} from '../../interfaces'
import {Args, getDefaultSuiteStats, getDefaultTestCaseStats, JUnitReporter, XMLTestCase} from '../../reporters/junit'
import {RunTestsCommand} from '../../run-tests-command'
Expand All @@ -23,7 +25,6 @@ import {
getMultiStepsServerResult,
getStep,
getSummary,
MOCK_BASE_URL,
} from '../fixtures'

const globalTestMock = getApiTest('123-456-789')
Expand Down
16 changes: 8 additions & 8 deletions src/commands/synthetics/__tests__/run-tests-lib.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'fs'

import {getAxiosError} from '../../../helpers/__tests__/fixtures'
import * as ciUtils from '../../../helpers/utils'

import * as api from '../api'
Expand All @@ -16,7 +17,6 @@ import {
ciConfig,
getApiResult,
getApiTest,
getAxiosHttpError,
getMobileTest,
MOBILE_PRESIGNED_URLS_PAYLOAD,
mockReporter,
Expand Down Expand Up @@ -332,7 +332,7 @@ describe('run-test', () => {
test(`getTestsList throws - ${status}`, async () => {
const apiHelper = {
searchTests: jest.fn(() => {
throw getAxiosHttpError(status, {message: 'Server Error'})
throw getAxiosError(status, {message: 'Server Error'})
}),
}
jest.spyOn(api, 'getApiHelper').mockImplementation(() => apiHelper as any)
Expand All @@ -348,7 +348,7 @@ describe('run-test', () => {
test(`getTestsToTrigger throws - ${status}`, async () => {
const apiHelper = {
getTest: jest.fn(() => {
throw getAxiosHttpError(status, {errors: ['Bad Gateway']})
throw getAxiosError(status, {errors: ['Bad Gateway']})
}),
}
jest.spyOn(api, 'getApiHelper').mockImplementation(() => apiHelper as any)
Expand Down Expand Up @@ -378,7 +378,7 @@ describe('run-test', () => {

const apiHelper = {
getTunnelPresignedURL: jest.fn(() => {
throw getAxiosHttpError(502, {message: 'Server Error'})
throw getAxiosError(502, {message: 'Server Error'})
}),
}

Expand Down Expand Up @@ -412,7 +412,7 @@ describe('run-test', () => {

const apiHelper = {
getMobileApplicationPresignedURLs: jest.fn(() => {
throw getAxiosHttpError(502, {message: 'Server Error'})
throw getAxiosError(502, {message: 'Server Error'})
}),
}

Expand Down Expand Up @@ -449,7 +449,7 @@ describe('run-test', () => {
const apiHelper = {
getMobileApplicationPresignedURLs: jest.fn(() => MOBILE_PRESIGNED_URLS_PAYLOAD),
uploadMobileApplicationPart: jest.fn(() => {
throw getAxiosHttpError(502, {message: 'Server Error'})
throw getAxiosError(502, {message: 'Server Error'})
}),
}

Expand Down Expand Up @@ -480,7 +480,7 @@ describe('run-test', () => {
const apiHelper = {
getTunnelPresignedURL: () => ({url: 'url'}),
triggerTests: jest.fn(() => {
throw getAxiosHttpError(502, {errors: ['Bad Gateway']})
throw getAxiosError(502, {errors: ['Bad Gateway']})
}),
}

Expand Down Expand Up @@ -531,7 +531,7 @@ describe('run-test', () => {
getBatch: () => ({results: []}),
getTunnelPresignedURL: () => ({url: 'url'}),
pollResults: jest.fn(() => {
throw getAxiosHttpError(502, {errors: ['Bad Gateway']})
throw getAxiosError(502, {errors: ['Bad Gateway']})
}),
}

Expand Down
Loading