From 8f453c4c015bd89932874190e082045f0fb91ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Wed, 10 Jun 2026 11:17:04 +0200 Subject: [PATCH 1/9] SP-1089: Add plan for command integration tests Captures the implementation plan to drive Content CLI command tests through the real Commander parser via parseAsync, including refactoring src/content-cli.ts to expose a createProgram factory and relocating the two existing module specs under tests/integration/commands. Includes-AI-Code: true Co-authored-by: Cursor --- ...command_integration_tests_e980d6d5.plan.md | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md diff --git a/.cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md b/.cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md new file mode 100644 index 0000000..98da8ff --- /dev/null +++ b/.cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md @@ -0,0 +1,141 @@ +--- +name: SP-1089 command integration tests +overview: Refactor `src/content-cli.ts` to expose a `createProgram(context)` factory, then convert the two existing module specs into Commander-driven integration tests under `tests/integration/commands/`. +todos: + - id: factory + content: "Refactor src/content-cli.ts: extract createProgram(context, { modules? }) factory, move banner/parseOptions/run into a guarded bootstrap, switch to parseAsync" + status: pending + - id: helper + content: Add tests/utls/cli-program.ts with buildTestProgram(modules) helper that wires testContext into createProgram + status: pending + - id: asset-registry-it + content: Move and rewrite asset-registry-module.spec.ts as tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts using parseAsync + status: pending + - id: config-mgmt-it + content: Move and rewrite configuration-management/module.spec.ts as tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts; convert .rejects.toThrow assertions to logger transport assertions + status: pending + - id: verify + content: Run the full jest suite and a build smoke test (node dist/content-cli.js --help) to confirm no regressions + status: pending +isProject: false +--- + +## Goal + +SP-1089 — drive Content CLI command tests through the real Commander parser instead of poking private action methods. The two existing "module" specs only exercise the action callbacks; they bypass Commander's command registration, option parsing, defaults, type coercion, and validation. After this change, tests will mirror what an end user types and run through the same parse path the binary uses. + +## Files in scope + +- [src/content-cli.ts](src/content-cli.ts) — refactor to expose a factory. +- [src/core/command/module-handler.ts](src/core/command/module-handler.ts) — read-only reference; do not change behaviour. The `Configurator.action` wrapper logs and swallows errors, which informs how tests must assert validation failures (see "Error assertions" below). +- [tests/commands/configuration-management/module.spec.ts](tests/commands/configuration-management/module.spec.ts) — rewrite & relocate. +- [tests/commands/asset-registry/asset-registry-module.spec.ts](tests/commands/asset-registry/asset-registry-module.spec.ts) — rewrite & relocate. +- [tests/utls/test-context.ts](tests/utls/test-context.ts) — reuse, add a small CLI helper next to it. + +The service-level specs (e.g. [tests/commands/asset-registry/asset-registry-list.spec.ts](tests/commands/asset-registry/asset-registry-list.spec.ts), the bulk of the `configuration-management/*.spec.ts` tree) are untouched — they cover service logic against mocked HTTP, which is orthogonal to argument parsing. + +## 1. Expose a `createProgram` factory + +Today `src/content-cli.ts` constructs `program` at module top level, calls `parseOptions(process.argv)`, prints the version banner, builds a `Context`, and finally calls `program.parse(process.argv)` inside `run()`. Importing this file from a test would execute all of that. + +Refactor by extracting two pure pieces and gating the bootstrap: + +- `export async function createProgram(context: Context, opts?: { modules?: IModuleConstructor[] }): Promise` — builds a fresh `Command`, configures help / version / global options, wires a `ModuleHandler`, calls `configureRootCommands`, and either auto-discovers modules (production) or registers the explicit list passed in (tests). Returns the configured program without parsing argv. +- `async function run(): Promise` — keeps the existing banner / debug-log / context-init / `parseAsync(process.argv)` flow, calling `createProgram(context)`. +- Replace top-level `run();` with `if (require.main === module) { run(); }` so importing the file from tests is a no-op. The version banner, debug-level switch, and `parseOptions(process.argv)` block move inside `run()` (they are CLI-bootstrap concerns, not factory concerns). + +Switch the parse to `parseAsync(process.argv)` so async actions are awaited end-to-end (matches what tests will use). The `try/catch` around it stays. + +Sketch of the factory shape: + +```typescript +export async function createProgram( + context: Context, + opts: { modules?: IModuleConstructor[] } = {}, +): Promise { + const program = new Command(); + program.configureHelp({ /* unchanged */ }); + program.version(VersionUtils.getCurrentCliVersion()); + program.option("-q, --quietmode", "Reduce output to a minimum", false); + program.option("-p, --profile [profile]"); + program.option("--gitProfile [gitProfile]", "Git profile to use"); + program.option("--debug", "Print debug messages", false); + program.option("--dev", "Development Mode", false); + + const moduleHandler = new ModuleHandler(program, context); + configureRootCommands(moduleHandler.configurator); + + if (opts.modules) { + for (const ModuleClass of opts.modules) { + new ModuleClass().register(context, moduleHandler.configurator); + } + } else { + moduleHandler.discoverAndRegisterModules(__dirname, program.opts().dev); + } + + return program; +} +``` + +Tests pass `{ modules: [Module] }` so they avoid filesystem-based discovery (which would conflict with the `jest.mock("fs")` setup in [tests/jest.setup.ts](tests/jest.setup.ts)). + +## 2. Add a tiny CLI test helper + +New file `tests/utls/cli-program.ts`: + +- Re-exports a small `buildTestProgram(modules: IModuleConstructor[]): Promise` that calls `createProgram(testContext, { modules })`. +- Tests do not need profile/HTTP changes — they continue to import `testContext` from [tests/utls/test-context.ts](tests/utls/test-context.ts), and individual specs `jest.mock(...)` the underlying services so no real HTTP is hit. + +## 3. Move and rewrite the two module specs + +Move: + +- `tests/commands/configuration-management/module.spec.ts` → `tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts` +- `tests/commands/asset-registry/asset-registry-module.spec.ts` → `tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts` + +`testMatch` in [jest.config.ts](jest.config.ts) is `/tests/**/*.spec.ts`, so the new path is picked up automatically. + +Inside each new spec: + +- Drop `(module as any).method(testContext, mockCommand, options)` calls. Replace with `await program.parseAsync(["node", "content-cli", ...args])`. +- Build the program once per test (or per `beforeEach`) via `buildTestProgram([Module])` so each test has a clean Commander state. +- Keep `jest.mock(...)` of the downstream services and the existing `mockImplementation(() => mockService)` pattern. Assertions on `mockService.` parameters stay almost identical, but values like `withDependencies` will now reflect Commander's parsing (e.g. an absent boolean flag becomes `undefined` rather than the explicit `false` the action body forces). +- Rename `describe` blocks to " command integration" to match the file naming. + +Example translation for `asset-registry list --json`: + +```typescript +const program = await buildTestProgram([Module]); +await program.parseAsync(["node", "content-cli", "asset-registry", "list", "--json"]); +expect(mockService.listTypes).toHaveBeenCalledWith(true); +``` + +Equivalent rewrite for the existing private-method test in [tests/commands/asset-registry/asset-registry-module.spec.ts](tests/commands/asset-registry/asset-registry-module.spec.ts). + +## 4. Error / validation assertions + +The current specs assert `await expect((module as any).fn(...)).rejects.toThrow("…")`. That works only because the test bypasses `Configurator.action`, which catches every error and logs `An unexpected error occured executing a command: `. Once tests go through `parseAsync`, the Configurator swallows the throw and `parseAsync` resolves cleanly. Two changes: + +- Use the existing `loggingTestTransport` from [tests/jest.setup.ts](tests/jest.setup.ts) to assert the validation message reached the user: + ```typescript + expect(loggingTestTransport.logMessages).toEqual(expect.arrayContaining([ + expect.objectContaining({ + level: "error", + message: expect.stringContaining("Please provide either --packageKeys or --keysByVersion, but not both."), + }), + ])); + ``` +- Continue to assert `expect(mockService.fn).not.toHaveBeenCalled()` so the negative path is still tightened. + +This is a more accurate test of what the user actually sees and keeps the production `Configurator` behaviour unchanged. + +## 5. Verification + +- `yarn jest` (or the project's existing script) — both rewritten specs and untouched service specs must stay green. +- Manual smoke: `yarn build && node dist/content-cli.js --help` to ensure the bootstrap refactor did not regress the binary entry. + +## Out of scope + +- Real HTTP / nock-based end-to-end tests — that is SP-1063 and explicitly a follow-up. +- Touching service-level specs that already exercise mocked axios. +- Behaviour changes to the `Configurator` error-handling wrapper. \ No newline at end of file From 22600c7132e3abce62d52d6ddfc9f3e6484c0810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Wed, 10 Jun 2026 16:29:25 +0200 Subject: [PATCH 2/9] SP-1089: Convert module specs to Commander integration tests Refactor the CLI bootstrap to expose a `createProgram(context, opts)` factory and gate the runtime entry behind `if (require.main === module)`, so tests can build isolated Commander programs and invoke commands via `parseAsync` instead of poking private action methods. The bin entry keeps the existing banner/debug behaviour by parsing global options through a small bootstrap `Command` before context init. Replace the two module-level specs with integration tests under `tests/integration/commands/` that drive each command through real Commander parsing, exercising option defaults/coercion. Action-body validation errors are caught by `Configurator.action`, so tests now assert on the in-memory winston transport rather than promise rejection. A new `tests/utls/cli-program.ts` helper wires `testContext` into the factory while skipping filesystem-based module discovery (incompatible with the `jest.mock("fs")` setup). Includes-AI-Code: true Co-authored-by: Cursor --- src/content-cli.ts | 138 ++- .../asset-registry-module.spec.ts | 149 --- .../configuration-management/module.spec.ts | 1081 ----------------- ...asset-registry.command-integration.spec.ts | 144 +++ ...ion-management.command-integration.spec.ts | 833 +++++++++++++ tests/utls/cli-program.ts | 8 + 6 files changed, 1077 insertions(+), 1276 deletions(-) delete mode 100644 tests/commands/asset-registry/asset-registry-module.spec.ts delete mode 100644 tests/commands/configuration-management/module.spec.ts create mode 100644 tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts create mode 100644 tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts create mode 100644 tests/utls/cli-program.ts diff --git a/src/content-cli.ts b/src/content-cli.ts index 60cb630..0fc566c 100644 --- a/src/content-cli.ts +++ b/src/content-cli.ts @@ -2,7 +2,7 @@ import semverSatisfies = require("semver/functions/satisfies"); import { Command } from "commander"; -import { Configurator, ModuleHandler } from "./core/command/module-handler"; +import { Configurator, IModuleConstructor, ModuleHandler } from "./core/command/module-handler"; import { Context } from "./core/command/cli-context"; import { VersionUtils } from "./core/utils/version"; import { logger } from "./core/utils/logger"; @@ -14,74 +14,120 @@ import { ContentCLIHelp } from "./core/command/CustomHelp"; * This is the main entry point for the CLI. */ -// Check if the Node.js version satisfies the minimum requirements const requiredVersion = ">=10.10.0"; -if (!semverSatisfies(process.version, requiredVersion)) { - logger.error( - `Node version ${process.version} not supported. Please upgrade your node version to ${requiredVersion}` - ); - process.exit(1); + +function configureRootCommands(configurator: Configurator): void { + configurator.command("list") + .description("Commands to list content.") + .alias("ls"); } -// Global configuration options -const program: Command = new Command(); -program.configureHelp({ - formatHelp: (cmd, helper) => new ContentCLIHelp().formatHelp(cmd, helper), - subcommandTerm:cmd => new ContentCLIHelp().subcommandTerm(cmd), - optionTerm: opt => new ContentCLIHelp().optionTerm(opt), -}); -program.version(VersionUtils.getCurrentCliVersion()); -program.option("-q, --quietmode", "Reduce output to a minimum", false); -program.option("-p, --profile [profile]"); -program.option("--gitProfile [gitProfile]", "Git profile to use"); -program.option("--debug", "Print debug messages", false); -program.option("--dev", "Development Mode", false); -program.parseOptions(process.argv); - -if (!program.opts().quietmode) { - console.log(`Content CLI - (C) Copyright 2025 - Celonis SE - Version ${VersionUtils.getCurrentCliVersion()}`); - console.log(); +export interface CreateProgramOptions { + /** + * Explicit list of module classes to register. When provided, the factory + * skips filesystem-based module discovery — useful for tests that want to + * exercise a single module against the real Commander parser. + */ + modules?: IModuleConstructor[]; + /** + * Override the root path used for filesystem-based module discovery. + * Defaults to the directory of this file (i.e. `dist/` at runtime). + */ + rootPath?: string; + /** + * Force development mode for module discovery (looks up `module.ts` + * instead of `module.js`). Defaults to `program.opts().dev`. + */ + devMode?: boolean; } -if (program.opts().debug) { - logger.transports.forEach(t => { - t.level = "debug"; +/** + * Build a fully-configured Commander program without parsing argv. The bin + * entry uses this with auto-discovery; tests pass `{ modules: [...] }` to + * register only the modules under test and avoid the filesystem walk (which + * conflicts with the `jest.mock("fs")` setup). + */ +export function createProgram(context: Context, opts: CreateProgramOptions = {}): Command { + const program = new Command(); + program.configureHelp({ + formatHelp: (cmd, helper) => new ContentCLIHelp().formatHelp(cmd, helper), + subcommandTerm: cmd => new ContentCLIHelp().subcommandTerm(cmd), + optionTerm: opt => new ContentCLIHelp().optionTerm(opt), }); -} + program.version(VersionUtils.getCurrentCliVersion()); + program.option("-q, --quietmode", "Reduce output to a minimum", false); + program.option("-p, --profile [profile]"); + program.option("--gitProfile [gitProfile]", "Git profile to use"); + program.option("--debug", "Print debug messages", false); + program.option("--dev", "Development Mode", false); -/** - * To support the legacy command structure, we have to configure some root commands - * that the individual modules will extend. - */ -function configureRootCommands(configurator: Configurator): void { - configurator.command("list") - .description("Commands to list content.") - .alias("ls"); + const moduleHandler = new ModuleHandler(program, context); + configureRootCommands(moduleHandler.configurator); + + if (opts.modules) { + for (const moduleClass of opts.modules) { + const moduleInstance = new moduleClass(); + moduleInstance.register(context, moduleHandler.configurator); + } + } else { + const rootPath = opts.rootPath ?? __dirname; + const devMode = opts.devMode ?? !!program.opts().dev; + moduleHandler.discoverAndRegisterModules(rootPath, devMode); + } + + return program; } async function run(): Promise { - const context = new Context(program.opts()); - await context.init(); + if (!semverSatisfies(process.version, requiredVersion)) { + logger.error( + `Node version ${process.version} not supported. Please upgrade your node version to ${requiredVersion}` + ); + process.exit(1); + } - const moduleHandler = new ModuleHandler(program, context); + // Parse global options up-front so banner/debug-level decisions can use + // them before module discovery runs. + const bootstrapProgram = new Command(); + bootstrapProgram.option("-q, --quietmode", "Reduce output to a minimum", false); + bootstrapProgram.option("-p, --profile [profile]"); + bootstrapProgram.option("--gitProfile [gitProfile]", "Git profile to use"); + bootstrapProgram.option("--debug", "Print debug messages", false); + bootstrapProgram.option("--dev", "Development Mode", false); + bootstrapProgram.allowUnknownOption(true); + bootstrapProgram.parseOptions(process.argv); + const globalOpts = bootstrapProgram.opts(); - configureRootCommands(moduleHandler.configurator); + if (!globalOpts.quietmode) { + console.log(`Content CLI - (C) Copyright 2025 - Celonis SE - Version ${VersionUtils.getCurrentCliVersion()}`); + console.log(); + } + + if (globalOpts.debug) { + logger.transports.forEach(t => { + t.level = "debug"; + }); + } - moduleHandler.discoverAndRegisterModules(__dirname, program.opts().dev); + const context = new Context(globalOpts); + await context.init(); + + const program = createProgram(context, { devMode: !!globalOpts.dev }); try { - program.parse(process.argv); + await program.parseAsync(process.argv); } catch (error) { logger.error(`An unexpected error occurred: ${error}`); } } -run(); +if (require.main === module) { + run(); +} -// catch uncaught exceptions process.on("uncaughtException", (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => { console.error("\n💥 UNCAUGHT EXCEPTION!\n"); console.error("Error:", error); console.error("Origin:", origin); process.exit(1); -}); \ No newline at end of file +}); diff --git a/tests/commands/asset-registry/asset-registry-module.spec.ts b/tests/commands/asset-registry/asset-registry-module.spec.ts deleted file mode 100644 index 17bde19..0000000 --- a/tests/commands/asset-registry/asset-registry-module.spec.ts +++ /dev/null @@ -1,149 +0,0 @@ -import Module = require("../../../src/commands/asset-registry/module"); -import { Command, OptionValues } from "commander"; -import { AssetRegistryService } from "../../../src/commands/asset-registry/asset-registry.service"; -import { testContext } from "../../utls/test-context"; -import { createMockConfigurator } from "../../utls/configurator-mock"; - -jest.mock("../../../src/commands/asset-registry/asset-registry.service"); - -describe("Asset Registry Module", () => { - let module: Module; - let mockCommand: Command; - let mockService: jest.Mocked; - - beforeEach(() => { - jest.clearAllMocks(); - module = new Module(); - mockCommand = {} as Command; - - mockService = { - listTypes: jest.fn().mockResolvedValue(undefined), - listSkills: jest.fn().mockResolvedValue(undefined), - getType: jest.fn().mockResolvedValue(undefined), - getSchema: jest.fn().mockResolvedValue(undefined), - validate: jest.fn().mockResolvedValue(undefined), - getExamples: jest.fn().mockResolvedValue(undefined), - } as any; - - (AssetRegistryService as jest.MockedClass) - .mockImplementation(() => mockService); - }); - - it("should call getSchema with correct parameters", async () => { - const options: OptionValues = { assetType: "BOARD_V2", json: true }; - await (module as any).getSchema(testContext, mockCommand, options); - expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", true); - }); - - it("should call validate with --configuration sub-mode options", async () => { - const options: OptionValues = { - assetType: "BOARD_V2", - packageKey: "my-pkg", - configuration: '{"components":[]}', - json: true, - }; - await (module as any).validate(testContext, mockCommand, options); - expect(mockService.validate).toHaveBeenCalledWith({ - assetType: "BOARD_V2", - packageKey: "my-pkg", - nodeKey: undefined, - configuration: '{"components":[]}', - file: undefined, - json: true, - }); - }); - - it("should call validate with --nodeKey sub-mode options", async () => { - const options: OptionValues = { - assetType: "BOARD_V2", - packageKey: "my-pkg", - nodeKey: "my-view", - json: "", - }; - await (module as any).validate(testContext, mockCommand, options); - expect(mockService.validate).toHaveBeenCalledWith({ - assetType: "BOARD_V2", - packageKey: "my-pkg", - nodeKey: "my-view", - configuration: undefined, - file: undefined, - json: false, - }); - }); - - it("should call validate with file mode options", async () => { - const options: OptionValues = { - assetType: "BOARD_V2", - file: "request.json", - json: "", - }; - await (module as any).validate(testContext, mockCommand, options); - expect(mockService.validate).toHaveBeenCalledWith({ - assetType: "BOARD_V2", - packageKey: undefined, - nodeKey: undefined, - configuration: undefined, - file: "request.json", - json: false, - }); - }); - - it("should call getExamples with correct parameters", async () => { - const options: OptionValues = { assetType: "BOARD_V2", json: "" }; - await (module as any).getExamples(testContext, mockCommand, options); - expect(mockService.getExamples).toHaveBeenCalledWith("BOARD_V2", false); - }); - - it("should call listTypes", async () => { - const options: OptionValues = { json: true }; - await (module as any).listTypes(testContext, mockCommand, options); - expect(mockService.listTypes).toHaveBeenCalledWith(true); - }); - - it("should call listSkills", async () => { - const options: OptionValues = { json: true }; - await (module as any).listSkills(testContext, mockCommand, options); - expect(mockService.listSkills).toHaveBeenCalledWith(true); - - jest.clearAllMocks(); - const optionsNoJson: OptionValues = { json: "" }; - await (module as any).listSkills(testContext, mockCommand, optionsNoJson); - expect(mockService.listSkills).toHaveBeenCalledWith(false); - }); - - it("should call getType", async () => { - const options: OptionValues = { assetType: "BOARD_V2", json: "" }; - await (module as any).getType(testContext, mockCommand, options); - expect(mockService.getType).toHaveBeenCalledWith("BOARD_V2", false); - }); - - describe("register", () => { - it("registers all expected command groups without throwing", () => { - const mockConfigurator = createMockConfigurator(); - - expect(() => new Module().register(testContext, mockConfigurator)).not.toThrow(); - - expect(mockConfigurator.command).toHaveBeenCalledWith("asset-registry"); - expect(mockConfigurator.command).toHaveBeenCalledWith("skills"); - expect(mockConfigurator.command).toHaveBeenCalledWith("list"); - expect(mockConfigurator.command).toHaveBeenCalledWith("get"); - expect(mockConfigurator.command).toHaveBeenCalledWith("schema"); - expect(mockConfigurator.command).toHaveBeenCalledWith("examples"); - expect(mockConfigurator.command).toHaveBeenCalledWith("validate"); - }); - - it("wires an action handler for every leaf subcommand", () => { - const mockConfigurator = createMockConfigurator(); - - new Module().register(testContext, mockConfigurator); - - // Each leaf command terminates the fluent chain with .action(handler). - // Keep this count in sync when adding or removing commands in module.ts. - const expectedLeafCommands = 6; - expect(mockConfigurator.action).toHaveBeenCalledTimes(expectedLeafCommands); - for (const call of mockConfigurator.action.mock.calls) { - expect(typeof call[0]).toBe("function"); - } - }); - }); -}); diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts deleted file mode 100644 index e24159c..0000000 --- a/tests/commands/configuration-management/module.spec.ts +++ /dev/null @@ -1,1081 +0,0 @@ -import Module = require("../../../src/commands/configuration-management/module"); -import { Command, OptionValues } from "commander"; -import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; -import { StagingPackageService } from "../../../src/commands/configuration-management/staging-package.service"; -import { MetadataService } from "../../../src/commands/configuration-management/metadata.service"; -import { T2tcCommandService } from "../../../src/commands/t2tc/t2tc-command.service"; -import { NodeDependencyService } from "../../../src/commands/configuration-management/node-dependency.service"; -import { PackageVersionCommandService } from "../../../src/commands/configuration-management/package-version-command.service"; -import { NodeDiffService } from "../../../src/commands/configuration-management/node-diff.service"; -import { SinglePackageImportService } from "../../../src/commands/configuration-management/single-package-import.service"; -import { testContext } from "../../utls/test-context"; -import { createMockConfigurator } from "../../utls/configurator-mock"; - -jest.mock("../../../src/commands/configuration-management/config-command.service"); -jest.mock("../../../src/commands/configuration-management/staging-package.service"); -jest.mock("../../../src/commands/configuration-management/metadata.service"); -jest.mock("../../../src/commands/t2tc/t2tc-command.service"); -jest.mock("../../../src/commands/configuration-management/node-dependency.service"); -jest.mock("../../../src/commands/configuration-management/node-diff.service"); -jest.mock("../../../src/commands/configuration-management/package-version-command.service"); -jest.mock("../../../src/commands/configuration-management/single-package-import.service"); - -/** Mirrors default values on `config variables list` Commander options (keep in sync with module.ts). */ -const variablesListOptionDefaults: OptionValues = { - packageKeys: [], - keysByVersion: [], - keysByVersionFile: "", -}; - -describe("Configuration Management Module - Action Validations", () => { - let module: Module; - let mockCommand: Command; - let mockConfigCommandService: jest.Mocked; - let mockStagingPackageService: jest.Mocked; - let mockMetadataService: jest.Mocked; - let mockT2tcCommandService: jest.Mocked; - let mockNodeDependencyService: jest.Mocked; - let mockNodeDiffService: jest.Mocked; - let mockSinglePackageImportService: jest.Mocked; - - beforeEach(() => { - jest.clearAllMocks(); - module = new Module(); - mockCommand = {} as Command; - - mockConfigCommandService = { - listVariables: jest.fn().mockResolvedValue(undefined), - } as any; - - mockStagingPackageService = { - listStagingPackages: jest.fn().mockResolvedValue(undefined), - } as any; - - mockMetadataService = { - exportPackagesMetadata: jest.fn().mockResolvedValue(undefined), - } as any; - - mockT2tcCommandService = { - listPackages: jest.fn().mockResolvedValue(undefined), - batchExportPackages: jest.fn().mockResolvedValue(undefined), - batchImportPackages: jest.fn().mockResolvedValue(undefined), - diffPackages: jest.fn().mockResolvedValue(undefined), - } as any; - - mockNodeDependencyService = { - listNodeDependencies: jest.fn().mockResolvedValue(undefined), - } as any; - - mockNodeDiffService = { - diff: jest.fn().mockResolvedValue(undefined), - diffWithFile: jest.fn().mockResolvedValue(undefined), - } as any; - - mockSinglePackageImportService = { - importPackage: jest.fn().mockResolvedValue(undefined), - } as any; - - (ConfigCommandService as jest.MockedClass).mockImplementation(() => mockConfigCommandService); - (StagingPackageService as jest.MockedClass).mockImplementation(() => mockStagingPackageService); - (MetadataService as jest.MockedClass).mockImplementation(() => mockMetadataService); - (T2tcCommandService as jest.MockedClass).mockImplementation(() => mockT2tcCommandService); - (NodeDependencyService as jest.MockedClass).mockImplementation(() => mockNodeDependencyService); - (NodeDiffService as jest.MockedClass).mockImplementation(() => mockNodeDiffService); - (SinglePackageImportService as jest.MockedClass).mockImplementation(() => mockSinglePackageImportService); - }); - - describe("listActivePackages 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.1.0.0", "package4.1.0.0"], - }; - - await expect( - (module as any).listPackages(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); - - expect(mockT2tcCommandService.listPackages).not.toHaveBeenCalled(); - }); - - it("should pass validation when only packageKeys is provided", async () => { - const options: OptionValues = { - packageKeys: ["package1", "package2"], - json: true, - }; - - await (module as any).listPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( - true, - undefined, - undefined, - ["package1", "package2"], - undefined, - undefined, - undefined, - undefined, - undefined - ); - }); - - it("should pass validation when only keysByVersion is provided", async () => { - const options: OptionValues = { - keysByVersion: ["package3.1.0.0", "package4.1.0.0"], - json: true, - }; - - await (module as any).listPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( - true, - undefined, - undefined, - undefined, - ["package3.1.0.0", "package4.1.0.0"], - undefined, - undefined, - undefined, - undefined - ); - }); - }); - describe("branches validation", () => { - it("should pass validation when branches is provided", async () => { - const options: OptionValues = { - branches: true, - json: true, - }; - - await (module as any).listPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( - true, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - undefined - ); - }); - }); - }); - - describe("listStagingPackages validation", () => { - it("should pass validation when branches is provided", async () => { - const options: OptionValues = { - branches: true, - json: true, - staging: true, - }; - - await (module as any).listPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( - true, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - true - ); - }); - - it("should throw error when packageKeys is provided", async () => { - const options: OptionValues = { - packageKeys: ["package1", "package2"], - staging: true, - }; - - await expect( - (module as any).listPackages(testContext, mockCommand, options) - ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); - }); - - it("should throw error when withDependencies is provided", async () => { - const options: OptionValues = { - withDependencies: true, - staging: true, - }; - - await expect( - (module as any).listPackages(testContext, mockCommand, options) - ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); - }); - - it("should throw error when keysByVersion is provided", async () => { - const options: OptionValues = { - keysByVersion: ["package3.1.0.0", "package4.1.0.0"], - staging: true, - }; - - await expect( - (module as any).listPackages(testContext, mockCommand, options) - ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); - }); - - it("should throw error when variableValue is provided", async () => { - const options: OptionValues = { - variableValue: "myValue", - staging: true, - }; - - await expect( - (module as any).listPackages(testContext, mockCommand, options) - ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); - }); - - it("should throw error when variableType is provided", async () => { - const options: OptionValues = { - variableType: "myType", - staging: true, - }; - - await expect( - (module as any).listPackages(testContext, mockCommand, options) - ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); - }); - }); - - describe("config package list handler", () => { - it("should list staging packages with json and flavors", async () => { - const options: OptionValues = { - json: true, - flavors: ["APP"], - }; - - await (module as any).listStagingPackages(testContext, mockCommand, options); - - expect(mockStagingPackageService.listStagingPackages).toHaveBeenCalledWith(["APP"], false, true); - }); - - it("should default flavors to an empty list and json to undefined when not provided", async () => { - const options: OptionValues = {}; - - await (module as any).listStagingPackages(testContext, mockCommand, options); - - expect(mockStagingPackageService.listStagingPackages).toHaveBeenCalledWith([], false, undefined); - }); - - it("should not pass legacy listPackages options", async () => { - const options: OptionValues = { - flavors: ["APP", "ANALYSIS"], - }; - - await (module as any).listStagingPackages(testContext, mockCommand, options); - - expect(mockStagingPackageService.listStagingPackages).toHaveBeenCalledWith(["APP", "ANALYSIS"], false, undefined); - expect(mockT2tcCommandService.listPackages).not.toHaveBeenCalled(); - }); - }); - - 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(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); - - expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); - }); - - it("should throw error when neither packageKeys nor keysByVersion are provided", async () => { - const options: OptionValues = {}; - - await expect( - (module as any).batchExportPackages(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); - - expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); - }); - - it("should pass validation when only packageKeys is provided", async () => { - const options: OptionValues = { - packageKeys: ["package1", "package2"], - }; - - await (module as any).batchExportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.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 option", async () => { - const options: OptionValues = { - packageKeys: ["package1"], - gitProfile: "myProfile", - }; - - await expect( - (module as any).batchExportPackages(testContext, mockCommand, options) - ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); - - expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); - }); - - 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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( - ["package1"], - undefined, - false, - "main", - undefined - ); - }); - - it("should pass validation when gitBranch is provided without gitProfile in context", async () => { - const options: OptionValues = { - packageKeys: ["package1"], - gitBranch: "main", - }; - - await (module as any).batchExportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( - ["package1"], - undefined, - false, - "main", - undefined - ); - }); - - it("should pass validation when neither gitProfile in context nor gitBranch in options are provided", async () => { - const options: OptionValues = { - packageKeys: ["package1"], - }; - - await (module as any).batchExportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.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(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageKeys or --keysByVersion, but not both."); - - expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); - }); - }); - }); - - describe("batchImportPackages validation", () => { - describe("gitProfile and gitBranch validation", () => { - 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(testContext, mockCommand, options) - ).rejects.toThrow("Please specify a branch using --gitBranch when using a Git profile."); - - expect(mockT2tcCommandService.batchImportPackages).not.toHaveBeenCalled(); - }); - - 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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - "export.zip", - undefined, - undefined, - "main", - undefined - ); - }); - - it("should pass validation when gitBranch is provided without gitProfile in context", async () => { - const options: OptionValues = { - directory: "./exported", - gitBranch: "develop", - }; - - await (module as any).batchImportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - undefined, - "./exported", - undefined, - "develop", - undefined - ); - }); - - it("should pass validation when neither gitProfile in context nor gitBranch in options are provided", async () => { - const options: OptionValues = { - file: "export.zip", - }; - - await (module as any).batchImportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - "export.zip", - undefined, - 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(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - "my-export.zip", - undefined, - undefined, - undefined, - undefined - ); - }); - - it("should pass directory option correctly", async () => { - const options: OptionValues = { - directory: "./my-exports", - }; - - await (module as any).batchImportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - undefined, - "./my-exports", - undefined, - undefined, - undefined - ); - }); - - it("should pass overwrite option correctly", async () => { - const options: OptionValues = { - file: "export.zip", - overwrite: true, - }; - - await (module as any).batchImportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - "export.zip", - undefined, - true, - undefined, - undefined - ); - }); - - it("should handle all options together", async () => { - const options: OptionValues = { - directory: "./exports", - overwrite: true, - gitBranch: "feature-branch", - }; - - await (module as any).batchImportPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( - undefined, - "./exports", - true, - "feature-branch", - undefined - ); - }); - }); - }); - - describe("importSinglePackage", () => { - it("should pass file option correctly", async () => { - const options: OptionValues = { - file: "single-package.zip", - }; - - await (module as any).importSinglePackage(testContext, mockCommand, options); - - expect(mockSinglePackageImportService.importPackage).toHaveBeenCalledWith( - "single-package.zip", - undefined, - undefined, - undefined - ); - }); - - it("should pass directory option correctly", async () => { - const options: OptionValues = { - directory: "./single-package-dir", - }; - - await (module as any).importSinglePackage(testContext, mockCommand, options); - - expect(mockSinglePackageImportService.importPackage).toHaveBeenCalledWith( - undefined, - "./single-package-dir", - undefined, - undefined - ); - }); - - it("should pass overwrite and json options correctly", async () => { - const options: OptionValues = { - file: "single-package.zip", - overwrite: true, - json: true, - }; - - await (module as any).importSinglePackage(testContext, mockCommand, options); - - expect(mockSinglePackageImportService.importPackage).toHaveBeenCalledWith( - "single-package.zip", - undefined, - true, - true - ); - }); - }); - - describe("listVariables validation", () => { - it("should throw when --packageKeys and --keysByVersion are both provided", async () => { - const options: OptionValues = { - ...variablesListOptionDefaults, - packageKeys: ["pkg-a"], - keysByVersion: ["key-1:1.0.0"], - }; - - await expect( - (module as any).listVariables(testContext, mockCommand, options) - ).rejects.toThrow( - "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." - ); - - expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); - }); - - it("should throw when --packageKeys and --keysByVersionFile are both provided", async () => { - const options: OptionValues = { - ...variablesListOptionDefaults, - packageKeys: ["pkg-a"], - keysByVersionFile: "mapping.json", - }; - - await expect( - (module as any).listVariables(testContext, mockCommand, options) - ).rejects.toThrow( - "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." - ); - - expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); - }); - - it("should throw when neither staging nor versioned inputs are provided", async () => { - const options: OptionValues = {...variablesListOptionDefaults}; - - await expect( - (module as any).listVariables(testContext, mockCommand, options) - ).rejects.toThrow( - "Please provide --packageKeys for staging, or --keysByVersion / --keysByVersionFile for versioned packages." - ); - - expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); - }); - - it("should call listVariables for staging when only --packageKeys is provided", async () => { - const options: OptionValues = { - ...variablesListOptionDefaults, - packageKeys: ["pkg-a", "pkg-b"], - json: true, - }; - - await (module as any).listVariables(testContext, mockCommand, options); - - expect(mockConfigCommandService.listVariables).toHaveBeenCalledWith( - true, - [], - "", - ["pkg-a", "pkg-b"] - ); - }); - - it("should call listVariables for versioned when only --keysByVersion is provided", async () => { - const options: OptionValues = { - ...variablesListOptionDefaults, - keysByVersion: ["k:v"], - json: false, - }; - - await (module as any).listVariables(testContext, mockCommand, options); - - expect(mockConfigCommandService.listVariables).toHaveBeenCalledWith( - false, - ["k:v"], - "", - [] - ); - }); - }); - - describe("createPackageVersion validation", () => { - let mockPackageVersionCommandService: jest.Mocked; - - beforeEach(() => { - mockPackageVersionCommandService = { - createPackageVersion: jest.fn().mockResolvedValue(undefined), - } as any; - - (PackageVersionCommandService as jest.MockedClass).mockImplementation(() => mockPackageVersionCommandService); - }); - - it("should throw error when both --packageVersion and --versionBumpOption PATCH are provided", async () => { - const options: OptionValues = { - packageKey: "my-package", - packageVersion: "1.2.0", - versionBumpOption: "PATCH", - }; - - await expect( - (module as any).createPackageVersion(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageVersion or --versionBumpOption, but not both."); - - expect(mockPackageVersionCommandService.createPackageVersion).not.toHaveBeenCalled(); - }); - - it("should throw error when neither --packageVersion nor --versionBumpOption PATCH are provided", async () => { - const options: OptionValues = { - packageKey: "my-package", - versionBumpOption: "NONE", - }; - - await expect( - (module as any).createPackageVersion(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageVersion or --versionBumpOption PATCH."); - - expect(mockPackageVersionCommandService.createPackageVersion).not.toHaveBeenCalled(); - }); - - it("should throw error when --packageVersion is missing and --versionBumpOption is not provided (defaults to NONE)", async () => { - const options: OptionValues = { - packageKey: "my-package", - }; - - await expect( - (module as any).createPackageVersion(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --packageVersion or --versionBumpOption PATCH."); - - expect(mockPackageVersionCommandService.createPackageVersion).not.toHaveBeenCalled(); - }); - - it("should pass validation when only --packageVersion is provided", async () => { - const options: OptionValues = { - packageKey: "my-package", - packageVersion: "1.2.0", - versionBumpOption: "NONE", - summaryOfChanges: "New features", - }; - - await (module as any).createPackageVersion(testContext, mockCommand, options); - - expect(mockPackageVersionCommandService.createPackageVersion).toHaveBeenCalledWith( - "my-package", - "1.2.0", - "NONE", - "New features", - undefined, - undefined, - ); - }); - - it("should pass validation when only --versionBumpOption PATCH is provided", async () => { - const options: OptionValues = { - packageKey: "my-package", - versionBumpOption: "PATCH", - summaryOfChanges: "Bug fixes", - }; - - await (module as any).createPackageVersion(testContext, mockCommand, options); - - expect(mockPackageVersionCommandService.createPackageVersion).toHaveBeenCalledWith( - "my-package", - undefined, - "PATCH", - "Bug fixes", - undefined, - undefined, - ); - }); - }); - - describe("register", () => { - it("registers all expected top-level command groups without throwing", () => { - const mockConfigurator = createMockConfigurator(); - - expect(() => new Module().register(testContext, mockConfigurator)).not.toThrow(); - - // Top-level groups attached to the root configurator. The 't2tc' group - // lives in its own module (tests/commands/t2tc/module.spec.ts). - expect(mockConfigurator.command).toHaveBeenCalledWith("config"); - expect(mockConfigurator.command).toHaveBeenCalledWith("list"); - expect(mockConfigurator.command).toHaveBeenCalledWith("package"); - }); - - it("wires an action handler for every leaf subcommand", () => { - const mockConfigurator = createMockConfigurator(); - - new Module().register(testContext, mockConfigurator); - - // Each leaf command terminates the fluent chain with .action(handler). - // Keep this count in sync when adding or removing commands in module.ts. - // The 4 't2tc package' leaf commands moved to their own module. - const expectedLeafCommands = 20; - expect(mockConfigurator.action).toHaveBeenCalledTimes(expectedLeafCommands); - for (const call of mockConfigurator.action.mock.calls) { - expect(typeof call[0]).toBe("function"); - } - }); - - it("marks the moved config commands as deprecated", () => { - const mockConfigurator = createMockConfigurator(); - - new Module().register(testContext, mockConfigurator); - - // config list/export/import/diff/validate are duplicated under t2tc package / - // config package and the originals carry a deprecation notice. - const expectedDeprecatedCommands = 5; - expect(mockConfigurator.deprecationNotice).toHaveBeenCalledTimes(expectedDeprecatedCommands); - for (const call of mockConfigurator.deprecationNotice.mock.calls) { - expect(typeof call[0]).toBe("string"); - } - }); - }); - - describe("listNodeDependencies", () => { - it("should call listNodeDependencies with correct parameters", async () => { - const options: OptionValues = { - packageKey: "test-package", - nodeKey: "test-node", - packageVersion: "1.0.0", - }; - - await (module as any).listNodeDependencies(testContext, mockCommand, options); - - expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( - "test-package", - "test-node", - "1.0.0", - undefined - ); - }); - - it("should pass json option when provided", async () => { - const options: OptionValues = { - packageKey: "test-package", - nodeKey: "test-node", - packageVersion: "2.0.0", - json: true, - }; - - await (module as any).listNodeDependencies(testContext, mockCommand, options); - - expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( - "test-package", - "test-node", - "2.0.0", - true - ); - }); - - it("should handle different package versions", async () => { - const options: OptionValues = { - packageKey: "production-package", - nodeKey: "production-node", - packageVersion: "3.5.2", - json: false, - }; - - await (module as any).listNodeDependencies(testContext, mockCommand, options); - - expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( - "production-package", - "production-node", - "3.5.2", - false - ); - }); - - it("should handle all parameters correctly", async () => { - const options: OptionValues = { - packageKey: "my-package", - nodeKey: "my-node", - packageVersion: "1.2.3", - json: true, - }; - - await (module as any).listNodeDependencies(testContext, mockCommand, options); - - expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledTimes(1); - expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( - "my-package", - "my-node", - "1.2.3", - true - ); - }); - }); - - describe("diffPackages", () => { - it("should call diffPackages using minimal parameters", async () => { - const options: OptionValues = { - file: "package.zip", - }; - - await (module as any).diffPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( - "package.zip", undefined, undefined, undefined - ); - }); - - it("should pass json parameter", async () => { - const options: OptionValues = { - file: "package.zip", - json: true, - }; - - await (module as any).diffPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( - "package.zip", undefined, undefined, true - ); - }); - - it("should pass hasChanges parameter", async () => { - const options: OptionValues = { - file: "package.zip", - hasChanges: true, - }; - - await (module as any).diffPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( - "package.zip", true, undefined, undefined - ); - }); - - it("should pass baseVersion parameter", async () => { - const options: OptionValues = { - file: "package.zip", - baseVersion: "1.0.0", - }; - - await (module as any).diffPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( - "package.zip", undefined, "1.0.0", undefined - ); - }); - - it("should pass both parameters when hasChanges and baseVersion are used together", async () => { - const options: OptionValues = { - file: "package.zip", - hasChanges: true, - baseVersion: "STAGING" - }; - - await (module as any).diffPackages(testContext, mockCommand, options); - - expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( - "package.zip", true, "STAGING", undefined - ); - }); - }); - - describe("diffNode validation", () => { - it("should throw when both --file and --compareVersion are provided", async () => { - const options: OptionValues = { - packageKey: "test-package", - nodeKey: "test-node", - baseVersion: "STAGING", - compareVersion: "1.0.0", - file: "./node.json", - }; - - await expect( - (module as any).diffNode(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --compareVersion or --file, but not both."); - - expect(mockNodeDiffService.diff).not.toHaveBeenCalled(); - expect(mockNodeDiffService.diffWithFile).not.toHaveBeenCalled(); - }); - - it("should throw when neither --file nor --compareVersion is provided", async () => { - const options: OptionValues = { - packageKey: "test-package", - nodeKey: "test-node", - baseVersion: "STAGING", - }; - - await expect( - (module as any).diffNode(testContext, mockCommand, options) - ).rejects.toThrow("Please provide either --compareVersion or --file, but not both."); - - expect(mockNodeDiffService.diff).not.toHaveBeenCalled(); - expect(mockNodeDiffService.diffWithFile).not.toHaveBeenCalled(); - }); - - it("should call diff when only --compareVersion is provided", async () => { - const options: OptionValues = { - packageKey: "test-package", - nodeKey: "test-node", - baseVersion: "STAGING", - compareVersion: "1.0.0", - json: true, - }; - - await (module as any).diffNode(testContext, mockCommand, options); - - expect(mockNodeDiffService.diff).toHaveBeenCalledWith( - "test-package", - "test-node", - "STAGING", - "1.0.0", - true - ); - expect(mockNodeDiffService.diffWithFile).not.toHaveBeenCalled(); - }); - - it("should call diffWithFile when only --file is provided", async () => { - const options: OptionValues = { - packageKey: "test-package", - nodeKey: "test-node", - baseVersion: "STAGING", - file: "./node.json", - }; - - await (module as any).diffNode(testContext, mockCommand, options); - - expect(mockNodeDiffService.diffWithFile).toHaveBeenCalledWith( - "test-package", - "test-node", - "STAGING", - "./node.json", - undefined - ); - expect(mockNodeDiffService.diff).not.toHaveBeenCalled(); - }); - }); -}); - diff --git a/tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts b/tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts new file mode 100644 index 0000000..07adacc --- /dev/null +++ b/tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts @@ -0,0 +1,144 @@ +import Module = require("../../../../src/commands/asset-registry/module"); +import { Command } from "commander"; +import { AssetRegistryService } from "../../../../src/commands/asset-registry/asset-registry.service"; +import { buildTestProgram } from "../../../utls/cli-program"; + +jest.mock("../../../../src/commands/asset-registry/asset-registry.service"); + +describe("asset-registry command integration", () => { + let program: Command; + let mockService: jest.Mocked; + + beforeEach(() => { + mockService = { + listTypes: jest.fn().mockResolvedValue(undefined), + listSkills: jest.fn().mockResolvedValue(undefined), + getType: jest.fn().mockResolvedValue(undefined), + getSchema: jest.fn().mockResolvedValue(undefined), + validate: jest.fn().mockResolvedValue(undefined), + getExamples: jest.fn().mockResolvedValue(undefined), + } as any; + + (AssetRegistryService as jest.MockedClass) + .mockImplementation(() => mockService); + + program = buildTestProgram([Module]); + }); + + function runCli(args: string[]): Promise { + return program.parseAsync(["node", "content-cli", ...args]); + } + + describe("asset-registry schema", () => { + it("calls getSchema with --json", async () => { + await runCli(["asset-registry", "schema", "--assetType", "BOARD_V2", "--json"]); + expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", true); + }); + + it("defaults --json to false when omitted", async () => { + await runCli(["asset-registry", "schema", "--assetType", "BOARD_V2"]); + expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", false); + }); + }); + + describe("asset-registry validate", () => { + it("forwards --configuration sub-mode options", async () => { + await runCli([ + "asset-registry", "validate", + "--assetType", "BOARD_V2", + "--packageKey", "my-pkg", + "--configuration", '{"components":[]}', + "--json", + ]); + expect(mockService.validate).toHaveBeenCalledWith({ + assetType: "BOARD_V2", + packageKey: "my-pkg", + nodeKey: undefined, + configuration: '{"components":[]}', + file: undefined, + json: true, + }); + }); + + it("forwards --nodeKey sub-mode options", async () => { + await runCli([ + "asset-registry", "validate", + "--assetType", "BOARD_V2", + "--packageKey", "my-pkg", + "--nodeKey", "my-view", + ]); + expect(mockService.validate).toHaveBeenCalledWith({ + assetType: "BOARD_V2", + packageKey: "my-pkg", + nodeKey: "my-view", + configuration: undefined, + file: undefined, + json: false, + }); + }); + + it("forwards --file mode options", async () => { + await runCli([ + "asset-registry", "validate", + "--assetType", "BOARD_V2", + "--file", "request.json", + ]); + expect(mockService.validate).toHaveBeenCalledWith({ + assetType: "BOARD_V2", + packageKey: undefined, + nodeKey: undefined, + configuration: undefined, + file: "request.json", + json: false, + }); + }); + }); + + describe("asset-registry examples", () => { + it("calls getExamples without --json", async () => { + await runCli(["asset-registry", "examples", "--assetType", "BOARD_V2"]); + expect(mockService.getExamples).toHaveBeenCalledWith("BOARD_V2", false); + }); + + it("calls getExamples with --json", async () => { + await runCli(["asset-registry", "examples", "--assetType", "BOARD_V2", "--json"]); + expect(mockService.getExamples).toHaveBeenCalledWith("BOARD_V2", true); + }); + }); + + describe("asset-registry list", () => { + it("calls listTypes with --json", async () => { + await runCli(["asset-registry", "list", "--json"]); + expect(mockService.listTypes).toHaveBeenCalledWith(true); + }); + + it("calls listTypes without --json", async () => { + await runCli(["asset-registry", "list"]); + expect(mockService.listTypes).toHaveBeenCalledWith(false); + }); + }); + + describe("asset-registry skills list", () => { + it("calls listSkills with --json", async () => { + await runCli(["asset-registry", "skills", "list", "--json"]); + expect(mockService.listSkills).toHaveBeenCalledWith(true); + }); + + it("calls listSkills without --json", async () => { + await runCli(["asset-registry", "skills", "list"]); + expect(mockService.listSkills).toHaveBeenCalledWith(false); + }); + }); + + describe("asset-registry get", () => { + it("calls getType with the requested assetType", async () => { + await runCli(["asset-registry", "get", "--assetType", "BOARD_V2"]); + expect(mockService.getType).toHaveBeenCalledWith("BOARD_V2", false); + }); + + it("calls getType with --json", async () => { + await runCli(["asset-registry", "get", "--assetType", "BOARD_V2", "--json"]); + expect(mockService.getType).toHaveBeenCalledWith("BOARD_V2", true); + }); + }); +}); diff --git a/tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts b/tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts new file mode 100644 index 0000000..5b5d044 --- /dev/null +++ b/tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts @@ -0,0 +1,833 @@ +import Module = require("../../../../src/commands/configuration-management/module"); +import { Command } from "commander"; +import { ConfigCommandService } from "../../../../src/commands/configuration-management/config-command.service"; +import { StagingPackageService } from "../../../../src/commands/configuration-management/staging-package.service"; +import { MetadataService } from "../../../../src/commands/configuration-management/metadata.service"; +import { T2tcCommandService } from "../../../../src/commands/t2tc/t2tc-command.service"; +import { NodeDependencyService } from "../../../../src/commands/configuration-management/node-dependency.service"; +import { PackageVersionCommandService } from "../../../../src/commands/configuration-management/package-version-command.service"; +import { NodeDiffService } from "../../../../src/commands/configuration-management/node-diff.service"; +import { SinglePackageImportService } from "../../../../src/commands/configuration-management/single-package-import.service"; +import { buildTestProgram } from "../../../utls/cli-program"; +import { loggingTestTransport } from "../../../jest.setup"; + +jest.mock("../../../../src/commands/configuration-management/config-command.service"); +jest.mock("../../../../src/commands/configuration-management/staging-package.service"); +jest.mock("../../../../src/commands/configuration-management/metadata.service"); +jest.mock("../../../../src/commands/t2tc/t2tc-command.service"); +jest.mock("../../../../src/commands/configuration-management/node-dependency.service"); +jest.mock("../../../../src/commands/configuration-management/node-diff.service"); +jest.mock("../../../../src/commands/configuration-management/package-version-command.service"); +jest.mock("../../../../src/commands/configuration-management/single-package-import.service"); + +describe("configuration-management command integration", () => { + let program: Command; + let mockConfigCommandService: jest.Mocked; + let mockStagingPackageService: jest.Mocked; + let mockMetadataService: jest.Mocked; + let mockT2tcCommandService: jest.Mocked; + let mockNodeDependencyService: jest.Mocked; + let mockNodeDiffService: jest.Mocked; + let mockPackageVersionCommandService: jest.Mocked; + let mockSinglePackageImportService: jest.Mocked; + + beforeEach(() => { + mockConfigCommandService = { + listVariables: jest.fn().mockResolvedValue(undefined), + } as any; + + mockStagingPackageService = { + listStagingPackages: jest.fn().mockResolvedValue(undefined), + } as any; + + mockMetadataService = { + exportPackagesMetadata: jest.fn().mockResolvedValue(undefined), + } as any; + + mockT2tcCommandService = { + listPackages: jest.fn().mockResolvedValue(undefined), + batchExportPackages: jest.fn().mockResolvedValue(undefined), + batchImportPackages: jest.fn().mockResolvedValue(undefined), + diffPackages: jest.fn().mockResolvedValue(undefined), + } as any; + + mockNodeDependencyService = { + listNodeDependencies: jest.fn().mockResolvedValue(undefined), + } as any; + + mockNodeDiffService = { + diff: jest.fn().mockResolvedValue(undefined), + diffWithFile: jest.fn().mockResolvedValue(undefined), + } as any; + + mockPackageVersionCommandService = { + createPackageVersion: jest.fn().mockResolvedValue(undefined), + getPackageVersion: jest.fn().mockResolvedValue(undefined), + } as any; + + mockSinglePackageImportService = { + importPackage: jest.fn().mockResolvedValue(undefined), + } as any; + + (ConfigCommandService as jest.MockedClass).mockImplementation(() => mockConfigCommandService); + (StagingPackageService as jest.MockedClass).mockImplementation(() => mockStagingPackageService); + (MetadataService as jest.MockedClass).mockImplementation(() => mockMetadataService); + (T2tcCommandService as jest.MockedClass).mockImplementation(() => mockT2tcCommandService); + (NodeDependencyService as jest.MockedClass).mockImplementation(() => mockNodeDependencyService); + (NodeDiffService as jest.MockedClass).mockImplementation(() => mockNodeDiffService); + (PackageVersionCommandService as jest.MockedClass).mockImplementation(() => mockPackageVersionCommandService); + (SinglePackageImportService as jest.MockedClass).mockImplementation(() => mockSinglePackageImportService); + + program = buildTestProgram([Module]); + }); + + function runCli(args: string[]): Promise { + return program.parseAsync(["node", "content-cli", ...args]); + } + + /** + * Action-body validation errors (`throw new Error(...)`) are caught by + * Configurator.action and re-emitted via `logger.error(...)`, so we + * inspect the in-memory winston transport instead of asserting on + * promise rejection. The level field is colorized by `winston.format.cli()`, + * hence the substring match. + */ + function expectErrorLogged(message: string): void { + expect(loggingTestTransport.logMessages).toEqual(expect.arrayContaining([ + expect.objectContaining({ + level: expect.stringContaining("error"), + message: expect.stringContaining(message), + }), + ])); + } + + describe("config list (deprecated listPackages)", () => { + it("rejects when both --packageKeys and --keysByVersion are provided", async () => { + await runCli([ + "config", "list", + "--packageKeys", "package1", "package2", + "--keysByVersion", "package3.1.0.0", "package4.1.0.0", + ]); + + expectErrorLogged("Please provide either --packageKeys or --keysByVersion, but not both."); + expect(mockT2tcCommandService.listPackages).not.toHaveBeenCalled(); + }); + + it("forwards only --packageKeys when provided", async () => { + await runCli([ + "config", "list", + "--packageKeys", "package1", "package2", + "--json", + ]); + + expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( + true, + undefined, + "", + ["package1", "package2"], + undefined, + undefined, + undefined, + false, + false + ); + }); + + it("forwards only --keysByVersion when provided", async () => { + await runCli([ + "config", "list", + "--keysByVersion", "package3.1.0.0", "package4.1.0.0", + "--json", + ]); + + expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( + true, + undefined, + "", + undefined, + ["package3.1.0.0", "package4.1.0.0"], + undefined, + undefined, + false, + false + ); + }); + + it("forwards --branches when provided", async () => { + await runCli(["config", "list", "--branches", "--json"]); + + expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( + true, + undefined, + "", + undefined, + undefined, + undefined, + undefined, + true, + false + ); + }); + + describe("--staging incompatibility", () => { + it("forwards --staging --branches without other filters", async () => { + await runCli(["config", "list", "--branches", "--staging", "--json"]); + + expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( + true, + undefined, + "", + undefined, + undefined, + undefined, + undefined, + true, + true + ); + }); + + it("rejects --staging combined with --packageKeys", async () => { + await runCli([ + "config", "list", + "--staging", + "--packageKeys", "package1", "package2", + ]); + + expectErrorLogged( + "Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType" + ); + }); + + it("rejects --staging combined with --withDependencies", async () => { + await runCli(["config", "list", "--staging", "--withDependencies"]); + + expectErrorLogged( + "Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType" + ); + }); + + it("rejects --staging combined with --keysByVersion", async () => { + await runCli([ + "config", "list", + "--staging", + "--keysByVersion", "package3.1.0.0", "package4.1.0.0", + ]); + + expectErrorLogged( + "Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType" + ); + }); + + it("rejects --staging combined with --variableValue", async () => { + await runCli(["config", "list", "--staging", "--variableValue", "myValue"]); + + expectErrorLogged( + "Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType" + ); + }); + + it("rejects --staging combined with --variableType", async () => { + await runCli(["config", "list", "--staging", "--variableType", "myType"]); + + expectErrorLogged( + "Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType" + ); + }); + }); + }); + + describe("config package list (listStagingPackages)", () => { + it("forwards --json and --flavors", async () => { + await runCli(["config", "package", "list", "--json", "--flavors", "APP"]); + + expect(mockStagingPackageService.listStagingPackages).toHaveBeenCalledWith( + ["APP"], + false, + true + ); + }); + + it("defaults flavors to an empty list and json to '' when not provided", async () => { + await runCli(["config", "package", "list"]); + + expect(mockStagingPackageService.listStagingPackages).toHaveBeenCalledWith([], false, ""); + }); + + it("forwards multiple --flavors values", async () => { + await runCli(["config", "package", "list", "--flavors", "APP", "ANALYSIS"]); + + expect(mockStagingPackageService.listStagingPackages).toHaveBeenCalledWith( + ["APP", "ANALYSIS"], + false, + "" + ); + expect(mockT2tcCommandService.listPackages).not.toHaveBeenCalled(); + }); + }); + + describe("config export (deprecated batchExportPackages)", () => { + it("rejects when both --packageKeys and --keysByVersion are provided", async () => { + await runCli([ + "config", "export", + "--packageKeys", "package1", "package2", + "--keysByVersion", "package3:v1", "package4:v2", + ]); + + expectErrorLogged("Please provide either --packageKeys or --keysByVersion, but not both."); + expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("rejects when neither --packageKeys nor --keysByVersion are provided", async () => { + await runCli(["config", "export"]); + + expectErrorLogged("Please provide either --packageKeys or --keysByVersion, but not both."); + expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("forwards only --packageKeys when provided", async () => { + await runCli(["config", "export", "--packageKeys", "package1", "package2"]); + + expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1", "package2"], + undefined, + "", + undefined, + "" + ); + }); + + it("forwards only --keysByVersion when provided", async () => { + await runCli(["config", "export", "--keysByVersion", "package3:v1", "package4:v2"]); + + expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( + undefined, + ["package3:v1", "package4:v2"], + "", + undefined, + "" + ); + }); + + it("rejects when --gitProfile is provided without --gitBranch", async () => { + await runCli([ + "config", "export", + "--packageKeys", "package1", + "--gitProfile", "myProfile", + ]); + + expectErrorLogged("Please specify a branch using --gitBranch when using a Git profile."); + expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("forwards --gitProfile + --gitBranch", async () => { + await runCli([ + "config", "export", + "--packageKeys", "package1", + "--gitProfile", "myProfile", + "--gitBranch", "main", + ]); + + expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + "", + "main", + "" + ); + }); + + it("forwards --withDependencies", async () => { + await runCli([ + "config", "export", + "--packageKeys", "package1", + "--withDependencies", + ]); + + expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + true, + undefined, + "" + ); + }); + + it("forwards --unzip", async () => { + await runCli([ + "config", "export", + "--packageKeys", "package1", + "--unzip", + ]); + + expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( + ["package1"], + undefined, + "", + undefined, + true + ); + }); + + it("fails on packageKeys/keysByVersion conflict before checking --gitProfile", async () => { + await runCli([ + "config", "export", + "--packageKeys", "package1", + "--keysByVersion", "package2:v1", + "--gitProfile", "myProfile", + ]); + + expectErrorLogged("Please provide either --packageKeys or --keysByVersion, but not both."); + expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + }); + + describe("config import (deprecated batchImportPackages)", () => { + it("rejects when --gitProfile is provided without --gitBranch", async () => { + await runCli([ + "config", "import", + "--file", "export.zip", + "--gitProfile", "myProfile", + ]); + + expectErrorLogged("Please specify a branch using --gitBranch when using a Git profile."); + expect(mockT2tcCommandService.batchImportPackages).not.toHaveBeenCalled(); + }); + + it("forwards --gitProfile + --gitBranch with --file", async () => { + await runCli([ + "config", "import", + "--file", "export.zip", + "--gitProfile", "myProfile", + "--gitBranch", "main", + ]); + + expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( + "export.zip", + undefined, + undefined, + "main", + false + ); + }); + + it("forwards --directory + --gitBranch", async () => { + await runCli([ + "config", "import", + "--directory", "./exported", + "--gitBranch", "develop", + ]); + + expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( + undefined, + "./exported", + undefined, + "develop", + false + ); + }); + + it("forwards minimal --file invocation", async () => { + await runCli(["config", "import", "--file", "export.zip"]); + + expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( + "export.zip", + undefined, + undefined, + undefined, + false + ); + }); + + it("forwards --directory invocation", async () => { + await runCli(["config", "import", "--directory", "./my-exports"]); + + expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( + undefined, + "./my-exports", + undefined, + undefined, + false + ); + }); + + it("forwards --overwrite", async () => { + await runCli(["config", "import", "--file", "export.zip", "--overwrite"]); + + expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( + "export.zip", + undefined, + true, + undefined, + false + ); + }); + + it("forwards combined options", async () => { + await runCli([ + "config", "import", + "--directory", "./exports", + "--overwrite", + "--gitBranch", "feature-branch", + ]); + + expect(mockT2tcCommandService.batchImportPackages).toHaveBeenCalledWith( + undefined, + "./exports", + true, + "feature-branch", + false + ); + }); + }); + + describe("config package import (importSinglePackage)", () => { + it("forwards --file", async () => { + await runCli(["config", "package", "import", "--file", "single-package.zip"]); + + expect(mockSinglePackageImportService.importPackage).toHaveBeenCalledWith( + "single-package.zip", + undefined, + undefined, + undefined + ); + }); + + it("forwards --directory", async () => { + await runCli(["config", "package", "import", "--directory", "./single-package-dir"]); + + expect(mockSinglePackageImportService.importPackage).toHaveBeenCalledWith( + undefined, + "./single-package-dir", + undefined, + undefined + ); + }); + + it("forwards --overwrite and --json", async () => { + await runCli([ + "config", "package", "import", + "--file", "single-package.zip", + "--overwrite", + "--json", + ]); + + expect(mockSinglePackageImportService.importPackage).toHaveBeenCalledWith( + "single-package.zip", + undefined, + true, + true + ); + }); + }); + + describe("config variables list (listVariables)", () => { + it("rejects when --packageKeys and --keysByVersion are both provided", async () => { + await runCli([ + "config", "variables", "list", + "--packageKeys", "pkg-a", + "--keysByVersion", "key-1:1.0.0", + ]); + + expectErrorLogged( + "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." + ); + expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); + }); + + it("rejects when --packageKeys and --keysByVersionFile are both provided", async () => { + await runCli([ + "config", "variables", "list", + "--packageKeys", "pkg-a", + "--keysByVersionFile", "mapping.json", + ]); + + expectErrorLogged( + "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." + ); + expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); + }); + + it("rejects when neither staging nor versioned inputs are provided", async () => { + await runCli(["config", "variables", "list"]); + + expectErrorLogged( + "Please provide --packageKeys for staging, or --keysByVersion / --keysByVersionFile for versioned packages." + ); + expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); + }); + + it("forwards staging-only variant when only --packageKeys is provided", async () => { + await runCli([ + "config", "variables", "list", + "--packageKeys", "pkg-a", "pkg-b", + "--json", + ]); + + expect(mockConfigCommandService.listVariables).toHaveBeenCalledWith( + true, + [], + "", + ["pkg-a", "pkg-b"] + ); + }); + + it("forwards versioned variant when only --keysByVersion is provided", async () => { + await runCli([ + "config", "variables", "list", + "--keysByVersion", "k:v", + ]); + + expect(mockConfigCommandService.listVariables).toHaveBeenCalledWith( + "", + ["k:v"], + "", + [] + ); + }); + }); + + describe("config versions create (createPackageVersion)", () => { + it("rejects when both --packageVersion and --versionBumpOption PATCH are provided", async () => { + await runCli([ + "config", "versions", "create", + "--packageKey", "my-package", + "--packageVersion", "1.2.0", + "--versionBumpOption", "PATCH", + ]); + + expectErrorLogged("Please provide either --packageVersion or --versionBumpOption, but not both."); + expect(mockPackageVersionCommandService.createPackageVersion).not.toHaveBeenCalled(); + }); + + it("rejects when --versionBumpOption is explicit NONE without --packageVersion", async () => { + await runCli([ + "config", "versions", "create", + "--packageKey", "my-package", + "--versionBumpOption", "NONE", + ]); + + expectErrorLogged("Please provide either --packageVersion or --versionBumpOption PATCH."); + expect(mockPackageVersionCommandService.createPackageVersion).not.toHaveBeenCalled(); + }); + + it("rejects when --packageVersion is missing and --versionBumpOption defaults to NONE", async () => { + await runCli([ + "config", "versions", "create", + "--packageKey", "my-package", + ]); + + expectErrorLogged("Please provide either --packageVersion or --versionBumpOption PATCH."); + expect(mockPackageVersionCommandService.createPackageVersion).not.toHaveBeenCalled(); + }); + + it("forwards --packageVersion + --summaryOfChanges", async () => { + await runCli([ + "config", "versions", "create", + "--packageKey", "my-package", + "--packageVersion", "1.2.0", + "--versionBumpOption", "NONE", + "--summaryOfChanges", "New features", + ]); + + expect(mockPackageVersionCommandService.createPackageVersion).toHaveBeenCalledWith( + "my-package", + "1.2.0", + "NONE", + "New features", + undefined, + undefined + ); + }); + + it("forwards --versionBumpOption PATCH only", async () => { + await runCli([ + "config", "versions", "create", + "--packageKey", "my-package", + "--versionBumpOption", "PATCH", + "--summaryOfChanges", "Bug fixes", + ]); + + expect(mockPackageVersionCommandService.createPackageVersion).toHaveBeenCalledWith( + "my-package", + undefined, + "PATCH", + "Bug fixes", + undefined, + undefined + ); + }); + }); + + describe("config nodes dependencies list (listNodeDependencies)", () => { + it("forwards required arguments", async () => { + await runCli([ + "config", "nodes", "dependencies", "list", + "--packageKey", "test-package", + "--nodeKey", "test-node", + "--packageVersion", "1.0.0", + ]); + + expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( + "test-package", + "test-node", + "1.0.0", + undefined + ); + }); + + it("forwards --json", async () => { + await runCli([ + "config", "nodes", "dependencies", "list", + "--packageKey", "test-package", + "--nodeKey", "test-node", + "--packageVersion", "2.0.0", + "--json", + ]); + + expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( + "test-package", + "test-node", + "2.0.0", + true + ); + }); + + it("calls listNodeDependencies exactly once", async () => { + await runCli([ + "config", "nodes", "dependencies", "list", + "--packageKey", "my-package", + "--nodeKey", "my-node", + "--packageVersion", "1.2.3", + "--json", + ]); + + expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledTimes(1); + expect(mockNodeDependencyService.listNodeDependencies).toHaveBeenCalledWith( + "my-package", + "my-node", + "1.2.3", + true + ); + }); + }); + + describe("config diff (diffPackages)", () => { + it("forwards minimal --file invocation", async () => { + await runCli(["config", "diff", "--file", "package.zip"]); + + expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( + "package.zip", undefined, undefined, undefined + ); + }); + + it("forwards --json", async () => { + await runCli(["config", "diff", "--file", "package.zip", "--json"]); + + expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( + "package.zip", undefined, undefined, true + ); + }); + + it("forwards --hasChanges", async () => { + await runCli(["config", "diff", "--file", "package.zip", "--hasChanges"]); + + expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( + "package.zip", true, undefined, undefined + ); + }); + + it("forwards --baseVersion", async () => { + await runCli([ + "config", "diff", + "--file", "package.zip", + "--baseVersion", "1.0.0", + ]); + + expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( + "package.zip", undefined, "1.0.0", undefined + ); + }); + + it("forwards --hasChanges + --baseVersion together", async () => { + await runCli([ + "config", "diff", + "--file", "package.zip", + "--hasChanges", + "--baseVersion", "STAGING", + ]); + + expect(mockT2tcCommandService.diffPackages).toHaveBeenCalledWith( + "package.zip", true, "STAGING", undefined + ); + }); + }); + + describe("config nodes diff (diffNode)", () => { + it("rejects when both --file and --compareVersion are provided", async () => { + await runCli([ + "config", "nodes", "diff", + "--packageKey", "test-package", + "--nodeKey", "test-node", + "--baseVersion", "STAGING", + "--compareVersion", "1.0.0", + "--file", "./node.json", + ]); + + expectErrorLogged("Please provide either --compareVersion or --file, but not both."); + expect(mockNodeDiffService.diff).not.toHaveBeenCalled(); + expect(mockNodeDiffService.diffWithFile).not.toHaveBeenCalled(); + }); + + it("rejects when neither --file nor --compareVersion is provided", async () => { + await runCli([ + "config", "nodes", "diff", + "--packageKey", "test-package", + "--nodeKey", "test-node", + "--baseVersion", "STAGING", + ]); + + expectErrorLogged("Please provide either --compareVersion or --file, but not both."); + expect(mockNodeDiffService.diff).not.toHaveBeenCalled(); + expect(mockNodeDiffService.diffWithFile).not.toHaveBeenCalled(); + }); + + it("calls diff when only --compareVersion is provided", async () => { + await runCli([ + "config", "nodes", "diff", + "--packageKey", "test-package", + "--nodeKey", "test-node", + "--baseVersion", "STAGING", + "--compareVersion", "1.0.0", + "--json", + ]); + + expect(mockNodeDiffService.diff).toHaveBeenCalledWith( + "test-package", + "test-node", + "STAGING", + "1.0.0", + true + ); + expect(mockNodeDiffService.diffWithFile).not.toHaveBeenCalled(); + }); + + it("calls diffWithFile when only --file is provided", async () => { + await runCli([ + "config", "nodes", "diff", + "--packageKey", "test-package", + "--nodeKey", "test-node", + "--baseVersion", "STAGING", + "--file", "./node.json", + ]); + + expect(mockNodeDiffService.diffWithFile).toHaveBeenCalledWith( + "test-package", + "test-node", + "STAGING", + "./node.json", + undefined + ); + expect(mockNodeDiffService.diff).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/utls/cli-program.ts b/tests/utls/cli-program.ts new file mode 100644 index 0000000..825fc0a --- /dev/null +++ b/tests/utls/cli-program.ts @@ -0,0 +1,8 @@ +import { Command } from "commander"; +import { createProgram } from "../../src/content-cli"; +import { IModuleConstructor } from "../../src/core/command/module-handler"; +import { testContext } from "./test-context"; + +export function buildTestProgram(modules: IModuleConstructor[]): Command { + return createProgram(testContext, { modules }); +} From 8fb0e9e1d646f8438992078e7302afa272554958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 08:54:15 +0200 Subject: [PATCH 3/9] Remove plan --- ...command_integration_tests_e980d6d5.plan.md | 141 ------------------ 1 file changed, 141 deletions(-) delete mode 100644 .cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md diff --git a/.cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md b/.cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md deleted file mode 100644 index 98da8ff..0000000 --- a/.cursor/plans/sp-1089_command_integration_tests_e980d6d5.plan.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -name: SP-1089 command integration tests -overview: Refactor `src/content-cli.ts` to expose a `createProgram(context)` factory, then convert the two existing module specs into Commander-driven integration tests under `tests/integration/commands/`. -todos: - - id: factory - content: "Refactor src/content-cli.ts: extract createProgram(context, { modules? }) factory, move banner/parseOptions/run into a guarded bootstrap, switch to parseAsync" - status: pending - - id: helper - content: Add tests/utls/cli-program.ts with buildTestProgram(modules) helper that wires testContext into createProgram - status: pending - - id: asset-registry-it - content: Move and rewrite asset-registry-module.spec.ts as tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts using parseAsync - status: pending - - id: config-mgmt-it - content: Move and rewrite configuration-management/module.spec.ts as tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts; convert .rejects.toThrow assertions to logger transport assertions - status: pending - - id: verify - content: Run the full jest suite and a build smoke test (node dist/content-cli.js --help) to confirm no regressions - status: pending -isProject: false ---- - -## Goal - -SP-1089 — drive Content CLI command tests through the real Commander parser instead of poking private action methods. The two existing "module" specs only exercise the action callbacks; they bypass Commander's command registration, option parsing, defaults, type coercion, and validation. After this change, tests will mirror what an end user types and run through the same parse path the binary uses. - -## Files in scope - -- [src/content-cli.ts](src/content-cli.ts) — refactor to expose a factory. -- [src/core/command/module-handler.ts](src/core/command/module-handler.ts) — read-only reference; do not change behaviour. The `Configurator.action` wrapper logs and swallows errors, which informs how tests must assert validation failures (see "Error assertions" below). -- [tests/commands/configuration-management/module.spec.ts](tests/commands/configuration-management/module.spec.ts) — rewrite & relocate. -- [tests/commands/asset-registry/asset-registry-module.spec.ts](tests/commands/asset-registry/asset-registry-module.spec.ts) — rewrite & relocate. -- [tests/utls/test-context.ts](tests/utls/test-context.ts) — reuse, add a small CLI helper next to it. - -The service-level specs (e.g. [tests/commands/asset-registry/asset-registry-list.spec.ts](tests/commands/asset-registry/asset-registry-list.spec.ts), the bulk of the `configuration-management/*.spec.ts` tree) are untouched — they cover service logic against mocked HTTP, which is orthogonal to argument parsing. - -## 1. Expose a `createProgram` factory - -Today `src/content-cli.ts` constructs `program` at module top level, calls `parseOptions(process.argv)`, prints the version banner, builds a `Context`, and finally calls `program.parse(process.argv)` inside `run()`. Importing this file from a test would execute all of that. - -Refactor by extracting two pure pieces and gating the bootstrap: - -- `export async function createProgram(context: Context, opts?: { modules?: IModuleConstructor[] }): Promise` — builds a fresh `Command`, configures help / version / global options, wires a `ModuleHandler`, calls `configureRootCommands`, and either auto-discovers modules (production) or registers the explicit list passed in (tests). Returns the configured program without parsing argv. -- `async function run(): Promise` — keeps the existing banner / debug-log / context-init / `parseAsync(process.argv)` flow, calling `createProgram(context)`. -- Replace top-level `run();` with `if (require.main === module) { run(); }` so importing the file from tests is a no-op. The version banner, debug-level switch, and `parseOptions(process.argv)` block move inside `run()` (they are CLI-bootstrap concerns, not factory concerns). - -Switch the parse to `parseAsync(process.argv)` so async actions are awaited end-to-end (matches what tests will use). The `try/catch` around it stays. - -Sketch of the factory shape: - -```typescript -export async function createProgram( - context: Context, - opts: { modules?: IModuleConstructor[] } = {}, -): Promise { - const program = new Command(); - program.configureHelp({ /* unchanged */ }); - program.version(VersionUtils.getCurrentCliVersion()); - program.option("-q, --quietmode", "Reduce output to a minimum", false); - program.option("-p, --profile [profile]"); - program.option("--gitProfile [gitProfile]", "Git profile to use"); - program.option("--debug", "Print debug messages", false); - program.option("--dev", "Development Mode", false); - - const moduleHandler = new ModuleHandler(program, context); - configureRootCommands(moduleHandler.configurator); - - if (opts.modules) { - for (const ModuleClass of opts.modules) { - new ModuleClass().register(context, moduleHandler.configurator); - } - } else { - moduleHandler.discoverAndRegisterModules(__dirname, program.opts().dev); - } - - return program; -} -``` - -Tests pass `{ modules: [Module] }` so they avoid filesystem-based discovery (which would conflict with the `jest.mock("fs")` setup in [tests/jest.setup.ts](tests/jest.setup.ts)). - -## 2. Add a tiny CLI test helper - -New file `tests/utls/cli-program.ts`: - -- Re-exports a small `buildTestProgram(modules: IModuleConstructor[]): Promise` that calls `createProgram(testContext, { modules })`. -- Tests do not need profile/HTTP changes — they continue to import `testContext` from [tests/utls/test-context.ts](tests/utls/test-context.ts), and individual specs `jest.mock(...)` the underlying services so no real HTTP is hit. - -## 3. Move and rewrite the two module specs - -Move: - -- `tests/commands/configuration-management/module.spec.ts` → `tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts` -- `tests/commands/asset-registry/asset-registry-module.spec.ts` → `tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts` - -`testMatch` in [jest.config.ts](jest.config.ts) is `/tests/**/*.spec.ts`, so the new path is picked up automatically. - -Inside each new spec: - -- Drop `(module as any).method(testContext, mockCommand, options)` calls. Replace with `await program.parseAsync(["node", "content-cli", ...args])`. -- Build the program once per test (or per `beforeEach`) via `buildTestProgram([Module])` so each test has a clean Commander state. -- Keep `jest.mock(...)` of the downstream services and the existing `mockImplementation(() => mockService)` pattern. Assertions on `mockService.` parameters stay almost identical, but values like `withDependencies` will now reflect Commander's parsing (e.g. an absent boolean flag becomes `undefined` rather than the explicit `false` the action body forces). -- Rename `describe` blocks to " command integration" to match the file naming. - -Example translation for `asset-registry list --json`: - -```typescript -const program = await buildTestProgram([Module]); -await program.parseAsync(["node", "content-cli", "asset-registry", "list", "--json"]); -expect(mockService.listTypes).toHaveBeenCalledWith(true); -``` - -Equivalent rewrite for the existing private-method test in [tests/commands/asset-registry/asset-registry-module.spec.ts](tests/commands/asset-registry/asset-registry-module.spec.ts). - -## 4. Error / validation assertions - -The current specs assert `await expect((module as any).fn(...)).rejects.toThrow("…")`. That works only because the test bypasses `Configurator.action`, which catches every error and logs `An unexpected error occured executing a command: `. Once tests go through `parseAsync`, the Configurator swallows the throw and `parseAsync` resolves cleanly. Two changes: - -- Use the existing `loggingTestTransport` from [tests/jest.setup.ts](tests/jest.setup.ts) to assert the validation message reached the user: - ```typescript - expect(loggingTestTransport.logMessages).toEqual(expect.arrayContaining([ - expect.objectContaining({ - level: "error", - message: expect.stringContaining("Please provide either --packageKeys or --keysByVersion, but not both."), - }), - ])); - ``` -- Continue to assert `expect(mockService.fn).not.toHaveBeenCalled()` so the negative path is still tightened. - -This is a more accurate test of what the user actually sees and keeps the production `Configurator` behaviour unchanged. - -## 5. Verification - -- `yarn jest` (or the project's existing script) — both rewritten specs and untouched service specs must stay green. -- Manual smoke: `yarn build && node dist/content-cli.js --help` to ensure the bootstrap refactor did not regress the binary entry. - -## Out of scope - -- Real HTTP / nock-based end-to-end tests — that is SP-1063 and explicitly a follow-up. -- Touching service-level specs that already exercise mocked axios. -- Behaviour changes to the `Configurator` error-handling wrapper. \ No newline at end of file From d38b602c10b36e0c4789db7caa4b94f6ad6dfb61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 08:56:10 +0200 Subject: [PATCH 4/9] Move method --- src/content-cli.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/content-cli.ts b/src/content-cli.ts index 0fc566c..5bbd8e3 100644 --- a/src/content-cli.ts +++ b/src/content-cli.ts @@ -16,12 +16,6 @@ import { ContentCLIHelp } from "./core/command/CustomHelp"; const requiredVersion = ">=10.10.0"; -function configureRootCommands(configurator: Configurator): void { - configurator.command("list") - .description("Commands to list content.") - .alias("ls"); -} - export interface CreateProgramOptions { /** * Explicit list of module classes to register. When provided, the factory @@ -78,6 +72,10 @@ export function createProgram(context: Context, opts: CreateProgramOptions = {}) return program; } +function configureRootCommands(configurator: Configurator): void { + configurator.command("list").description("Commands to list content.").alias("ls"); +} + async function run(): Promise { if (!semverSatisfies(process.version, requiredVersion)) { logger.error( From 844a91585e8a9e1f23efc4f26b55259425c777ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 08:56:37 +0200 Subject: [PATCH 5/9] Re-add comment --- src/content-cli.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/content-cli.ts b/src/content-cli.ts index 5bbd8e3..5b12b0c 100644 --- a/src/content-cli.ts +++ b/src/content-cli.ts @@ -72,6 +72,10 @@ export function createProgram(context: Context, opts: CreateProgramOptions = {}) return program; } +/** + * To support the legacy command structure, we have to configure some root commands + * that the individual modules will extend. + */ function configureRootCommands(configurator: Configurator): void { configurator.command("list").description("Commands to list content.").alias("ls"); } From e8db442ad5c2b9ef7af36688efe33b80c34cf0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 08:58:28 +0200 Subject: [PATCH 6/9] Adjust comment --- src/content-cli.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/content-cli.ts b/src/content-cli.ts index 5b12b0c..602eef6 100644 --- a/src/content-cli.ts +++ b/src/content-cli.ts @@ -36,10 +36,7 @@ export interface CreateProgramOptions { } /** - * Build a fully-configured Commander program without parsing argv. The bin - * entry uses this with auto-discovery; tests pass `{ modules: [...] }` to - * register only the modules under test and avoid the filesystem walk (which - * conflicts with the `jest.mock("fs")` setup). + * Build a fully-configured Commander program without parsing argv */ export function createProgram(context: Context, opts: CreateProgramOptions = {}): Command { const program = new Command(); From ad83c5924b12a89366ea431e7483aa3f86417101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 09:00:57 +0200 Subject: [PATCH 7/9] Drop unused config flag --- src/content-cli.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/content-cli.ts b/src/content-cli.ts index 602eef6..f15e769 100644 --- a/src/content-cli.ts +++ b/src/content-cli.ts @@ -19,15 +19,9 @@ const requiredVersion = ">=10.10.0"; export interface CreateProgramOptions { /** * Explicit list of module classes to register. When provided, the factory - * skips filesystem-based module discovery — useful for tests that want to - * exercise a single module against the real Commander parser. + * skips automatic, filesystem-based module discovery. */ modules?: IModuleConstructor[]; - /** - * Override the root path used for filesystem-based module discovery. - * Defaults to the directory of this file (i.e. `dist/` at runtime). - */ - rootPath?: string; /** * Force development mode for module discovery (looks up `module.ts` * instead of `module.js`). Defaults to `program.opts().dev`. @@ -61,7 +55,7 @@ export function createProgram(context: Context, opts: CreateProgramOptions = {}) moduleInstance.register(context, moduleHandler.configurator); } } else { - const rootPath = opts.rootPath ?? __dirname; + const rootPath = __dirname; const devMode = opts.devMode ?? !!program.opts().dev; moduleHandler.discoverAndRegisterModules(rootPath, devMode); } From 366e0eed6da6cddc98b3c4847ec8041ab67af14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 09:02:04 +0200 Subject: [PATCH 8/9] Remove comment --- src/content-cli.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/content-cli.ts b/src/content-cli.ts index f15e769..e05648c 100644 --- a/src/content-cli.ts +++ b/src/content-cli.ts @@ -22,10 +22,6 @@ export interface CreateProgramOptions { * skips automatic, filesystem-based module discovery. */ modules?: IModuleConstructor[]; - /** - * Force development mode for module discovery (looks up `module.ts` - * instead of `module.js`). Defaults to `program.opts().dev`. - */ devMode?: boolean; } From 69320aa8a4143daedfa6d7abede3b33b54116196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Neum=C3=BCller?= Date: Thu, 11 Jun 2026 09:11:09 +0200 Subject: [PATCH 9/9] Move and rename specs --- ...gration.spec.ts => asset-registry.spec.ts} | 8 ++-- ...ec.ts => configuration-management.spec.ts} | 40 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) rename tests/integration/commands/{asset-registry/asset-registry.command-integration.spec.ts => asset-registry.spec.ts} (94%) rename tests/integration/commands/{configuration-management/configuration-management.command-integration.spec.ts => configuration-management.spec.ts} (94%) diff --git a/tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts b/tests/integration/commands/asset-registry.spec.ts similarity index 94% rename from tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts rename to tests/integration/commands/asset-registry.spec.ts index 07adacc..3829b35 100644 --- a/tests/integration/commands/asset-registry/asset-registry.command-integration.spec.ts +++ b/tests/integration/commands/asset-registry.spec.ts @@ -1,9 +1,9 @@ -import Module = require("../../../../src/commands/asset-registry/module"); +import Module = require("../../../src/commands/asset-registry/module"); import { Command } from "commander"; -import { AssetRegistryService } from "../../../../src/commands/asset-registry/asset-registry.service"; -import { buildTestProgram } from "../../../utls/cli-program"; +import { AssetRegistryService } from "../../../src/commands/asset-registry/asset-registry.service"; +import { buildTestProgram } from "../../utls/cli-program"; -jest.mock("../../../../src/commands/asset-registry/asset-registry.service"); +jest.mock("../../../src/commands/asset-registry/asset-registry.service"); describe("asset-registry command integration", () => { let program: Command; diff --git a/tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts b/tests/integration/commands/configuration-management.spec.ts similarity index 94% rename from tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts rename to tests/integration/commands/configuration-management.spec.ts index 5b5d044..cdb6bbe 100644 --- a/tests/integration/commands/configuration-management/configuration-management.command-integration.spec.ts +++ b/tests/integration/commands/configuration-management.spec.ts @@ -1,24 +1,24 @@ -import Module = require("../../../../src/commands/configuration-management/module"); +import Module = require("../../../src/commands/configuration-management/module"); import { Command } from "commander"; -import { ConfigCommandService } from "../../../../src/commands/configuration-management/config-command.service"; -import { StagingPackageService } from "../../../../src/commands/configuration-management/staging-package.service"; -import { MetadataService } from "../../../../src/commands/configuration-management/metadata.service"; -import { T2tcCommandService } from "../../../../src/commands/t2tc/t2tc-command.service"; -import { NodeDependencyService } from "../../../../src/commands/configuration-management/node-dependency.service"; -import { PackageVersionCommandService } from "../../../../src/commands/configuration-management/package-version-command.service"; -import { NodeDiffService } from "../../../../src/commands/configuration-management/node-diff.service"; -import { SinglePackageImportService } from "../../../../src/commands/configuration-management/single-package-import.service"; -import { buildTestProgram } from "../../../utls/cli-program"; -import { loggingTestTransport } from "../../../jest.setup"; - -jest.mock("../../../../src/commands/configuration-management/config-command.service"); -jest.mock("../../../../src/commands/configuration-management/staging-package.service"); -jest.mock("../../../../src/commands/configuration-management/metadata.service"); -jest.mock("../../../../src/commands/t2tc/t2tc-command.service"); -jest.mock("../../../../src/commands/configuration-management/node-dependency.service"); -jest.mock("../../../../src/commands/configuration-management/node-diff.service"); -jest.mock("../../../../src/commands/configuration-management/package-version-command.service"); -jest.mock("../../../../src/commands/configuration-management/single-package-import.service"); +import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; +import { StagingPackageService } from "../../../src/commands/configuration-management/staging-package.service"; +import { MetadataService } from "../../../src/commands/configuration-management/metadata.service"; +import { T2tcCommandService } from "../../../src/commands/t2tc/t2tc-command.service"; +import { NodeDependencyService } from "../../../src/commands/configuration-management/node-dependency.service"; +import { PackageVersionCommandService } from "../../../src/commands/configuration-management/package-version-command.service"; +import { NodeDiffService } from "../../../src/commands/configuration-management/node-diff.service"; +import { SinglePackageImportService } from "../../../src/commands/configuration-management/single-package-import.service"; +import { buildTestProgram } from "../../utls/cli-program"; +import { loggingTestTransport } from "../../jest.setup"; + +jest.mock("../../../src/commands/configuration-management/config-command.service"); +jest.mock("../../../src/commands/configuration-management/staging-package.service"); +jest.mock("../../../src/commands/configuration-management/metadata.service"); +jest.mock("../../../src/commands/t2tc/t2tc-command.service"); +jest.mock("../../../src/commands/configuration-management/node-dependency.service"); +jest.mock("../../../src/commands/configuration-management/node-diff.service"); +jest.mock("../../../src/commands/configuration-management/package-version-command.service"); +jest.mock("../../../src/commands/configuration-management/single-package-import.service"); describe("configuration-management command integration", () => { let program: Command;