diff --git a/src/analysis.ts b/src/analysis.ts index c0a9a9bf..93e64d92 100644 --- a/src/analysis.ts +++ b/src/analysis.ts @@ -37,31 +37,37 @@ import { AnalyzeGitOptions, GitOptions, } from './interfaces/analysis-options.interface'; + +import { RequestOptions } from './interfaces/http-options.interface'; + import { fromEntries } from './lib/utils'; const sleep = (duration: number) => new Promise(resolve => setTimeout(resolve, duration)); -async function pollAnalysis({ - baseURL, - sessionToken, - includeLint, - severity, - bundleId, - oAuthToken, - username, - limitToFiles, - source, -}: { - baseURL: string; - sessionToken: string; - includeLint: boolean; - severity: AnalysisSeverity; - bundleId: string; - oAuthToken?: string; - username?: string; - limitToFiles?: string[]; - source: string; -}): Promise> { +async function pollAnalysis( + { + baseURL, + sessionToken, + includeLint, + severity, + bundleId, + oAuthToken, + username, + limitToFiles, + source, + }: { + baseURL: string; + sessionToken: string; + includeLint: boolean; + severity: AnalysisSeverity; + bundleId: string; + oAuthToken?: string; + username?: string; + limitToFiles?: string[]; + source: string; + }, + requestOptions?: RequestOptions, +): Promise> { let analysisResponse: IResult; let analysisData: GetAnalysisResponseDto; @@ -73,17 +79,20 @@ async function pollAnalysis({ // eslint-disable-next-line no-constant-condition while (true) { // eslint-disable-next-line no-await-in-loop - analysisResponse = await getAnalysis({ - baseURL, - sessionToken, - oAuthToken, - username, - bundleId, - includeLint, - severity, - limitToFiles, - source, - }); + analysisResponse = await getAnalysis( + { + baseURL, + sessionToken, + oAuthToken, + username, + bundleId, + includeLint, + severity, + limitToFiles, + source, + }, + requestOptions, + ); if (analysisResponse.type === 'error') { return analysisResponse; @@ -112,39 +121,45 @@ async function pollAnalysis({ } } -export async function analyzeBundle({ - baseURL = defaultBaseURL, - sessionToken = '', - includeLint = false, - severity = AnalysisSeverity.info, - bundleId, - oAuthToken, - username, - limitToFiles, - source, -}: { - baseURL: string; - sessionToken: string; - includeLint: boolean; - severity: AnalysisSeverity; - bundleId: string; - oAuthToken?: string; - username?: string; - limitToFiles?: string[]; - source: string; -}): Promise { - // Call remote bundle for analysis results and emit intermediate progress - const analysisData = await pollAnalysis({ - baseURL, - sessionToken, +export async function analyzeBundle( + { + baseURL = defaultBaseURL, + sessionToken = '', + includeLint = false, + severity = AnalysisSeverity.info, + bundleId, oAuthToken, username, - bundleId, - includeLint, - severity, limitToFiles, source, - }); + }: { + baseURL: string; + sessionToken: string; + includeLint: boolean; + severity: AnalysisSeverity; + bundleId: string; + oAuthToken?: string; + username?: string; + limitToFiles?: string[]; + source: string; + }, + requestOptions?: RequestOptions, +): Promise { + // Call remote bundle for analysis results and emit intermediate progress + const analysisData = await pollAnalysis( + { + baseURL, + sessionToken, + oAuthToken, + username, + bundleId, + includeLint, + severity, + limitToFiles, + source, + }, + requestOptions, + ); if (analysisData.type === 'error') { throw analysisData.error; @@ -393,31 +408,37 @@ const analyzeGitDefaults = { source: '', }; -export async function analyzeGit(options: GitOptions): Promise { +export async function analyzeGit(options: GitOptions, requestOptions?: RequestOptions): Promise { const analysisOptions: AnalyzeGitOptions = { ...analyzeGitDefaults, ...options }; const { baseURL, sessionToken, oAuthToken, username, includeLint, severity, gitUri, sarif, source } = analysisOptions; - const bundleResponse = await createGitBundle({ - baseURL, - sessionToken, - oAuthToken, - username, - gitUri, - source, - }); + const bundleResponse = await createGitBundle( + { + baseURL, + sessionToken, + oAuthToken, + username, + gitUri, + source, + }, + requestOptions, + ); if (bundleResponse.type === 'error') { throw bundleResponse.error; } const { bundleId } = bundleResponse.value; - const analysisData = await analyzeBundle({ - baseURL, - sessionToken, - oAuthToken, - username, - includeLint, - severity, - bundleId, - source, - }); + const analysisData = await analyzeBundle( + { + baseURL, + sessionToken, + oAuthToken, + username, + includeLint, + severity, + bundleId, + source, + }, + requestOptions, + ); const result = { baseURL, diff --git a/src/http.ts b/src/http.ts index 47429683..79693310 100644 --- a/src/http.ts +++ b/src/http.ts @@ -6,6 +6,7 @@ import axios from './axios'; import { IFiles, IFileContent, ISupportedFiles } from './interfaces/files.interface'; import { IAnalysisResult } from './interfaces/analysis-result.interface'; +import { RequestOptions } from './interfaces/http-options.interface'; type ResultSuccess = { type: 'success'; value: T }; type ResultError = { @@ -298,16 +299,19 @@ const CREATE_GIT_BUNDLE_ERROR_MESSAGES: { [P in CreateGitBundleErrorCodes]: stri [ErrorCodes.notFound]: 'Unable to found requested repository or commit hash', }; -export async function createGitBundle(options: { - readonly baseURL: string; - readonly sessionToken: string; - readonly oAuthToken?: string; - readonly username?: string; - readonly gitUri: string; - readonly source: string; -}): Promise> { +export async function createGitBundle( + options: { + readonly baseURL: string; + readonly sessionToken: string; + readonly oAuthToken?: string; + readonly username?: string; + readonly gitUri: string; + readonly source: string; + }, + requestOptions?: RequestOptions, +): Promise> { const { baseURL, sessionToken, oAuthToken, username, gitUri, source } = options; - const headers = { 'Session-Token': sessionToken, source }; + const headers = { ...requestOptions?.headers, 'Session-Token': sessionToken, source }; if (oAuthToken) { headers['X-OAuthToken'] = oAuthToken; } @@ -409,17 +413,20 @@ const GET_ANALYSIS_ERROR_MESSAGES: { [P in GetAnalysisErrorCodes]: string } = { [ErrorCodes.serverError]: 'Getting analysis failed', }; -export async function getAnalysis(options: { - readonly baseURL: string; - readonly sessionToken: string; - readonly bundleId: string; - readonly includeLint?: boolean; - readonly severity: number; - readonly limitToFiles?: string[]; - readonly oAuthToken?: string; - readonly username?: string; - readonly source: string; -}): Promise> { +export async function getAnalysis( + options: { + readonly baseURL: string; + readonly sessionToken: string; + readonly bundleId: string; + readonly includeLint?: boolean; + readonly severity: number; + readonly limitToFiles?: string[]; + readonly oAuthToken?: string; + readonly username?: string; + readonly source: string; + }, + requestOptions?: RequestOptions, +): Promise> { const { baseURL, sessionToken, @@ -434,7 +441,7 @@ export async function getAnalysis(options: { // ?linters=false is still a truthy query value, if(includeLint === false) we have to avoid sending the value altogether const params = { severity, linters: includeLint || undefined }; - const headers = { 'Session-Token': sessionToken, source }; + const headers = { ...requestOptions?.headers, 'Session-Token': sessionToken, source }; if (oAuthToken) { headers['X-OAuthToken'] = oAuthToken; } diff --git a/src/interfaces/http-options.interface.ts b/src/interfaces/http-options.interface.ts new file mode 100644 index 00000000..42988c3d --- /dev/null +++ b/src/interfaces/http-options.interface.ts @@ -0,0 +1,3 @@ +export interface RequestOptions { + headers: { [key: string]: string }; +} diff --git a/tests/git.analysis.spec.ts b/tests/git.analysis.spec.ts index 6deb8151..2b4a8930 100644 --- a/tests/git.analysis.spec.ts +++ b/tests/git.analysis.spec.ts @@ -7,6 +7,7 @@ import { Log } from 'sarif'; import * as sarifSchema from './sarif-schema-2.1.0.json'; import { ErrorCodes } from '../src/constants'; import { IGitBundle } from '../src/interfaces/analysis-result.interface'; +import axios from '../src/axios'; const oAuthToken = process.env.SNYK_OAUTH_KEY || ''; const sessionTokenNoRepoAccess = process.env.SNYK_API_KEY_NO_ACCESS || ''; @@ -191,6 +192,39 @@ describe('Functional test of analysis', () => { }); }); +describe('Custom request options', () => { + beforeAll(() => { + jest.mock('axios'); + axios.request = jest.fn().mockRejectedValue({}); + }); + + it( + 'passes custom options correctly', + async () => { + try { + await analyzeGit( + { + baseURL, + sessionToken, + includeLint: false, + severity: 1, + gitUri: 'git@github.com:DeepCodeAI/cli.git', + }, + { headers: { 'X-test-header': 'Snyk' } }, + ); + } catch (e) { + // expected to fail, we are interested in correct propagation of headers only + } + expect((axios.request as jest.Mock).mock.calls[0][0]).toMatchObject({headers: { 'X-test-header': 'Snyk' }}); + }, + TEST_TIMEOUT, + ); + + afterAll(() => { + jest.resetAllMocks(); + }); +}); + function getNumOfIssues(bundle: IGitBundle): number { let numberOfIssues = 0;