diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml
index ced06f58..05185562 100644
--- a/.github/workflows/ci-linux.yml
+++ b/.github/workflows/ci-linux.yml
@@ -43,7 +43,6 @@ jobs:
with:
name: screenshots
path: /Users/runner/work/ast-vscode-extension/ast-vscode-extension/test-resources/screenshots/*.png
-
unit-tests:
strategy:
max-parallel: 1
@@ -64,7 +63,7 @@ jobs:
- name: Run unit tests with coverage
run: npm run unit-coverage
- integration-tests:
+ ui-tests:
strategy:
max-parallel: 1
matrix:
diff --git a/.gitignore b/.gitignore
index b8fe1525..f63837f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ test-resources
.idea/
.DS_Store
.npmrc
+coverage/
diff --git a/package-lock.json b/package-lock.json
index c28b836b..c3aaaf89 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"@types/chai": "4.3.11",
"@types/mocha": "10.0.6",
"@types/node": "^22.9.0",
+ "@types/sinon": "^17.0.3",
"@types/vscode": "^1.50.0",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.2.0",
@@ -1184,6 +1185,21 @@
"integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
"dev": true
},
+ "node_modules/@types/sinon": {
+ "version": "17.0.3",
+ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz",
+ "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==",
+ "dev": true,
+ "dependencies": {
+ "@types/sinonjs__fake-timers": "*"
+ }
+ },
+ "node_modules/@types/sinonjs__fake-timers": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
+ "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
+ "dev": true
+ },
"node_modules/@types/vscode": {
"version": "1.66.0",
"dev": true,
@@ -6649,7 +6665,6 @@
"resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz",
"integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==",
"dev": true,
- "license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.1",
"@sinonjs/fake-timers": "^13.0.2",
diff --git a/package.json b/package.json
index 2cfa1ace..75144812 100644
--- a/package.json
+++ b/package.json
@@ -917,6 +917,7 @@
"@types/chai": "4.3.11",
"@types/mocha": "10.0.6",
"@types/node": "^22.9.0",
+ "@types/sinon": "^17.0.3",
"@types/vscode": "^1.50.0",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.2.0",
diff --git a/src/unit/authValidate.test.ts b/src/unit/authValidate.test.ts
new file mode 100644
index 00000000..167bfa25
--- /dev/null
+++ b/src/unit/authValidate.test.ts
@@ -0,0 +1,40 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import "./mocks/cxWrapper-mock";
+import { cx } from "../cx";
+import { Logs } from "../models/logs";
+
+describe("Cx - authValidate", () => {
+ let logs: Logs;
+
+
+ beforeEach(() => {
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ replace: () => {},
+ name: "Test"
+ };
+
+ logs = {
+ info: () => {},
+ error: () => {},
+ output: mockOutputChannel,
+ log: () => {},
+ warn: () => {},
+ show: () => {}
+ } as Logs;
+ });
+
+ it("should return true when authentication is successful", async () => {
+ // Using valid API key from vscode mock
+ const result = await cx.authValidate(logs);
+ expect(result).to.be.true;
+ });
+
+
+});
\ No newline at end of file
diff --git a/src/unit/commonCommand.test.ts b/src/unit/commonCommand.test.ts
new file mode 100644
index 00000000..c42082e5
--- /dev/null
+++ b/src/unit/commonCommand.test.ts
@@ -0,0 +1,48 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import { CommonCommand } from "../commands/commonCommand";
+import { Logs } from "../models/logs";
+import * as vscode from "vscode";
+import { getCommandsExecuted, clearCommandsExecuted } from "./mocks/vscode-mock";
+
+describe("CommonCommand", () => {
+ let commonCommand: CommonCommand;
+ let logs: Logs;
+ let mockContext: vscode.ExtensionContext;
+
+ beforeEach(() => {
+ clearCommandsExecuted();
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ replace: () => {},
+ name: "Test"
+ };
+
+ logs = {
+ info: () => {},
+ error: () => {},
+ output: mockOutputChannel,
+ log: () => {},
+ warn: () => {},
+ show: () => {}
+ } as Logs;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockContext = { subscriptions: [] } as any;
+
+ commonCommand = new CommonCommand(mockContext, logs);
+ });
+
+ describe("executeCheckSettings", () => {
+ it("should execute setContext command with correct parameters", () => {
+ commonCommand.executeCheckSettings();
+ expect(getCommandsExecuted()).to.include("setContext");
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/unit/details.test.ts b/src/unit/details.test.ts
new file mode 100644
index 00000000..e55a7541
--- /dev/null
+++ b/src/unit/details.test.ts
@@ -0,0 +1,232 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import "./mocks/vscode-mock";
+import * as vscode from "vscode";
+import { expect } from "chai";
+import * as sinon from "sinon";
+import { Details } from "../utils/interface/details";
+import { AstResult } from "../models/results";
+import { constants } from "../utils/common/constants";
+import { messages } from "../utils/common/messages";
+import CxMask from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/mask/CxMask";
+
+describe("Details", () => {
+ let details: Details;
+ let mockContext: vscode.ExtensionContext;
+ let mockResult: AstResult;
+ let sandbox: sinon.SinonSandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+
+ mockContext = {
+ subscriptions: [],
+ extensionUri: vscode.Uri.parse("file:///mock"),
+ extensionPath: "/mock",
+ } as any;
+
+ mockResult = {
+ label: "Test_Result",
+ type: "sast",
+ severity: "HIGH",
+ state: "NEW",
+ description: "Test description",
+ data: {
+ value: "test value",
+ remediation: "test remediation",
+ ruleDescription: "test rule description"
+ },
+ getKicsValues: () => "test kics values",
+ getTitle: () => "
Test Title
",
+ getHtmlDetails: () => "Test Details |
",
+ scaContent: () => "test sca content",
+ scaNode: {
+ packageIdentifier: "test-package"
+ }
+ } as any as AstResult;
+
+ details = new Details(mockResult, mockContext, true);
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe("header", () => {
+ it("should generate correct header HTML", () => {
+ const severityPath = vscode.Uri.parse("file:///mock/severity.png");
+ const html = details.header(severityPath);
+
+ expect(html).to.include("Test Result"); // Checks if underscore is replaced
+ expect(html).to.include(severityPath.toString());
+ expect(html).to.include("header-container");
+ });
+ });
+
+ describe("changes", () => {
+ it("should generate changes section with triage", () => {
+ const html = details.changes("test-class");
+
+ expect(html).to.include("history-container-loader");
+ expect(html).to.include("select_severity");
+ expect(html).to.include("select_state");
+ });
+ });
+
+ describe("triage", () => {
+ it("should generate triage section for SAST", () => {
+ const html = details.triage("test-class");
+
+ expect(html).to.include("select_severity");
+ expect(html).to.include("select_state");
+ expect(html).to.include("Update");
+ expect(html).to.include("comment_box");
+ });
+
+ it("should generate triage section for SCA without comment and update button", () => {
+ mockResult.type = constants.sca;
+ const html = details.triage("test-class");
+
+ expect(html).to.include("select_severity");
+ expect(html).to.include("select_state");
+ expect(html).to.not.include("Update");
+ expect(html).to.not.include("comment_box");
+ });
+ });
+
+ describe("generalTab", () => {
+ it("should generate general tab content", () => {
+ const cxPath = vscode.Uri.parse("file:///mock/cx.png");
+ const html = details.generalTab(cxPath);
+
+ expect(html).to.include("Test description");
+ expect(html).to.include("test kics values");
+ expect(html).to.include("Test Title");
+ expect(html).to.include("Test Details");
+ });
+ });
+
+ describe("secretDetectiongeneralTab", () => {
+ it("should generate secret detection general tab", () => {
+ const html = details.secretDetectiongeneralTab();
+ expect(html).to.include("Test description");
+ });
+ });
+
+ describe("scaView", () => {
+ it("should generate SCA view content", () => {
+ const paths = {
+ severityPath: vscode.Uri.parse("file:///mock/severity.png"),
+ scaAtackVector: vscode.Uri.parse("file:///mock/attack.png"),
+ scaComplexity: vscode.Uri.parse("file:///mock/complexity.png"),
+ scaAuthentication: vscode.Uri.parse("file:///mock/auth.png"),
+ scaConfidentiality: vscode.Uri.parse("file:///mock/confidentiality.png"),
+ scaIntegrity: vscode.Uri.parse("file:///mock/integrity.png"),
+ scaAvailability: vscode.Uri.parse("file:///mock/availability.png"),
+ scaUpgrade: vscode.Uri.parse("file:///mock/upgrade.png"),
+ scaUrl: vscode.Uri.parse("file:///mock/url.png")
+ };
+
+ const html = details.scaView(
+ paths.severityPath,
+ paths.scaAtackVector,
+ paths.scaComplexity,
+ paths.scaAuthentication,
+ paths.scaConfidentiality,
+ paths.scaIntegrity,
+ paths.scaAvailability,
+ paths.scaUpgrade,
+ paths.scaUrl
+ );
+
+ expect(html).to.include("test-package");
+ expect(html).to.include("test sca content");
+ });
+ });
+
+ describe("secretDetectionDetailsRemediationTab", () => {
+ it("should show remediation content when available", () => {
+ const html = details.secretDetectionDetailsRemediationTab();
+ expect(html).to.include("test remediation");
+ });
+
+ it("should show no remediation message when content unavailable", () => {
+ mockResult.data.remediation = undefined;
+ const html = details.secretDetectionDetailsRemediationTab();
+ expect(html).to.include(messages.noRemediationExamplesTab);
+ });
+ });
+
+ describe("secretDetectionDetailsDescriptionTab", () => {
+ it("should show description content when available", () => {
+ const html = details.secretDetectionDetailsDescriptionTab();
+ expect(html).to.include("test rule description");
+ });
+
+ it("should show no description message when content unavailable", () => {
+ mockResult.data.ruleDescription = undefined;
+ const html = details.secretDetectionDetailsDescriptionTab();
+ expect(html).to.include(messages.noDescriptionTab);
+ });
+ });
+
+ describe("generateMaskedSection", () => {
+ it("should generate HTML for masked secrets", () => {
+ const masked: CxMask = {
+ maskedSecrets: [
+ {
+ secret: "password123",
+ masked: "********",
+ line: 42
+ }
+ ]
+ } as CxMask;
+
+ details.masked = masked;
+ const html = details.generateMaskedSection();
+
+ expect(html).to.include("password123");
+ expect(html).to.include("********");
+ expect(html).to.include("Line: 42");
+ });
+
+
+
+ it("should show no secrets message when no secrets are masked", () => {
+ const html = details.generateMaskedSection();
+ expect(html).to.include("No secrets were detected and masked");
+ });
+ });
+
+ describe("tab", () => {
+ it("should generate tabs structure with provided content", () => {
+ const html = details.tab(
+ "tab1 content",
+ "tab2 content",
+ "tab3 content",
+ "Tab 1",
+ "Tab 2",
+ "Tab 3",
+ "Tab 4",
+ "tab4 content",
+ "Tab 6",
+ "tab6 content"
+ );
+
+ expect(html).to.include("tab1 content");
+ expect(html).to.include("tab2 content");
+ expect(html).to.include("tab3 content");
+ expect(html).to.include("tab4 content");
+ expect(html).to.include("tab6 content");
+ expect(html).to.include("Tab 1");
+ expect(html).to.include("Tab 2");
+ expect(html).to.include("Tab 3");
+ expect(html).to.include("Tab 4");
+ expect(html).to.include("Tab 6");
+ });
+
+ it("should handle empty tab content and labels", () => {
+ const html = details.tab("", "", "", "", "", "", "", "", "", "");
+ expect(html.trim()).to.equal("");
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/unit/filterCommand.test.ts b/src/unit/filterCommand.test.ts
new file mode 100644
index 00000000..b2a54219
--- /dev/null
+++ b/src/unit/filterCommand.test.ts
@@ -0,0 +1,76 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import "./mocks/vscode-mock"; // Must be first
+import { expect } from "chai";
+import * as sinon from "sinon";
+import * as vscode from "vscode";
+import { FilterCommand } from "../commands/filterCommand";
+import { Logs } from "../models/logs";
+// import { SeverityLevel, StateLevel, constants } from "../utils/common/constants";
+import { commands } from "../utils/common/commands";
+
+describe("FilterCommand", () => {
+ let filterCommand: FilterCommand;
+ let mockContext: vscode.ExtensionContext;
+ let logs: Logs;
+ let sandbox: sinon.SinonSandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ mockContext = {
+ subscriptions: [],
+ globalState: {
+ get: sinon.stub(),
+ update: sinon.stub().resolves(),
+ },
+ extensionUri: vscode.Uri.file("/mock/path"),
+ extensionPath: "/mock/path"
+ } as any;
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ name: "Mock Output",
+ replace: () => {}
+ } as vscode.OutputChannel;
+
+ logs = new Logs(mockOutputChannel);
+ sinon.stub(logs, "info");
+ sinon.stub(logs, "error");
+ sinon.stub(logs, "log");
+
+ filterCommand = new FilterCommand(mockContext, logs);
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+
+
+
+ describe("registerFilters", () => {
+ it("should register all filter commands", () => {
+ const registerCommandStub = sandbox.stub(vscode.commands, "registerCommand");
+
+ filterCommand.registerFilters();
+
+ const registeredCommands = registerCommandStub.args.map(call => call[0]);
+ expect(registeredCommands).to.include(commands.filterCritical);
+ expect(registeredCommands).to.include(commands.filterHigh);
+ expect(registeredCommands).to.include(commands.filterMedium);
+ expect(registeredCommands).to.include(commands.filterLow);
+ expect(registeredCommands).to.include(commands.filterInfo);
+ expect(registeredCommands).to.include(commands.filterNotExploitable);
+ expect(registeredCommands).to.include(commands.filterProposed);
+ expect(registeredCommands).to.include(commands.filterConfirmed);
+ expect(registeredCommands).to.include(commands.filterToVerify);
+ expect(registeredCommands).to.include(commands.filterUrgent);
+ expect(registeredCommands).to.include(commands.filterNotIgnored);
+ expect(registeredCommands).to.include(commands.filterIgnored);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/unit/getBaseAstConfiguration.test.ts b/src/unit/getBaseAstConfiguration.test.ts
new file mode 100644
index 00000000..ad5b3075
--- /dev/null
+++ b/src/unit/getBaseAstConfiguration.test.ts
@@ -0,0 +1,12 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import "./mocks/cxWrapper-mock";
+import { cx } from "../cx";
+
+describe("Cx - getBaseAstConfiguration", () => {
+ it("should return configuration with additional parameters", async () => {
+ const config = cx.getBaseAstConfiguration();
+ expect(config).to.not.be.undefined;
+ expect(config.additionalParameters).to.equal("valid-api-key");
+ });
+});
\ No newline at end of file
diff --git a/src/unit/getProject.test.ts b/src/unit/getProject.test.ts
new file mode 100644
index 00000000..2cdb3167
--- /dev/null
+++ b/src/unit/getProject.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import "./mocks/cxWrapper-mock";
+import { cx } from "../cx";
+
+describe("Cx - getProject", () => {
+ it("should return project object when projectId is provided", async () => {
+ const projectId = "test-project-id";
+ const result = await cx.getProject(projectId);
+
+ expect(result).to.deep.equal({
+ id: "test-project-id",
+ name: "Test Project",
+ createdAt: "2023-04-19T10:07:37.628413+01:00",
+ updatedAt: "2023-04-19T09:08:27.151913Z",
+ groups: [],
+ tags: {},
+ criticality: 3
+ });
+ });
+
+ it("should return undefined when projectId is not provided", async () => {
+ const result = await cx.getProject(undefined);
+ expect(result).to.be.undefined;
+ });
+});
\ No newline at end of file
diff --git a/src/unit/getResults.test.ts b/src/unit/getResults.test.ts
new file mode 100644
index 00000000..378ff164
--- /dev/null
+++ b/src/unit/getResults.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import "./mocks/cxWrapper-mock";
+import { cx } from "../cx";
+
+describe("Cx - getResults", () => {
+ it("should get results when scanId is provided", async () => {
+ const scanId = "valid-scan-id";
+ await cx.getResults(scanId);
+ // Since getResults doesn't return anything, we just verify it doesn't throw
+ });
+
+ it("should return undefined when scanId is not provided", async () => {
+ const result = await cx.getResults(undefined);
+ expect(result).to.be.undefined;
+ });
+
+ it("should throw error when getting results fails", async () => {
+ const scanId = "invalid-scan-id";
+
+ try {
+ await cx.getResults(scanId);
+ expect.fail("Expected error was not thrown");
+ } catch (error) {
+ expect(error.message).to.equal("Failed to get results");
+ }
+ });
+});
\ No newline at end of file
diff --git a/src/unit/gptView.test.ts b/src/unit/gptView.test.ts
new file mode 100644
index 00000000..354c6931
--- /dev/null
+++ b/src/unit/gptView.test.ts
@@ -0,0 +1,145 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import "./mocks/vscode-mock"; // Ensure mock is loaded first
+import * as vscode from "vscode";
+import { expect } from "chai";
+import * as sinon from "sinon";
+import { GptView } from "../views/gptView/gptView";
+import { GptResult } from "../models/gptResult";
+import { constants } from "../utils/common/constants";
+
+describe("GptView", () => {
+ let context: vscode.ExtensionContext;
+ let extensionUri: vscode.Uri;
+ let gptView: GptView;
+ let gptResult: GptResult;
+ let mockWebview: vscode.Webview;
+
+ beforeEach(() => {
+ context = {
+ subscriptions: [],
+ extensionUri: vscode.Uri.parse("file:///mock"),
+ extensionPath: "/mock",
+ } as any;
+
+ extensionUri = vscode.Uri.parse("file:///mock");
+
+ // ✅ Fix: Provide valid mock objects for GptResult
+ gptResult = new GptResult(
+ {
+ fileName: "mockFile.js",
+ type: "sast",
+ id: "mock-result-id",
+ severity: "High",
+ label: "Mock Label",
+ kicsNode: { data: { filename: "mockFile.js", line: 5 } },
+ } as any,
+ {
+ files: [{ file_name: "mockFile.js", line: 5 }],
+ severity: "High",
+ query_name: "Mock Query",
+ } as any
+ );
+
+ gptView = new GptView(extensionUri, gptResult, context, false);
+
+ // Mock webview with proper asWebviewUri implementation
+ mockWebview = {
+ asWebviewUri: (uri: vscode.Uri) => ({
+ ...uri,
+ toString: () => `mock-uri://${uri.path}`
+ }),
+ options: {},
+ html: "",
+ onDidReceiveMessage: sinon.stub(),
+ postMessage: sinon.stub(),
+ } as any;
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should initialize GptView correctly", () => {
+ expect(gptView).to.be.instanceOf(GptView);
+ expect(gptView.getLoad()).to.be.false;
+ expect(gptView.getResult()).to.deep.equal(gptResult);
+ });
+
+ it("should set and get the result", () => {
+ const newResult = new GptResult(
+ {
+ fileName: "newMockFile.js",
+ type: "sast",
+ id: "new-mock-result-id",
+ severity: "Medium",
+ label: "New Mock Label",
+ } as any,
+ {
+ files: [{ file_name: "newMockFile.js", line: 10 }],
+ severity: "Medium",
+ query_name: "New Mock Query",
+ } as any
+ );
+
+ gptView.setResult(newResult);
+ expect(gptView.getResult()).to.equal(newResult);
+ });
+
+ it("should set and get loadChanges state", () => {
+ gptView.setLoad(true);
+ expect(gptView.getLoad()).to.be.true;
+ });
+
+ it("should set webview and generate HTML content", async () => {
+ const mockWebviewView = {
+ webview: mockWebview,
+ } as any;
+
+ await gptView.resolveWebviewView(mockWebviewView, {} as any, {} as any);
+
+ expect(mockWebview.options.enableScripts).to.be.true;
+ expect(mockWebview.options.localResourceRoots).to.deep.equal([extensionUri]);
+ expect(mockWebview.html).to.be.a("string");
+ expect(mockWebview.html).to.include(constants.aiSecurityChampion);
+ });
+
+ it("should get AskKicsIcon and KicsUserIcon URIs", async () => {
+ const mockWebviewView = {
+ webview: mockWebview,
+ } as any;
+
+ await gptView.resolveWebviewView(mockWebviewView, {} as any, {} as any);
+
+ const kicsIcon = gptView.getAskKicsIcon();
+ const userIcon = gptView.getAskKicsUserIcon();
+
+ expect(kicsIcon).to.not.be.undefined;
+ expect(userIcon).to.not.be.undefined;
+ expect(kicsIcon.toString()).to.include("kics.png");
+ expect(userIcon.toString()).to.include("userKics.png");
+ });
+
+ it("should return valid webview instance", () => {
+ expect(gptView.getWebView()).to.be.undefined; // Initially undefined
+ });
+
+ it("should generate masked secrets section correctly", () => {
+ const maskedData = {
+ maskedSecrets: [
+ { secret: "password123", masked: "******", line: 10 },
+ ],
+ } as any;
+
+ gptView = new GptView(extensionUri, gptResult, context, false, undefined, maskedData);
+ const html = gptView.generateMaskedSection();
+
+ expect(html).to.include("password123");
+ expect(html).to.include("******");
+ expect(html).to.include("Line: 10");
+ });
+
+ it("should return default message if no secrets are masked", () => {
+ const html = gptView.generateMaskedSection();
+ expect(html).to.include("No secrets were detected and masked");
+ });
+});
diff --git a/src/unit/groupByCommand.test.ts b/src/unit/groupByCommand.test.ts
new file mode 100644
index 00000000..cadffef4
--- /dev/null
+++ b/src/unit/groupByCommand.test.ts
@@ -0,0 +1,87 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import "./mocks/vscode-mock";
+import { expect } from "chai";
+import * as sinon from "sinon";
+import * as vscode from "vscode";
+import { GroupByCommand } from "../commands/groupByCommand";
+import { Logs } from "../models/logs";
+import { commands } from "../utils/common/commands";
+import { GroupBy, constants } from "../utils/common/constants";
+
+describe("GroupByCommand", () => {
+ let groupByCommand: GroupByCommand;
+ let mockContext: vscode.ExtensionContext;
+ let logs: Logs;
+ let sandbox: sinon.SinonSandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ mockContext = {
+ subscriptions: [],
+ globalState: {
+ get: sinon.stub(),
+ update: sinon.stub().resolves(),
+ },
+ extensionUri: vscode.Uri.file("/mock/path"),
+ extensionPath: "/mock/path"
+ } as any;
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ name: "Mock Output",
+ replace: () => {}
+ } as vscode.OutputChannel;
+
+ logs = new Logs(mockOutputChannel);
+ sinon.stub(logs, "info");
+ sinon.stub(logs, "error");
+ sinon.stub(logs, "log");
+
+ groupByCommand = new GroupByCommand(mockContext, logs);
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe("registerGroupBy", () => {
+ it("should register all group by commands", () => {
+ const registerCommandStub = sandbox.stub(vscode.commands, "registerCommand");
+
+ groupByCommand.registerGroupBy();
+
+ const registeredCommands = registerCommandStub.args.map(call => call[0]);
+ expect(registeredCommands).to.include(commands.groupByQueryName);
+ expect(registeredCommands).to.include(commands.groupByLanguage);
+ expect(registeredCommands).to.include(commands.groupBySeverity);
+ expect(registeredCommands).to.include(commands.groupByStatus);
+ expect(registeredCommands).to.include(commands.groupByState);
+ expect(registeredCommands).to.include(commands.groupByFile);
+ expect(registeredCommands).to.include(commands.groupByDirectDependency);
+ });
+ });
+
+
+
+ describe("group", () => {
+ it("should update group by value and refresh tree", async () => {
+ const executeCommandStub = sandbox.stub(vscode.commands, "executeCommand").resolves();
+
+ await groupByCommand["group"](
+ logs,
+ mockContext,
+ GroupBy.severity,
+ constants.severityGroup
+ );
+
+ const updateStub = mockContext.globalState.update as sinon.SinonStub;
+ expect(updateStub.args).to.deep.include([constants.severityGroup, true]);
+ expect(executeCommandStub.calledWith(commands.refreshTree)).to.be.true;
+ });
+ });
+});
diff --git a/src/unit/kicsRealtimeCommand.test.ts b/src/unit/kicsRealtimeCommand.test.ts
new file mode 100644
index 00000000..6d8a23f3
--- /dev/null
+++ b/src/unit/kicsRealtimeCommand.test.ts
@@ -0,0 +1,61 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import { KICSRealtimeCommand } from "../commands/kicsRealtimeCommand";
+import { Logs } from "../models/logs";
+import * as vscode from "vscode";
+import { clearCommandsExecuted } from "./mocks/vscode-mock";
+import { KicsProvider } from "../kics/kicsRealtimeProvider";
+
+describe("KICSRealtimeCommand", () => {
+ let kicsCommand: KICSRealtimeCommand;
+ let logs: Logs;
+ let mockContext: vscode.ExtensionContext;
+ let mockKicsProvider: KicsProvider;
+
+
+ beforeEach(() => {
+ clearCommandsExecuted();
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ replace: () => {},
+ name: "Test"
+ };
+
+ logs = {
+ info: () => {},
+ error: () => {},
+ output: mockOutputChannel,
+ log: () => {},
+ warn: () => {},
+ show: () => {}
+ } as Logs;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockContext = { subscriptions: [] } as any;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockKicsProvider = { runKicsIfEnabled: async () => {} } as any;
+
+ kicsCommand = new KICSRealtimeCommand(mockContext, mockKicsProvider, logs);
+ });
+
+ describe("registerKicsScans", () => {
+ it("should register kics scan command", () => {
+ kicsCommand.registerKicsScans();
+ expect(mockContext.subscriptions.length).to.be.greaterThan(0);
+ });
+ });
+
+ describe("registerSettings", () => {
+ it("should register kics settings command", () => {
+ kicsCommand.registerSettings();
+ expect(mockContext.subscriptions.length).to.be.greaterThan(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/unit/mocks/cxWrapper-mock.ts b/src/unit/mocks/cxWrapper-mock.ts
index 1e8bbc9c..51d0c208 100644
--- a/src/unit/mocks/cxWrapper-mock.ts
+++ b/src/unit/mocks/cxWrapper-mock.ts
@@ -1,6 +1,15 @@
import mockRequire from "mock-require";
+import { CxParamType } from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/wrapper/CxParamType";
+import { CxConfig } from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/wrapper/CxConfig";
+
mockRequire("@checkmarxdev/ast-cli-javascript-wrapper", {
CxWrapper: class {
+ config: CxConfig;
+
+ constructor(config?: CxConfig) {
+ this.config = config || new CxConfig();
+ }
+
async scanShow(scanId: string) {
if (scanId === "1") {
return {
@@ -26,5 +35,79 @@ mockRequire("@checkmarxdev/ast-cli-javascript-wrapper", {
};
}
}
+
+ async projectShow(projectId: string) {
+ if (projectId === "test-project-id") {
+ return {
+ payload: [
+ {
+ id: "test-project-id",
+ name: "Test Project",
+ createdAt: "2023-04-19T10:07:37.628413+01:00",
+ updatedAt: "2023-04-19T09:08:27.151913Z",
+ groups: [],
+ tags: {},
+ criticality: 3
+ }
+ ],
+ exitCode: 0
+ };
+ } else {
+ return {
+ status: "Project not found",
+ payload: [],
+ exitCode: 1
+ };
+ }
+ }
+
+ async scanCreate(params: Map) {
+ if (params.get(CxParamType.PROJECT_NAME) === "test-project" &&
+ params.get(CxParamType.BRANCH) === "main") {
+ return {
+ payload: [
+ {
+ id: "scan-123",
+ status: "Created",
+ projectId: "test-project-id",
+ branch: "main",
+ createdAt: "2023-04-19T10:07:37.628413+01:00"
+ }
+ ],
+ exitCode: 0
+ };
+ } else {
+ return {
+ status: "Failed to create scan",
+ payload: undefined,
+ exitCode: 1
+ };
+ }
+ }
+
+ async getResults(scanId: string, fileExtension: string, fileName: string, filePath: string, agent: string) {
+ if (scanId === "valid-scan-id") {
+ return {
+ payload: "Results retrieved successfully",
+ exitCode: 0
+ };
+ } else {
+ throw new Error("Failed to get results");
+ }
+ }
+
+ async authValidate() {
+ if (this.config?.apiKey === "valid-api-key") {
+ return {
+ exitCode: 0,
+ status: "Authenticated successfully"
+ };
+ } else {
+ return {
+ exitCode: 1,
+ status: "Authentication failed"
+ };
+ }
+ }
},
-});
+});
\ No newline at end of file
diff --git a/src/unit/mocks/vscode-mock.ts b/src/unit/mocks/vscode-mock.ts
index c1ba54c8..353003fd 100644
--- a/src/unit/mocks/vscode-mock.ts
+++ b/src/unit/mocks/vscode-mock.ts
@@ -1,6 +1,11 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable @typescript-eslint/naming-convention */
import mockRequire from "mock-require";
import { constants } from "../../utils/common/constants";
-import sinon from "sinon";
+import * as sinon from "sinon";
+
+let commandsExecuted: string[] = [];
const mockDiagnosticCollection = {
set: sinon.stub(),
@@ -14,14 +19,17 @@ function resetMocks() {
mockDiagnosticCollection.clear.reset();
}
-const mockVscode = {
+const mock = {
workspace: {
getConfiguration: (section: string) => {
if (section === "checkmarxOne") {
return {
get: (key: string) => {
if (key === constants.apiKey) {
- return constants.apiKey;
+ return "valid-api-key";
+ }
+ if (key === "additionalParams") {
+ return "valid-api-key";
}
return undefined;
},
@@ -30,12 +38,49 @@ const mockVscode = {
return undefined;
},
workspaceFolders: [{ uri: { fsPath: "/mock/path" } }],
+ openTextDocument: () => Promise.resolve({
+ // Mock document properties
+ }),
+ findFiles: () => Promise.resolve([])
},
- ProgressLocation: {
- Notification: "Notification",
+ window: {
+ showErrorMessage: () => Promise.resolve(),
+ showInformationMessage: () => Promise.resolve(),
+ createOutputChannel: () => ({
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ replace: () => {},
+ name: "Test"
+ }),
+ createWebviewPanel: () => ({
+ webview: {
+ html: "",
+ asWebviewUri: (uri: any) => uri,
+ onDidReceiveMessage: () => ({ dispose: () => {} }),
+ postMessage: () => Promise.resolve()
+ },
+ reveal: () => {},
+ dispose: () => {},
+ onDidDispose: () => ({ dispose: () => {} })
+ })
},
- // Add these for ASCA diagnostics
+
+ commands: {
+ executeCommand: (command: string) => {
+ commandsExecuted.push(command);
+ return Promise.resolve();
+ },
+ getCommands: () => Promise.resolve([]),
+ registerCommand: (command: string, callback: (...args: any[]) => any) => {
+ return { dispose: () => {} };
+ }
+ },
+
languages: {
createDiagnosticCollection: () => mockDiagnosticCollection
},
@@ -43,11 +88,14 @@ const mockVscode = {
DiagnosticSeverity: {
Error: 0,
Warning: 1,
- Information: 2
+ Information: 2,
+ Hint: 3
},
Position: class Position {
constructor(public line: number, public character: number) {}
+ translate() { return this; }
+ with() { return this; }
},
Range: class Range {
@@ -55,18 +103,64 @@ const mockVscode = {
public start: { line: number; character: number },
public end: { line: number; character: number }
) {}
+ with() { return this; }
},
Diagnostic: class Diagnostic {
source: string | undefined;
+ code?: string | number;
+ relatedInformation?: any[];
+ tags?: any[];
+
constructor(
public range: { start: { line: number; character: number }; end: { line: number; character: number } },
public message: string,
public severity: number
) {}
+ },
+
+ ProgressLocation: {
+ Notification: "Notification",
+ },
+
+ Uri: {
+ file: (path: string) => ({
+ fsPath: path,
+ scheme: 'file',
+ path: path
+ }),
+ parse: (path: string) => ({
+ fsPath: path,
+ scheme: 'file',
+ path: path
+ }),
+ joinPath: (uri: any, ...pathSegments: string[]) => ({
+ fsPath: pathSegments.join('/'),
+ scheme: 'file',
+ path: pathSegments.join('/')
+ })
+ },
+
+ // 🔹 **תוספת של TreeItem**
+ TreeItem: class {
+ label: string;
+ collapsibleState: any;
+
+ constructor(label: string, collapsibleState: any) {
+ this.label = label;
+ this.collapsibleState = collapsibleState;
+ }
+ },
+
+ TreeItemCollapsibleState: {
+ None: 0,
+ Collapsed: 1,
+ Expanded: 2
}
};
-mockRequire("vscode", mockVscode);
+mockRequire("vscode", mock);
-export { mockDiagnosticCollection, mockVscode, resetMocks };
+export { mockDiagnosticCollection, resetMocks };
+export const getCommandsExecuted = () => commandsExecuted;
+export const clearCommandsExecuted = () => { commandsExecuted = []; };
diff --git a/src/unit/pickerCommand.test.ts b/src/unit/pickerCommand.test.ts
new file mode 100644
index 00000000..4540732b
--- /dev/null
+++ b/src/unit/pickerCommand.test.ts
@@ -0,0 +1,140 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import "./mocks/vscode-mock";
+import { expect } from "chai";
+import * as sinon from "sinon";
+import * as vscode from "vscode";
+import { PickerCommand } from "../commands/pickerCommand";
+import { Logs } from "../models/logs";
+import { commands } from "../utils/common/commands";
+// import { projectPicker, branchPicker, scanPicker, scanInput } from "../utils/pickers/pickers";
+import { AstResultsProvider } from "../views/resultsView/astResultsProvider";
+
+describe("PickerCommand", () => {
+ let pickerCommand: PickerCommand;
+ let mockContext: vscode.ExtensionContext;
+ let logs: Logs;
+ let sandbox: sinon.SinonSandbox;
+ let resultsProvider: AstResultsProvider;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ mockContext = {
+ subscriptions: [],
+ globalState: {
+ get: sinon.stub(),
+ update: sinon.stub().resolves(),
+ },
+ extensionUri: vscode.Uri.file("/mock/path"),
+ extensionPath: "/mock/path"
+ } as any;
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ name: "Mock Output",
+ replace: () => {}
+ } as vscode.OutputChannel;
+
+ logs = new Logs(mockOutputChannel);
+ sinon.stub(logs, "info");
+ sinon.stub(logs, "error");
+ sinon.stub(logs, "log");
+
+ resultsProvider = {} as AstResultsProvider;
+ pickerCommand = new PickerCommand(mockContext, logs, resultsProvider);
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe("registerPickerCommands", () => {
+ it("should register all picker commands", () => {
+ const registerCommandStub = sandbox.stub(vscode.commands, "registerCommand");
+
+ pickerCommand.registerPickerCommands();
+
+ expect(registerCommandStub.calledWith(commands.generalPick)).to.be.true;
+ expect(registerCommandStub.calledWith(commands.projectPick)).to.be.true;
+ expect(registerCommandStub.calledWith(commands.branchPick)).to.be.true;
+ expect(registerCommandStub.calledWith(commands.scanPick)).to.be.true;
+ expect(registerCommandStub.calledWith(commands.scanInput)).to.be.true;
+ });
+ });
+
+ describe("projectPicker", () => {
+ // it("should call projectPicker function", async () => {
+ // const projectPickerStub = sandbox.stub().resolves();
+ // sandbox.stub(vscode.commands, "registerCommand").callsFake((command, callback) => {
+ // if (command === commands.projectPick) {
+ // callback();
+ // }
+ // return {} as vscode.Disposable;
+ // });
+
+ // // sandbox.stub(projectPicker, "default").returns(Promise.resolve());
+
+ // await pickerCommand.registerPickerCommands();
+
+ // expect(projectPickerStub.calledOnce).to.be.true;
+ // });
+ });
+
+ describe("branchPicker", () => {
+ // it("should call branchPicker function", async () => {
+ // const branchPickerStub = sandbox.stub().resolves();
+ // sandbox.stub(vscode.commands, "registerCommand").callsFake((command, callback) => {
+ // if (command === commands.branchPick) {
+ // callback();
+ // }
+ // return {} as vscode.Disposable;
+ // });
+
+ // // sandbox.stub(branchPicker).resolves();
+
+ // await pickerCommand.registerPickerCommands();
+
+ // expect(branchPickerStub.calledOnce).to.be.true;
+ // });
+ });
+
+ describe("scanPicker", () => {
+ // it("should call scanPicker function", async () => {
+ // const scanPickerStub = sandbox.stub().resolves();
+ // sandbox.stub(vscode.commands, "registerCommand").callsFake((command, callback) => {
+ // if (command === commands.scanPick) {
+ // callback();
+ // }
+ // return {} as vscode.Disposable;
+ // });
+
+ // // sandbox.stub(scanPicker).resolves();
+
+ // await pickerCommand.registerPickerCommands();
+
+ // expect(scanPickerStub.calledOnce).to.be.true;
+ // });
+ });
+
+ describe("scanInput", () => {
+ // it("should call scanInput function", async () => {
+ // const scanInputStub = sandbox.stub().resolves();
+ // sandbox.stub(vscode.commands, "registerCommand").callsFake((command, callback) => {
+ // if (command === commands.scanInput) {
+ // callback();
+ // }
+ // return {} as vscode.Disposable;
+ // });
+
+ // // sandbox.stub(scanInput).resolves();
+
+ // await pickerCommand.registerPickerCommands();
+
+ // expect(scanInputStub.calledOnce).to.be.true;
+ // });
+ });
+});
diff --git a/src/unit/scanCommand.test.ts b/src/unit/scanCommand.test.ts
new file mode 100644
index 00000000..0d1116cb
--- /dev/null
+++ b/src/unit/scanCommand.test.ts
@@ -0,0 +1,64 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import { ScanCommand } from "../commands/scanCommand";
+import { Logs } from "../models/logs";
+import * as vscode from "vscode";
+import { clearCommandsExecuted } from "./mocks/vscode-mock";
+
+describe("ScanCommand", () => {
+ let scanCommand: ScanCommand;
+ let logs: Logs;
+ let mockContext: vscode.ExtensionContext;
+ let mockStatusBar: vscode.StatusBarItem;
+ let scanStarted = false;
+
+ beforeEach(() => {
+ clearCommandsExecuted();
+ scanStarted = false;
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ replace: () => {},
+ name: "Test"
+ };
+
+ logs = {
+ info: () => {},
+ error: () => {},
+ output: mockOutputChannel,
+ log: () => {},
+ warn: () => {},
+ show: () => {}
+ } as Logs;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockContext = { subscriptions: [] } as any;
+
+ mockStatusBar = {
+ show: () => {},
+ hide: () => {},
+ dispose: () => {}
+ } as vscode.StatusBarItem;
+
+ scanCommand = new ScanCommand(mockContext, mockStatusBar, logs);
+ });
+
+ describe("registerIdeScans", () => {
+ it("should register scan commands", () => {
+ scanCommand.registerIdeScans();
+ expect(mockContext.subscriptions.length).to.equal(3); // createScan, cancelScan, pollScan
+ });
+ });
+
+ describe("executePollScan", () => {
+ it("should execute poll scan command", () => {
+ scanCommand.executePollScan();
+ expect(scanStarted).to.be.false;
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/unit/scanCreate.test.ts b/src/unit/scanCreate.test.ts
new file mode 100644
index 00000000..1326b903
--- /dev/null
+++ b/src/unit/scanCreate.test.ts
@@ -0,0 +1,32 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import "./mocks/cxWrapper-mock";
+import { cx } from "../cx";
+
+describe("Cx - scanCreate", () => {
+ it("should create scan when all parameters are provided", async () => {
+ const projectName = "test-project";
+ const branchName = "main";
+ const sourcePath = "/test/path";
+
+ const result = await cx.scanCreate(projectName, branchName, sourcePath);
+
+ expect(result).to.deep.equal({
+ id: "scan-123",
+ status: "Created",
+ projectId: "test-project-id",
+ branch: "main",
+ createdAt: "2023-04-19T10:07:37.628413+01:00"
+ });
+ });
+
+ it("should return undefined when projectName is not provided", async () => {
+ const result = await cx.scanCreate(undefined, "main", "/test/path");
+ expect(result).to.be.undefined;
+ });
+
+ it("should return undefined when branchName is not provided", async () => {
+ const result = await cx.scanCreate("test-project", undefined, "/test/path");
+ expect(result).to.be.undefined;
+ });
+});
\ No newline at end of file
diff --git a/src/unit/treeCommand.test.ts b/src/unit/treeCommand.test.ts
new file mode 100644
index 00000000..cca94b2c
--- /dev/null
+++ b/src/unit/treeCommand.test.ts
@@ -0,0 +1,64 @@
+import { expect } from "chai";
+import "./mocks/vscode-mock";
+import { TreeCommand } from "../commands/treeCommand";
+import { Logs } from "../models/logs";
+import * as vscode from "vscode";
+import { clearCommandsExecuted } from "./mocks/vscode-mock";
+import { AstResultsProvider } from "../views/resultsView/astResultsProvider";
+import { SCAResultsProvider } from "../views/scaView/scaResultsProvider";
+
+describe("TreeCommand", () => {
+ let treeCommand: TreeCommand;
+ let logs: Logs;
+ let mockContext: vscode.ExtensionContext;
+ let mockAstProvider: AstResultsProvider;
+ let mockScaProvider: SCAResultsProvider;
+
+ beforeEach(() => {
+ clearCommandsExecuted();
+
+ const mockOutputChannel = {
+ append: () => {},
+ appendLine: () => {},
+ clear: () => {},
+ show: () => {},
+ hide: () => {},
+ dispose: () => {},
+ replace: () => {},
+ name: "Test"
+ };
+
+ logs = {
+ info: () => {},
+ error: () => {},
+ output: mockOutputChannel,
+ log: () => {},
+ warn: () => {},
+ show: () => {}
+ } as Logs;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockContext = { subscriptions: [] } as any;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockAstProvider = { refreshData: async () => {}, clean: async () => {} } as any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ mockScaProvider = { refreshData: async () => {}, clean: async () => {} } as any;
+
+ treeCommand = new TreeCommand(mockContext, mockAstProvider, mockScaProvider, logs);
+ });
+
+ describe("registerRefreshCommands", () => {
+ it("should register refresh commands", () => {
+ treeCommand.registerRefreshCommands();
+ expect(mockContext.subscriptions.length).to.be.greaterThan(0);
+ });
+ });
+
+ describe("registerClearCommands", () => {
+ it("should register clear commands", () => {
+ treeCommand.registerClearCommands();
+ expect(mockContext.subscriptions.length).to.be.greaterThan(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/unit/webViewCommand.test.ts b/src/unit/webViewCommand.test.ts
new file mode 100644
index 00000000..4f2ab87a
--- /dev/null
+++ b/src/unit/webViewCommand.test.ts
@@ -0,0 +1,102 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import "./mocks/vscode-mock"; // Must be first
+import * as vscode from "vscode";
+import { WebViewCommand } from "../commands/webViewCommand";
+import { Logs } from "../models/logs";
+import { AstResultsProvider } from "../views/resultsView/astResultsProvider";
+import { expect } from "chai";
+import * as sinon from "sinon";
+import { commands } from "../utils/common/commands";
+
+describe("WebViewCommand", () => {
+ let webViewCommand: WebViewCommand;
+ let context: vscode.ExtensionContext;
+ let logs: Logs;
+ let resultsProvider: AstResultsProvider;
+
+ beforeEach(() => {
+ context = {
+ subscriptions: [],
+ extensionUri: vscode.Uri.parse("file:///mock"),
+ extensionPath: "/mock",
+ } as any;
+ logs = sinon.createStubInstance(Logs);
+ resultsProvider = sinon.createStubInstance(AstResultsProvider);
+
+ webViewCommand = new WebViewCommand(context, logs, resultsProvider);
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should register new details command", async () => {
+ const registerCommandStub = sinon.stub(vscode.commands, "registerCommand");
+
+ webViewCommand.registerNewDetails();
+
+ expect(registerCommandStub.calledWith(commands.newDetails)).to.be.true; // ✅ Fixed reference
+ });
+
+ it("should register GPT command", async () => {
+ const registerCommandStub = sinon.stub(vscode.commands, "registerCommand");
+
+ webViewCommand.registerGpt();
+
+ expect(registerCommandStub.calledWith(commands.gpt)).to.be.true; // ✅ Fixed reference
+ });
+
+ // it("should create a WebviewPanel when registering new details", async () => {
+ // const createWebviewPanelStub = sinon.stub(vscode.window, "createWebviewPanel");
+ // const mockPanel = {
+ // webview: {
+ // html: "",
+ // onDidReceiveMessage: sinon.stub(),
+ // postMessage: sinon.stub(),
+ // },
+ // dispose: sinon.stub(),
+ // onDidDispose: sinon.stub(),
+ // };
+ // createWebviewPanelStub.returns(mockPanel as any);
+
+ // webViewCommand.registerNewDetails();
+
+ // await vscode.commands.executeCommand(commands.newDetails, {
+ // severity: "High",
+ // label: "Mock Issue",
+ // });
+
+ // expect(createWebviewPanelStub.called).to.be.true;
+ // });
+
+ // it("should handle messages sent to Webview", async () => {
+ // const mockPanel = {
+ // webview: {
+ // onDidReceiveMessage: sinon.stub(),
+ // postMessage: sinon.stub(),
+ // },
+ // dispose: sinon.stub(),
+ // onDidDispose: sinon.stub(),
+ // };
+
+ // (webViewCommand as any).detailsPanel = mockPanel as any;
+
+ // await vscode.commands.executeCommand(commands.newDetails, {
+ // severity: "High",
+ // label: "Mock Issue",
+ // });
+
+ // expect(mockPanel.webview.onDidReceiveMessage.called).to.be.true;
+ // });
+
+ it("should dispose WebviewPanel on close", () => {
+ const disposeStub = sinon.stub();
+ (webViewCommand as any).detailsPanel = {
+ dispose: disposeStub,
+ } as any;
+
+ (webViewCommand as any).detailsPanel.dispose();
+
+ expect(disposeStub.called).to.be.true;
+ });
+});