diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62acdc4f..9d698acb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ permissions: jobs: delete: - uses: CheckmarxDev/ast-cli-javascript-wrapper/.github/workflows/delete-packages-and-releases.yml@main + uses: Checkmarx/ast-cli-javascript-wrapper/.github/workflows/delete-packages-and-releases.yml@main with: tag: ${{ inputs.jsTag }} secrets: inherit @@ -174,7 +174,7 @@ jobs: release_version: ${{ needs.release.outputs.TAG_NAME }} cli_release_version: ${{ needs.release.outputs.CLI_VERSION }} release_author: "Phoenix Team" - release_url: https://github.com/CheckmarxDev/ast-cli-javascript-wrapper/releases/tag/${{ needs.release.outputs.TAG_NAME }} + release_url: https://github.com/Checkmarx/ast-cli-javascript-wrapper/releases/tag/${{ needs.release.outputs.TAG_NAME }} jira_product_name: JAVASCRIPT_WRAPPER secrets: inherit diff --git a/src/main/wrapper/CxConfig.ts b/src/main/wrapper/CxConfig.ts index 1a01118f..41a98b64 100644 --- a/src/main/wrapper/CxConfig.ts +++ b/src/main/wrapper/CxConfig.ts @@ -7,4 +7,5 @@ export class CxConfig { apiKey: string; tenant: string; additionalParameters:string; + agentName: string; } diff --git a/src/main/wrapper/CxConstants.ts b/src/main/wrapper/CxConstants.ts index 7c40bc12..4a71554f 100644 --- a/src/main/wrapper/CxConstants.ts +++ b/src/main/wrapper/CxConstants.ts @@ -116,6 +116,8 @@ export enum CxConstants { CMD_LEARN_MORE = "learn-more", IDE_SCANS_KEY = "scan.config.plugins.ideScans", AI_GUIDED_REMEDIATION_KEY = "scan.config.plugins.aiGuidedRemediation", + STANDALONE_KEY = "scan.config.plugins.cxdevassist", + ASSIST_KEY = "scan.config.plugins.cxoneassist", AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer", TELEMETRY = "telemetry", SUB_CMD_TELEMETRY_AI = "ai", diff --git a/src/main/wrapper/CxWrapper.ts b/src/main/wrapper/CxWrapper.ts index 67ded66c..d5eab111 100644 --- a/src/main/wrapper/CxWrapper.ts +++ b/src/main/wrapper/CxWrapper.ts @@ -55,6 +55,9 @@ export class CxWrapper { if (cxScanConfig.additionalParameters) { this.config.additionalParameters = cxScanConfig.additionalParameters; } + if (cxScanConfig.agentName) { + this.config.agentName = cxScanConfig.agentName; + } } @@ -89,6 +92,10 @@ export class CxWrapper { list.push(param) }) } + if (this.config.agentName) { + list.push(CxConstants.AGENT); + list.push(this.config.agentName); + } if (formatRequired) { list.push(CxConstants.FORMAT); list.push(CxConstants.FORMAT_JSON); @@ -130,7 +137,6 @@ export class CxWrapper { async scanAsca( sourceFile: string, updateVersion = false, - agent?: string | null, ignoredFilePath?: string ): Promise { const commands: string[] = [ @@ -144,12 +150,6 @@ export class CxWrapper { commands.push(CxConstants.ASCA_UPDATE_VERSION); } - if (agent) { - commands.push(CxConstants.AGENT, agent); - } else { - commands.push(CxConstants.AGENT, '"js-wrapper"'); - } - if (ignoredFilePath) { commands.push(CxConstants.IGNORE__FILE_PATH, ignoredFilePath); } @@ -352,8 +352,8 @@ export class CxWrapper { return exec.executeResultsCommandsFile(scanId, CxConstants.FORMAT_HTML, CxConstants.FORMAT_HTML_FILE, commands, this.config.pathToExecutable, fileName); } - async getResults(scanId: string, resultType: string, outputFileName: string, outputFilePath: string, agent?: string | null) { - const commands = this.resultsShow(scanId, resultType, outputFileName, outputFilePath, agent) + async getResults(scanId: string, resultType: string, outputFileName: string, outputFilePath: string) { + const commands = this.resultsShow(scanId, resultType, outputFileName, outputFilePath) const exec = new ExecutionService(); return await exec.executeCommands(this.config.pathToExecutable, commands); } @@ -365,7 +365,7 @@ export class CxWrapper { return await exec.executeCommands(this.config.pathToExecutable, commands, CxConstants.CODE_BASHING_TYPE); } - resultsShow(scanId: string, reportFormat: string, outputFileName: string, outputPath: string, agent?: string | null): string[] { + resultsShow(scanId: string, reportFormat: string, outputFileName: string, outputPath: string): string[] { const commands: string[] = [CxConstants.CMD_RESULT, CxConstants.SUB_CMD_SHOW, CxConstants.SCAN_ID, scanId, CxConstants.REPORT_FORMAT, reportFormat]; if (outputFileName) { commands.push(CxConstants.OUTPUT_NAME); @@ -375,10 +375,6 @@ export class CxWrapper { commands.push(CxConstants.OUTPUT_PATH); commands.push(outputPath); } - if (agent) { - commands.push(CxConstants.AGENT); - commands.push(agent); - } commands.push(...this.initializeCommands(false)); return commands; } @@ -466,6 +462,27 @@ export class CxWrapper { return value?.toLowerCase() === "true"; } + async standaloneEnabled(): Promise { + const commands: string[] = [CxConstants.CMD_UTILS, CxConstants.SUB_CMD_TENANT]; + commands.push(...this.initializeCommands(false)); + + const exec = new ExecutionService(); + const output = await exec.executeMapTenantOutputCommands(this.config.pathToExecutable, commands); + + const value = getTrimmedMapValue(output, CxConstants.STANDALONE_KEY); + return value?.toLowerCase() === "true"; + } + + async cxOneAssistEnabled(): Promise { + const commands: string[] = [CxConstants.CMD_UTILS, CxConstants.SUB_CMD_TENANT]; + commands.push(...this.initializeCommands(false)); + + const exec = new ExecutionService(); + const output = await exec.executeMapTenantOutputCommands(this.config.pathToExecutable, commands); + + const value = getTrimmedMapValue(output, CxConstants.ASSIST_KEY); + return value?.toLowerCase() === "true"; + } async aiMcpServerEnabled(): Promise { const commands: string[] = [CxConstants.CMD_UTILS, CxConstants.SUB_CMD_TENANT]; @@ -530,12 +547,11 @@ export class CxWrapper { return new ExecutionService().executeCommands(this.config.pathToExecutable, commands, CxConstants.MASK_TYPE); } - telemetryAIEvent(aiProvider: string, agent: string, eventType: string, subType: string, engine: string, problemSeverity: string, scanType: string, status: string, totalCount: number): Promise { + telemetryAIEvent(aiProvider: string, eventType: string, subType: string, engine: string, problemSeverity: string, scanType: string, status: string, totalCount: number): Promise { const commands: string[] = [ CxConstants.TELEMETRY, CxConstants.SUB_CMD_TELEMETRY_AI, CxConstants.AI_PROVIDER, aiProvider, - CxConstants.AGENT, agent, CxConstants.TYPE, eventType, CxConstants.SUB_TYPE, subType, CxConstants.ENGINE, engine, diff --git a/src/tests/AssistEnabledTest.test.ts b/src/tests/AssistEnabledTest.test.ts new file mode 100644 index 00000000..00eb5cc7 --- /dev/null +++ b/src/tests/AssistEnabledTest.test.ts @@ -0,0 +1,52 @@ +import { CxWrapper } from '../main/wrapper/CxWrapper'; +import { BaseTest } from './BaseTest'; +import { ExecutionService } from '../main/wrapper/ExecutionService'; +import { CxConstants } from '../main/wrapper/CxConstants'; + +describe('cxOneAssistEnabled tenant setting', () => { + const baseConfig = new BaseTest(); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('returns true when assist key value is true (lowercase)', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.ASSIST_KEY, 'true']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.cxOneAssistEnabled(); + expect(enabled).toBe(true); + }); + + it('returns true when assist key value is TRUE (uppercase)', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.ASSIST_KEY, 'TRUE']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.cxOneAssistEnabled(); + expect(enabled).toBe(true); + }); + + it('returns false when assist key value is false', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.ASSIST_KEY, 'false']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.cxOneAssistEnabled(); + expect(enabled).toBe(false); + }); + + it('returns false when assist key is missing', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.IDE_SCANS_KEY, 'true']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.cxOneAssistEnabled(); + expect(enabled).toBe(false); + }); + + it('trims whitespace around key/value before evaluating', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[` ${CxConstants.ASSIST_KEY} `, ' true ']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.cxOneAssistEnabled(); + expect(enabled).toBe(true); + }); +}); diff --git a/src/tests/BaseTest.ts b/src/tests/BaseTest.ts index 82c7e7c7..d054749b 100644 --- a/src/tests/BaseTest.ts +++ b/src/tests/BaseTest.ts @@ -8,6 +8,7 @@ export class BaseTest { pathToExecutable: string; tenant: string; additionalParameters:string; + agentName:string; constructor() { this.baseUri = process.env["CX_BASE_URI"]; @@ -16,6 +17,7 @@ export class BaseTest { this.clientSecret = process.env["CX_CLIENT_SECRET"]; this.tenant = process.env["CX_TENANT"]; this.apiKey = process.env["CX_APIKEY"]; + this.agentName = "VS Code" this.additionalParameters = "--debug" if (process.env["PATH_TO_EXECUTABLE"] !== null && process.env["PATH_TO_EXECUTABLE"] !== undefined) { this.pathToExecutable = process.env["PATH_TO_EXECUTABLE"]; diff --git a/src/tests/ResultTest.test.ts b/src/tests/ResultTest.test.ts index 2aff2d12..c282ab2c 100644 --- a/src/tests/ResultTest.test.ts +++ b/src/tests/ResultTest.test.ts @@ -20,7 +20,7 @@ describe("Results cases",() => { const cxCommandOutput: CxCommandOutput = await auth.scanList("statuses=Completed"); const sampleId = cxCommandOutput.payload.pop().id; - auth.getResults(sampleId,"json","jsonList", ".", "jswrapper").then(() => { + auth.getResults(sampleId,"json","jsonList", "jswrapper").then(() => { fileExists("./jsonList.json").then(file => expect(file).toBe(true)); }); }); diff --git a/src/tests/ScanTest.test.ts b/src/tests/ScanTest.test.ts index 7ec12696..360e2252 100644 --- a/src/tests/ScanTest.test.ts +++ b/src/tests/ScanTest.test.ts @@ -292,7 +292,6 @@ it.skip('ScanAsca with ignore file should filter one result', async () => { const cxCommandOutput: CxCommandOutput = await wrapper.scanAsca( sourcePath, false, - null, ignoreFile ); diff --git a/src/tests/StandaloneEnabledTest.test.ts b/src/tests/StandaloneEnabledTest.test.ts new file mode 100644 index 00000000..0153d442 --- /dev/null +++ b/src/tests/StandaloneEnabledTest.test.ts @@ -0,0 +1,53 @@ +import { CxWrapper } from '../main/wrapper/CxWrapper'; +import { BaseTest } from './BaseTest'; +import { ExecutionService } from '../main/wrapper/ExecutionService'; +import { CxConstants } from '../main/wrapper/CxConstants'; + +describe('standaloneEnabled tenant setting', () => { + const baseConfig = new BaseTest(); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('returns true when standalone tenant flag is true (lowercase)', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.STANDALONE_KEY, 'true']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.standaloneEnabled(); + expect(enabled).toBe(true); + }); + + it('returns true when standalone tenant flag is TRUE (uppercase)', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.STANDALONE_KEY, 'TRUE']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.standaloneEnabled(); + expect(enabled).toBe(true); + }); + + it('returns false when standalone tenant flag is false', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.STANDALONE_KEY, 'false']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.standaloneEnabled(); + expect(enabled).toBe(false); + }); + + it('returns false when standalone tenant flag key is missing', async () => { + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[CxConstants.IDE_SCANS_KEY, 'true']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.standaloneEnabled(); + expect(enabled).toBe(false); + }); + + it('trims whitespace around key and value before evaluating', async () => { + // Simulate raw output map entries with leading/trailing spaces + jest.spyOn(ExecutionService.prototype, 'executeMapTenantOutputCommands') + .mockResolvedValue(new Map([[` ${CxConstants.STANDALONE_KEY} `, ' true ']])); + const wrapper = new CxWrapper(baseConfig); + const enabled = await wrapper.standaloneEnabled(); + expect(enabled).toBe(true); + }); +}); diff --git a/src/tests/TelemetryTest.test.ts b/src/tests/TelemetryTest.test.ts index df100008..c0471769 100644 --- a/src/tests/TelemetryTest.test.ts +++ b/src/tests/TelemetryTest.test.ts @@ -9,7 +9,6 @@ describe("Telemetry cases", () => { const wrapper = new CxWrapper(cxScanConfig); const cxCommandOutput: CxCommandOutput = await wrapper.telemetryAIEvent( "Cursor", - "Cursos", "click", "ast-results.viewPackageDetails", "secrets", @@ -30,7 +29,6 @@ describe("Telemetry cases", () => { "", "", "", - "", "asca", "Critical", 10 @@ -43,8 +41,7 @@ describe("Telemetry cases", () => { it('TelemetryAIEvent Successful case with edge case parameters', async () => { const wrapper = new CxWrapper(cxScanConfig); const cxCommandOutput: CxCommandOutput = await wrapper.telemetryAIEvent( - "", - "", + "", "", "", "",