From f1f477ea13b04181321a5920a7ad676e0c56edbb Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 14:16:43 +0200 Subject: [PATCH 1/9] TA-4324: Add validation for git profile usage without git branch --- src/commands/configuration-management/module.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 9020ef4a..b4a41896 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -85,6 +85,9 @@ class Module extends IModule { if ((options.packageKeys && options.keysByVersion) || (!options.packageKeys && !options.keysByVersion)) { throw new Error("Please provide either --packageKeys or --keysByVersion, but not both."); } + if (options.gitProfile && !options.gitBranch) { + throw new Error("Please specify a branch using --gitBranch when using a Git profile."); + } options.withDependencies = options.withDependencies ?? false; await new ConfigCommandService(context).batchExportPackages(options.packageKeys, options.keysByVersion, options.withDependencies, options.gitBranch, options.unzip); } @@ -94,6 +97,9 @@ class Module extends IModule { } private async batchImportPackages(context: Context, command: Command, options: OptionValues): Promise { + if (options.gitProfile && !options.gitBranch) { + throw new Error("Please specify a branch using --gitBranch when using a Git profile."); + } await new ConfigCommandService(context).batchImportPackages(options.file, options.directory, options.overwrite, options.gitBranch); } From 0cbd196740eaa2816e7465eb0d33331ecd853254 Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 16:54:21 +0200 Subject: [PATCH 2/9] TA-4324: Add unit tests for module file --- .../configuration-management/module.spec.ts | 361 ++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 tests/commands/configuration-management/module.spec.ts diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts new file mode 100644 index 00000000..743936c0 --- /dev/null +++ b/tests/commands/configuration-management/module.spec.ts @@ -0,0 +1,361 @@ +/** + * Unit tests for configuration-management module action method validations + */ + +import Module = require("../../../src/commands/configuration-management/module"); +import { Context } from "../../../src/core/command/cli-context"; +import { Command, OptionValues } from "commander"; +import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; + +jest.mock("../../../src/commands/configuration-management/config-command.service"); + +describe("Configuration Management Module - Action Validations", () => { + let module: Module; + let mockContext: Context; + let mockCommand: Command; + let mockConfigCommandService: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + module = new Module(); + mockContext = {} as Context; + mockCommand = {} as Command; + + mockConfigCommandService = { + batchExportPackages: jest.fn().mockResolvedValue(undefined), + batchImportPackages: jest.fn().mockResolvedValue(undefined), + } as any; + + (ConfigCommandService as jest.MockedClass).mockImplementation(() => mockConfigCommandService); + }); + + describe("batchExportPackages validation", () => { + describe("packageKeys and keysByVersion validation", () => { + it("should throw error when both packageKeys and keysByVersion are provided", async () => { + const options: OptionValues = { + packageKeys: ["package1", "package2"], + keysByVersion: ["package3:v1", "package4:v2"], + }; + + await expect( + (module as any).batchExportPackages(mockContext, mockCommand, options) + ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); + + // Ensure the service method was never called + expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("should throw error when neither packageKeys nor keysByVersion are provided", async () => { + const options: OptionValues = {}; + + await expect( + (module as any).batchExportPackages(mockContext, mockCommand, options) + ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); + + // Ensure the service method was never called + expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("should pass validation when only packageKeys is provided", async () => { + const options: OptionValues = { + packageKeys: ["package1", "package2"], + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1", "package2"], + undefined, + false, + undefined, + undefined + ); + }); + + it("should pass validation when only keysByVersion is provided", async () => { + const options: OptionValues = { + keysByVersion: ["package3:v1", "package4:v2"], + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + undefined, + ["package3:v1", "package4:v2"], + false, + undefined, + undefined + ); + }); + }); + + describe("gitProfile and gitBranch validation", () => { + it("should throw error when gitProfile is provided without gitBranch", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + gitProfile: "myProfile", + }; + + await expect( + (module as any).batchExportPackages(mockContext, mockCommand, options) + ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); + + // Ensure the service method was never called + expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("should pass validation when gitProfile is provided with gitBranch", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + gitProfile: "myProfile", + gitBranch: "main", + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + false, + "main", + undefined + ); + }); + + it("should pass validation when gitBranch is provided without gitProfile", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + gitBranch: "main", + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + false, + "main", + undefined + ); + }); + + it("should pass validation when neither gitProfile nor gitBranch are provided", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalled(); + }); + }); + + describe("withDependencies option", () => { + it("should default withDependencies to false when not provided", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + false, + undefined, + undefined + ); + }); + + it("should pass withDependencies as true when provided", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + withDependencies: true, + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + true, + undefined, + undefined + ); + }); + + it("should pass unzip option when provided", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + unzip: true, + }; + + await (module as any).batchExportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + false, + undefined, + true + ); + }); + }); + + describe("combined validation scenarios", () => { + it("should fail on first validation error (packageKeys conflict) before checking gitProfile", async () => { + const options: OptionValues = { + packageKeys: ["package1"], + keysByVersion: ["package2:v1"], + gitProfile: "myProfile", + }; + + await expect( + (module as any).batchExportPackages(mockContext, mockCommand, options) + ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); + + expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + }); + }); + + describe("batchImportPackages validation", () => { + describe("gitProfile and gitBranch validation", () => { + it("should throw error when gitProfile is provided without gitBranch", async () => { + const options: OptionValues = { + file: "export.zip", + gitProfile: "myProfile", + }; + + await expect( + (module as any).batchImportPackages(mockContext, mockCommand, options) + ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); + + // Ensure the service method was never called + expect(mockConfigCommandService.batchImportPackages).not.toHaveBeenCalled(); + }); + + it("should pass validation when gitProfile is provided with gitBranch", async () => { + const options: OptionValues = { + file: "export.zip", + gitProfile: "myProfile", + gitBranch: "main", + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + "export.zip", + undefined, + undefined, + "main" + ); + }); + + it("should pass validation when gitBranch is provided without gitProfile", async () => { + const options: OptionValues = { + directory: "./exported", + gitBranch: "develop", + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + undefined, + "./exported", + undefined, + "develop" + ); + }); + + it("should pass validation when neither gitProfile nor gitBranch are provided", async () => { + const options: OptionValues = { + file: "export.zip", + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + // Ensure the service method was called + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + "export.zip", + undefined, + undefined, + undefined + ); + }); + }); + + describe("import options", () => { + it("should pass file option correctly", async () => { + const options: OptionValues = { + file: "my-export.zip", + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + "my-export.zip", + undefined, + undefined, + undefined + ); + }); + + it("should pass directory option correctly", async () => { + const options: OptionValues = { + directory: "./my-exports", + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + undefined, + "./my-exports", + undefined, + undefined + ); + }); + + it("should pass overwrite option correctly", async () => { + const options: OptionValues = { + file: "export.zip", + overwrite: true, + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + "export.zip", + undefined, + true, + undefined + ); + }); + + it("should handle all options together", async () => { + const options: OptionValues = { + directory: "./exports", + overwrite: true, + gitBranch: "feature-branch", + }; + + await (module as any).batchImportPackages(mockContext, mockCommand, options); + + expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( + undefined, + "./exports", + true, + "feature-branch" + ); + }); + }); + }); +}); + From 4db80abf2bc605e47c9a341f4a5cc84bbcb010ca Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 19:39:07 +0200 Subject: [PATCH 3/9] TA-4324: Refactor tests --- .../configuration-management/module.spec.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index 743936c0..ef39a61d 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -1,7 +1,3 @@ -/** - * Unit tests for configuration-management module action method validations - */ - import Module = require("../../../src/commands/configuration-management/module"); import { Context } from "../../../src/core/command/cli-context"; import { Command, OptionValues } from "commander"; @@ -41,7 +37,6 @@ describe("Configuration Management Module - Action Validations", () => { (module as any).batchExportPackages(mockContext, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); - // Ensure the service method was never called expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); }); @@ -52,7 +47,6 @@ describe("Configuration Management Module - Action Validations", () => { (module as any).batchExportPackages(mockContext, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); - // Ensure the service method was never called expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); }); @@ -63,7 +57,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchExportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1", "package2"], undefined, @@ -80,7 +73,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchExportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( undefined, ["package3:v1", "package4:v2"], @@ -102,7 +94,6 @@ describe("Configuration Management Module - Action Validations", () => { (module as any).batchExportPackages(mockContext, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); - // Ensure the service method was never called expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); }); @@ -115,7 +106,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchExportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], undefined, @@ -133,7 +123,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchExportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], undefined, @@ -150,7 +139,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchExportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalled(); }); }); @@ -236,7 +224,6 @@ describe("Configuration Management Module - Action Validations", () => { (module as any).batchImportPackages(mockContext, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); - // Ensure the service method was never called expect(mockConfigCommandService.batchImportPackages).not.toHaveBeenCalled(); }); @@ -249,7 +236,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchImportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip", undefined, @@ -266,7 +252,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchImportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( undefined, "./exported", @@ -282,7 +267,6 @@ describe("Configuration Management Module - Action Validations", () => { await (module as any).batchImportPackages(mockContext, mockCommand, options); - // Ensure the service method was called expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip", undefined, From 092ac8c660b2caa59b1208ea08c7b49352ecd836 Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 20:18:27 +0200 Subject: [PATCH 4/9] TA-4324: Use the already provided testContext instead of mocking again --- .../configuration-management/module.spec.ts | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index ef39a61d..6f9ed65c 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -1,20 +1,18 @@ import Module = require("../../../src/commands/configuration-management/module"); -import { Context } from "../../../src/core/command/cli-context"; import { Command, OptionValues } from "commander"; import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; +import { testContext } from "../../utls/test-context"; jest.mock("../../../src/commands/configuration-management/config-command.service"); describe("Configuration Management Module - Action Validations", () => { let module: Module; - let mockContext: Context; let mockCommand: Command; let mockConfigCommandService: jest.Mocked; beforeEach(() => { jest.clearAllMocks(); module = new Module(); - mockContext = {} as Context; mockCommand = {} as Command; mockConfigCommandService = { @@ -34,7 +32,7 @@ describe("Configuration Management Module - Action Validations", () => { }; await expect( - (module as any).batchExportPackages(mockContext, mockCommand, options) + (module as any).batchExportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); @@ -44,7 +42,7 @@ describe("Configuration Management Module - Action Validations", () => { const options: OptionValues = {}; await expect( - (module as any).batchExportPackages(mockContext, mockCommand, options) + (module as any).batchExportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); @@ -55,7 +53,7 @@ describe("Configuration Management Module - Action Validations", () => { packageKeys: ["package1", "package2"], }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1", "package2"], @@ -71,7 +69,7 @@ describe("Configuration Management Module - Action Validations", () => { keysByVersion: ["package3:v1", "package4:v2"], }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( undefined, @@ -91,7 +89,7 @@ describe("Configuration Management Module - Action Validations", () => { }; await expect( - (module as any).batchExportPackages(mockContext, mockCommand, options) + (module as any).batchExportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); @@ -104,7 +102,7 @@ describe("Configuration Management Module - Action Validations", () => { gitBranch: "main", }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -121,7 +119,7 @@ describe("Configuration Management Module - Action Validations", () => { gitBranch: "main", }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -137,7 +135,7 @@ describe("Configuration Management Module - Action Validations", () => { packageKeys: ["package1"], }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalled(); }); @@ -149,7 +147,7 @@ describe("Configuration Management Module - Action Validations", () => { packageKeys: ["package1"], }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -166,7 +164,7 @@ describe("Configuration Management Module - Action Validations", () => { withDependencies: true, }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -183,7 +181,7 @@ describe("Configuration Management Module - Action Validations", () => { unzip: true, }; - await (module as any).batchExportPackages(mockContext, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -204,7 +202,7 @@ describe("Configuration Management Module - Action Validations", () => { }; await expect( - (module as any).batchExportPackages(mockContext, mockCommand, options) + (module as any).batchExportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); @@ -221,7 +219,7 @@ describe("Configuration Management Module - Action Validations", () => { }; await expect( - (module as any).batchImportPackages(mockContext, mockCommand, options) + (module as any).batchImportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); expect(mockConfigCommandService.batchImportPackages).not.toHaveBeenCalled(); @@ -234,7 +232,7 @@ describe("Configuration Management Module - Action Validations", () => { gitBranch: "main", }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip", @@ -250,7 +248,7 @@ describe("Configuration Management Module - Action Validations", () => { gitBranch: "develop", }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( undefined, @@ -265,7 +263,7 @@ describe("Configuration Management Module - Action Validations", () => { file: "export.zip", }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip", @@ -282,7 +280,7 @@ describe("Configuration Management Module - Action Validations", () => { file: "my-export.zip", }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "my-export.zip", @@ -297,7 +295,7 @@ describe("Configuration Management Module - Action Validations", () => { directory: "./my-exports", }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( undefined, @@ -313,7 +311,7 @@ describe("Configuration Management Module - Action Validations", () => { overwrite: true, }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip", @@ -330,7 +328,7 @@ describe("Configuration Management Module - Action Validations", () => { gitBranch: "feature-branch", }; - await (module as any).batchImportPackages(mockContext, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( undefined, From 926760b209faaa3c40d70b66efd4b235be4b88fe Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 21:02:46 +0200 Subject: [PATCH 5/9] TA-4324: Use gitProfile from context instead of from options --- src/commands/configuration-management/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index b4a41896..68eeaa22 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -85,7 +85,7 @@ class Module extends IModule { if ((options.packageKeys && options.keysByVersion) || (!options.packageKeys && !options.keysByVersion)) { throw new Error("Please provide either --packageKeys or --keysByVersion, but not both."); } - if (options.gitProfile && !options.gitBranch) { + if (context.gitProfile && !options.gitBranch) { throw new Error("Please specify a branch using --gitBranch when using a Git profile."); } options.withDependencies = options.withDependencies ?? false; @@ -97,7 +97,7 @@ class Module extends IModule { } private async batchImportPackages(context: Context, command: Command, options: OptionValues): Promise { - if (options.gitProfile && !options.gitBranch) { + if (context.gitProfile && !options.gitBranch) { throw new Error("Please specify a branch using --gitBranch when using a Git profile."); } await new ConfigCommandService(context).batchImportPackages(options.file, options.directory, options.overwrite, options.gitBranch); From 7a036fc806a63b7a6d9dd0abefe87ee6fd4a0c3c Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 21:03:31 +0200 Subject: [PATCH 6/9] TA-4324: Adapt tests --- .../configuration-management/module.spec.ts | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index 6f9ed65c..2ec124c3 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -82,27 +82,29 @@ describe("Configuration Management Module - Action Validations", () => { }); describe("gitProfile and gitBranch validation", () => { - it("should throw error when gitProfile is provided without gitBranch", async () => { + it("should throw error when gitProfile is set in context without gitBranch option", async () => { + const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); + contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { packageKeys: ["package1"], - gitProfile: "myProfile", }; await expect( - (module as any).batchExportPackages(testContext, mockCommand, options) + (module as any).batchExportPackages(contextWithGitProfile, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); }); - it("should pass validation when gitProfile is provided with gitBranch", async () => { + it("should pass validation when gitProfile is set in context with gitBranch option", async () => { + const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); + contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { packageKeys: ["package1"], - gitProfile: "myProfile", gitBranch: "main", }; - await (module as any).batchExportPackages(testContext, mockCommand, options); + await (module as any).batchExportPackages(contextWithGitProfile, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -113,7 +115,7 @@ describe("Configuration Management Module - Action Validations", () => { ); }); - it("should pass validation when gitBranch is provided without gitProfile", async () => { + it("should pass validation when gitBranch is provided without gitProfile in context", async () => { const options: OptionValues = { packageKeys: ["package1"], gitBranch: "main", @@ -130,7 +132,7 @@ describe("Configuration Management Module - Action Validations", () => { ); }); - it("should pass validation when neither gitProfile nor gitBranch are provided", async () => { + it("should pass validation when neither gitProfile in context nor gitBranch in options are provided", async () => { const options: OptionValues = { packageKeys: ["package1"], }; @@ -195,14 +197,15 @@ describe("Configuration Management Module - Action Validations", () => { describe("combined validation scenarios", () => { it("should fail on first validation error (packageKeys conflict) before checking gitProfile", async () => { + const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); + contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { packageKeys: ["package1"], keysByVersion: ["package2:v1"], - gitProfile: "myProfile", }; await expect( - (module as any).batchExportPackages(testContext, mockCommand, options) + (module as any).batchExportPackages(contextWithGitProfile, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); @@ -212,27 +215,29 @@ describe("Configuration Management Module - Action Validations", () => { describe("batchImportPackages validation", () => { describe("gitProfile and gitBranch validation", () => { - it("should throw error when gitProfile is provided without gitBranch", async () => { + it("should throw error when gitProfile is set in context without gitBranch option", async () => { + const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); + contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { file: "export.zip", - gitProfile: "myProfile", }; await expect( - (module as any).batchImportPackages(testContext, mockCommand, options) + (module as any).batchImportPackages(contextWithGitProfile, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); expect(mockConfigCommandService.batchImportPackages).not.toHaveBeenCalled(); }); - it("should pass validation when gitProfile is provided with gitBranch", async () => { + it("should pass validation when gitProfile is set in context with gitBranch option", async () => { + const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); + contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { file: "export.zip", - gitProfile: "myProfile", gitBranch: "main", }; - await (module as any).batchImportPackages(testContext, mockCommand, options); + await (module as any).batchImportPackages(contextWithGitProfile, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip", @@ -242,7 +247,7 @@ describe("Configuration Management Module - Action Validations", () => { ); }); - it("should pass validation when gitBranch is provided without gitProfile", async () => { + it("should pass validation when gitBranch is provided without gitProfile in context", async () => { const options: OptionValues = { directory: "./exported", gitBranch: "develop", @@ -258,7 +263,7 @@ describe("Configuration Management Module - Action Validations", () => { ); }); - it("should pass validation when neither gitProfile nor gitBranch are provided", async () => { + it("should pass validation when neither gitProfile in context nor gitBranch in options are provided", async () => { const options: OptionValues = { file: "export.zip", }; From 71ffc0d64a52f0848e9bc769d883a41e0e009c0a Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Mon, 6 Oct 2025 21:12:28 +0200 Subject: [PATCH 7/9] TA-4324: Refactor tests --- .../commands/configuration-management/module.spec.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index 2ec124c3..af86d475 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -9,6 +9,7 @@ describe("Configuration Management Module - Action Validations", () => { let module: Module; let mockCommand: Command; let mockConfigCommandService: jest.Mocked; + const contextWithGitProfile = { gitProfile: "myProfile" } as any; beforeEach(() => { jest.clearAllMocks(); @@ -83,8 +84,6 @@ describe("Configuration Management Module - Action Validations", () => { describe("gitProfile and gitBranch validation", () => { it("should throw error when gitProfile is set in context without gitBranch option", async () => { - const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); - contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { packageKeys: ["package1"], }; @@ -97,8 +96,6 @@ describe("Configuration Management Module - Action Validations", () => { }); it("should pass validation when gitProfile is set in context with gitBranch option", async () => { - const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); - contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { packageKeys: ["package1"], gitBranch: "main", @@ -197,8 +194,6 @@ describe("Configuration Management Module - Action Validations", () => { describe("combined validation scenarios", () => { it("should fail on first validation error (packageKeys conflict) before checking gitProfile", async () => { - const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); - contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { packageKeys: ["package1"], keysByVersion: ["package2:v1"], @@ -216,8 +211,6 @@ describe("Configuration Management Module - Action Validations", () => { describe("batchImportPackages validation", () => { describe("gitProfile and gitBranch validation", () => { it("should throw error when gitProfile is set in context without gitBranch option", async () => { - const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); - contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { file: "export.zip", }; @@ -230,8 +223,6 @@ describe("Configuration Management Module - Action Validations", () => { }); it("should pass validation when gitProfile is set in context with gitBranch option", async () => { - const contextWithGitProfile = Object.assign(Object.create(Object.getPrototypeOf(testContext)), testContext); - contextWithGitProfile.gitProfile = "myProfile"; const options: OptionValues = { file: "export.zip", gitBranch: "main", From 615a0987ef8af40e5c0d142922cb5244a669a172 Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Tue, 7 Oct 2025 22:12:59 +0200 Subject: [PATCH 8/9] TA-4286: Use global option handler in the commands --- src/core/command/module-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/command/module-handler.ts b/src/core/command/module-handler.ts index d4927c9f..5c94ad11 100644 --- a/src/core/command/module-handler.ts +++ b/src/core/command/module-handler.ts @@ -214,7 +214,7 @@ export class CommandConfig { this.printBetaNoticeIfBetaCommand(); this.printBetaNoticeIfBetaOptions(); this.printDeprecationNoticeIfDeprecated(); - await handler(this.ctx, this.cmd, this.cmd.opts()); + await handler(this.ctx, this.cmd, this.cmd.optsWithGlobals()); } catch (error) { logger.error(`An unexpected error occured executing a command: ${error}`); } @@ -237,7 +237,7 @@ export class CommandConfig { if ((this.cmd as any).isBeta) { return; } - const providedOptions = this.cmd.opts(); + const providedOptions = this.cmd.optsWithGlobals(); for (const option of this.cmd.options) { const optionName = option.attributeName(); if ((option as any).isBeta && Object.hasOwn(providedOptions, optionName)) { From 306ba735ad9af27b0bd7ac4f706b20d309bfaa4b Mon Sep 17 00:00:00 2001 From: zgjimhaziri Date: Tue, 7 Oct 2025 22:13:45 +0200 Subject: [PATCH 9/9] TA-4286: Change back to asserting gitProfile option instead of the context --- .../configuration-management/module.ts | 4 ++-- .../configuration-management/module.spec.ts | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 68eeaa22..b4a41896 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -85,7 +85,7 @@ class Module extends IModule { if ((options.packageKeys && options.keysByVersion) || (!options.packageKeys && !options.keysByVersion)) { throw new Error("Please provide either --packageKeys or --keysByVersion, but not both."); } - if (context.gitProfile && !options.gitBranch) { + if (options.gitProfile && !options.gitBranch) { throw new Error("Please specify a branch using --gitBranch when using a Git profile."); } options.withDependencies = options.withDependencies ?? false; @@ -97,7 +97,7 @@ class Module extends IModule { } private async batchImportPackages(context: Context, command: Command, options: OptionValues): Promise { - if (context.gitProfile && !options.gitBranch) { + if (options.gitProfile && !options.gitBranch) { throw new Error("Please specify a branch using --gitBranch when using a Git profile."); } await new ConfigCommandService(context).batchImportPackages(options.file, options.directory, options.overwrite, options.gitBranch); diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index af86d475..3fd40a33 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -9,7 +9,6 @@ describe("Configuration Management Module - Action Validations", () => { let module: Module; let mockCommand: Command; let mockConfigCommandService: jest.Mocked; - const contextWithGitProfile = { gitProfile: "myProfile" } as any; beforeEach(() => { jest.clearAllMocks(); @@ -83,25 +82,27 @@ describe("Configuration Management Module - Action Validations", () => { }); describe("gitProfile and gitBranch validation", () => { - it("should throw error when gitProfile is set in context without gitBranch option", async () => { + it("should throw error when gitProfile is provided without gitBranch option", async () => { const options: OptionValues = { packageKeys: ["package1"], + gitProfile: "myProfile", }; await expect( - (module as any).batchExportPackages(contextWithGitProfile, mockCommand, options) + (module as any).batchExportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); }); - it("should pass validation when gitProfile is set in context with gitBranch option", async () => { + it("should pass validation when gitProfile provided with gitBranch option", async () => { const options: OptionValues = { packageKeys: ["package1"], gitBranch: "main", + gitProfile: "myProfile", }; - await (module as any).batchExportPackages(contextWithGitProfile, mockCommand, options); + await (module as any).batchExportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchExportPackages).toHaveBeenCalledWith( ["package1"], @@ -197,10 +198,11 @@ describe("Configuration Management Module - Action Validations", () => { const options: OptionValues = { packageKeys: ["package1"], keysByVersion: ["package2:v1"], + gitProfile: "myProfile", }; await expect( - (module as any).batchExportPackages(contextWithGitProfile, mockCommand, options) + (module as any).batchExportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); expect(mockConfigCommandService.batchExportPackages).not.toHaveBeenCalled(); @@ -210,25 +212,27 @@ describe("Configuration Management Module - Action Validations", () => { describe("batchImportPackages validation", () => { describe("gitProfile and gitBranch validation", () => { - it("should throw error when gitProfile is set in context without gitBranch option", async () => { + it("should throw error when gitProfile is provided without gitBranch option", async () => { const options: OptionValues = { file: "export.zip", + gitProfile: "myProfile", }; await expect( - (module as any).batchImportPackages(contextWithGitProfile, mockCommand, options) + (module as any).batchImportPackages(testContext, mockCommand, options) ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); expect(mockConfigCommandService.batchImportPackages).not.toHaveBeenCalled(); }); - it("should pass validation when gitProfile is set in context with gitBranch option", async () => { + it("should pass validation when gitProfile is provided with gitBranch option", async () => { const options: OptionValues = { file: "export.zip", gitBranch: "main", + gitProfile: "myProfile", }; - await (module as any).batchImportPackages(contextWithGitProfile, mockCommand, options); + await (module as any).batchImportPackages(testContext, mockCommand, options); expect(mockConfigCommandService.batchImportPackages).toHaveBeenCalledWith( "export.zip",