From c9ba4d7a465f5d3f7b12b89bdc7d27f191ef870c Mon Sep 17 00:00:00 2001 From: denniswo Date: Mon, 10 Aug 2026 10:12:35 +0200 Subject: [PATCH 1/4] SP-1173: route already-async --json writes through CuiFileService Sixteen write sites whose enclosing method is already async and awaited to the Commander action, so this is call-site substitution only: no signature changes. Each now logs the returned filename, since marking renames the file to "CUI - .zip". Includes-AI-Code: true Co-authored-by: Cursor --- .../asset-registry/asset-registry.service.ts | 11 ++++--- .../node-dependency.service.ts | 9 ++++-- .../configuration-management/node.service.ts | 19 +++++++----- .../package-validation.service.ts | 8 +++-- .../package-version.service.ts | 13 +++++---- src/commands/deployment/deployment.service.ts | 29 ++++++++++--------- src/commands/studio/service/asset-service.ts | 8 +++-- 7 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/commands/asset-registry/asset-registry.service.ts b/src/commands/asset-registry/asset-registry.service.ts index d850193a..7f5eab2a 100644 --- a/src/commands/asset-registry/asset-registry.service.ts +++ b/src/commands/asset-registry/asset-registry.service.ts @@ -2,14 +2,17 @@ import { AssetRegistryApi } from "./asset-registry-api"; import { AssetRegistryDescriptor, ValidateOptions } from "./asset-registry.interfaces"; import { Context } from "../../core/command/cli-context"; import { fileService, FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { FatalError, logger } from "../../core/utils/logger"; import { v4 as uuidv4 } from "uuid"; export class AssetRegistryService { private readonly api: AssetRegistryApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.api = new AssetRegistryApi(context); + this.cuiFileService = new CuiFileService(context); } public async listTypes(jsonResponse: boolean): Promise { @@ -18,8 +21,8 @@ export class AssetRegistryService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(metadata), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(metadata), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { if (descriptors.length === 0) { logger.info("No asset types registered."); @@ -36,8 +39,8 @@ export class AssetRegistryService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(descriptor), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(descriptor), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { this.logDescriptorDetail(descriptor); } diff --git a/src/commands/configuration-management/node-dependency.service.ts b/src/commands/configuration-management/node-dependency.service.ts index 111f3a67..991b9b90 100644 --- a/src/commands/configuration-management/node-dependency.service.ts +++ b/src/commands/configuration-management/node-dependency.service.ts @@ -1,15 +1,18 @@ import { NodeDependencyApi } from "./api/node-dependency-api"; import { Context } from "../../core/command/cli-context"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { v4 as uuidv4 } from "uuid"; import { NodeDependencyTransport } from "./interfaces/node-dependency.interfaces"; export class NodeDependencyService { private readonly nodeDependencyApi: NodeDependencyApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.nodeDependencyApi = new NodeDependencyApi(context); + this.cuiFileService = new CuiFileService(context); } public async listNodeDependencies(packageKey: string, nodeKey: string, version: string, jsonResponse: boolean): Promise { @@ -23,8 +26,8 @@ export class NodeDependencyService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(dependencies, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(dependencies, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { if (dependencies.length === 0) { logger.info("No dependencies found for this node."); diff --git a/src/commands/configuration-management/node.service.ts b/src/commands/configuration-management/node.service.ts index ca802bc2..5abbfcf9 100644 --- a/src/commands/configuration-management/node.service.ts +++ b/src/commands/configuration-management/node.service.ts @@ -1,15 +1,18 @@ import { NodeApi } from "./api/node-api"; import { Context } from "../../core/command/cli-context"; import { fileService, FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { v4 as uuidv4 } from "uuid"; import { NodeTransport, SaveNodeTransport, UpdateNodeTransport } from "./interfaces/node.interfaces"; export class NodeService { private nodeApi: NodeApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.nodeApi = new NodeApi(context); + this.cuiFileService = new CuiFileService(context); } public async findNode(packageKey: string, nodeKey: string, withConfiguration: boolean, packageVersion: string | null, jsonResponse: boolean): Promise { @@ -19,8 +22,8 @@ export class NodeService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(node, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(node, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { this.printNode(node); } @@ -31,8 +34,8 @@ export class NodeService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(nodes, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(nodes, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { nodes.forEach(node => { logger.info(JSON.stringify(node)) @@ -51,8 +54,8 @@ export class NodeService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(node, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(node, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { this.printNode(node as NodeTransport); } @@ -69,8 +72,8 @@ export class NodeService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(node, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(node, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { this.printNode(node as NodeTransport); } diff --git a/src/commands/configuration-management/package-validation.service.ts b/src/commands/configuration-management/package-validation.service.ts index 6cdc912d..dad71b88 100644 --- a/src/commands/configuration-management/package-validation.service.ts +++ b/src/commands/configuration-management/package-validation.service.ts @@ -3,14 +3,16 @@ import { Context } from "../../core/command/cli-context"; import { PackageValidationApi } from "./api/package-validation-api"; import { PackageValidationRequest, SchemaValidationResponse, SchemaValidationResult } from "./interfaces/package-validation.interfaces"; import { logger } from "../../core/utils/logger"; -import { fileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; export class PackageValidationService { private packageValidationApi: PackageValidationApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.packageValidationApi = new PackageValidationApi(context); + this.cuiFileService = new CuiFileService(context); } public async validatePackage(packageKey: string, layers: string[], nodeKeys: string[], jsonOutput: boolean): Promise { @@ -23,8 +25,8 @@ export class PackageValidationService { if (jsonOutput) { const reportFileName = "config_validate_report_" + uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(response), reportFileName); - logger.info("Validation report file: " + reportFileName); + const writtenFileName = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(response), reportFileName); + logger.info("Validation report file: " + writtenFileName); } else { this.printValidationResult(response); } diff --git a/src/commands/configuration-management/package-version.service.ts b/src/commands/configuration-management/package-version.service.ts index 19e93fdd..f4aa60f2 100644 --- a/src/commands/configuration-management/package-version.service.ts +++ b/src/commands/configuration-management/package-version.service.ts @@ -1,5 +1,6 @@ import { Context } from "../../core/command/cli-context"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { v4 as uuidv4 } from "uuid"; import { PackageVersionApi } from "./api/package-version-api"; @@ -12,17 +13,19 @@ import { export class PackageVersionService { private packageVersionApi: PackageVersionApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.packageVersionApi = new PackageVersionApi(context); + this.cuiFileService = new CuiFileService(context); } public async findPackageVersion(packageKey: string, version: string, jsonResponse: boolean): Promise { const packageVersionTransport: PackageVersionTransport = await this.packageVersionApi.findOne(packageKey, version); if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(packageVersionTransport, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(packageVersionTransport, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { this.printPackageVersionTransport(packageVersionTransport); } @@ -53,8 +56,8 @@ export class PackageVersionService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(created, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(created, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { this.printPackageVersionCreatedTransport(created); } diff --git a/src/commands/deployment/deployment.service.ts b/src/commands/deployment/deployment.service.ts index 16140946..58b98a3d 100644 --- a/src/commands/deployment/deployment.service.ts +++ b/src/commands/deployment/deployment.service.ts @@ -1,15 +1,18 @@ import { DeploymentApi } from "./deployment-api"; import { CreateDeploymentRequest, fromString, GetDeploymentsRequest } from "./deployment.interfaces"; import { Context } from "../../core/command/cli-context"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { v4 as uuidv4 } from "uuid"; export class DeploymentService { private deploymentApi: DeploymentApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.deploymentApi = new DeploymentApi(context); + this.cuiFileService = new CuiFileService(context); } public async createDeployment(packageKey: string, packageVersion: string, deployableType: string, targetId: string, jsonResponse: boolean): Promise { @@ -24,8 +27,8 @@ export class DeploymentService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(deployment), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(deployment), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { logger.info(`Deployment created with ID: ${deployment.id}, Status: ${deployment.status}`); } @@ -50,8 +53,8 @@ export class DeploymentService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(deployments), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(deployments), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { deployments.forEach(deployment => { logger.info(`ID: ${deployment.id}, Package: ${deployment.packageKey}, Version: ${deployment.packageVersion}, Status: ${deployment.status}, Created at: ${new Date(deployment.createdAt).toISOString()}`); @@ -64,8 +67,8 @@ export class DeploymentService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(deployment), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(deployment), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { logger.info(`ID: ${deployment.id}, Package: ${deployment.packageKey}, Version: ${deployment.packageVersion}, Status: ${deployment.status}, Deployed at: ${new Date(deployment.deployedAt).toISOString()}`); } @@ -76,8 +79,8 @@ export class DeploymentService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(deployments), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(deployments), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { deployments.forEach(deployment => { logger.info(`ID: ${deployment.id}, Package: ${deployment.packageKey}, Version: ${deployment.packageVersion}, Status: ${deployment.status}, Deployed at: ${new Date(deployment.deployedAt).toISOString()}`); @@ -90,8 +93,8 @@ export class DeploymentService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(targets), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(targets), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { targets.forEach(target => { logger.info(`ID: ${target.id}, Name: ${target.name}`); @@ -104,8 +107,8 @@ export class DeploymentService { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(deployables), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(deployables), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { deployables.forEach(deployable => { logger.info(`Name: ${deployable.name}, Type: ${deployable.type}`); diff --git a/src/commands/studio/service/asset-service.ts b/src/commands/studio/service/asset-service.ts index d5fa1c81..0ff20448 100644 --- a/src/commands/studio/service/asset-service.ts +++ b/src/commands/studio/service/asset-service.ts @@ -3,15 +3,17 @@ import { Context } from "../../../core/command/cli-context"; import { SaveContentNode } from "../interfaces/save-content-node.interface"; import { AssetApi } from "../api/asset-api"; import { logger } from "../../../core/utils/logger"; -import { fileService } from "../../../core/utils/file-service"; +import { CuiFileService } from "../../../core/utils/cui-file-service"; export class AssetService { protected readonly fileDownloadedMessage = "File downloaded successfully. New filename: "; private assetApi: AssetApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.assetApi = new AssetApi(context); + this.cuiFileService = new CuiFileService(context); } public async listAssets(assetType: string): Promise { @@ -27,7 +29,7 @@ export class AssetService { const nodes: SaveContentNode[] = await this.assetApi.findAllAssets(assetType); const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(nodes, fieldsToInclude), filename); - logger.info(this.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(nodes, fieldsToInclude), filename); + logger.info(this.fileDownloadedMessage + writtenFilename); } } From cff014f7eb2caecd8ac44b9d936c89b581fef433 Mon Sep 17 00:00:00 2001 From: denniswo Date: Mon, 10 Aug 2026 10:17:56 +0200 Subject: [PATCH 2/4] SP-1173: make the --json write helpers async so marking can be awaited Ten private write helpers were synchronous, so they could not await the CUI cover probe. Each becomes async and every caller awaits it. list-assignments used a blanket axios mock that shadowed the shared cover-endpoint default and left the probe without a status; it now goes through mockAxiosGet like the other specs. Includes-AI-Code: true Co-authored-by: Cursor --- .../asset-registry/asset-registry.service.ts | 12 +++++------ .../metadata.service.ts | 13 +++++++----- .../node-diff.service.ts | 15 +++++++------ .../single-package-import.service.ts | 11 ++++++---- .../staging-package.service.ts | 13 +++++++----- .../variable.service.ts | 15 +++++++------ .../data-pool/data-pool-service.ts | 11 ++++++---- src/commands/t2tc/diff.service.ts | 21 +++++++++++-------- src/commands/t2tc/t2tc-package.service.ts | 13 +++++++----- .../list-assignments.spec.ts | 18 ++++++++-------- 10 files changed, 83 insertions(+), 59 deletions(-) diff --git a/src/commands/asset-registry/asset-registry.service.ts b/src/commands/asset-registry/asset-registry.service.ts index 7f5eab2a..e674c761 100644 --- a/src/commands/asset-registry/asset-registry.service.ts +++ b/src/commands/asset-registry/asset-registry.service.ts @@ -48,18 +48,18 @@ export class AssetRegistryService { public async getSchema(assetType: string, jsonResponse: boolean): Promise { const data = await this.api.getSchema(assetType); - this.outputResponse(data, jsonResponse); + await this.outputResponse(data, jsonResponse); } public async getExamples(assetType: string, jsonResponse: boolean): Promise { const data = await this.api.getExamples(assetType); - this.outputResponse(data, jsonResponse); + await this.outputResponse(data, jsonResponse); } public async validate(opts: ValidateOptions): Promise { const payload = this.buildValidatePayload(opts); const data = await this.api.validate(opts.assetType, payload); - this.outputResponse(data, opts.json); + await this.outputResponse(data, opts.json); } private static readonly INLINE_VALIDATION_NODE_KEY = "validation-node"; @@ -115,11 +115,11 @@ export class AssetRegistryService { } } - private outputResponse(data: any, jsonResponse: boolean): void { + private async outputResponse(data: any, jsonResponse: boolean): Promise { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(data, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(data, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } else { logger.info(typeof data === "string" ? data : JSON.stringify(data, null, 2)); } diff --git a/src/commands/configuration-management/metadata.service.ts b/src/commands/configuration-management/metadata.service.ts index 3d098f75..79dde3f3 100644 --- a/src/commands/configuration-management/metadata.service.ts +++ b/src/commands/configuration-management/metadata.service.ts @@ -1,23 +1,26 @@ import { v4 as uuidv4 } from "uuid"; import { Context } from "../../core/command/cli-context"; import { PackageMetadataExportTransport } from "./interfaces/package-export.interfaces"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { MetadataApi } from "./api/metadata-api"; export class MetadataService { private metadataApi: MetadataApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.metadataApi = new MetadataApi(context); + this.cuiFileService = new CuiFileService(context); } public async exportPackagesMetadata(packageKeys: string[], jsonResponse: boolean): Promise { const exportedPackagesMetadata: PackageMetadataExportTransport[] = await this.metadataApi.exportPackagesMetadata(packageKeys); if (jsonResponse) { - this.exportListOfPackagesMetadata(exportedPackagesMetadata); + await this.exportListOfPackagesMetadata(exportedPackagesMetadata); } else { exportedPackagesMetadata.forEach(pkg => { logger.info(`${pkg.key} - Has Unpublished Changes: ${pkg.hasUnpublishedChanges}`); @@ -25,9 +28,9 @@ export class MetadataService { } } - private exportListOfPackagesMetadata(packagesMetadata: PackageMetadataExportTransport[]): void { + private async exportListOfPackagesMetadata(packagesMetadata: PackageMetadataExportTransport[]): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(packagesMetadata), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(packagesMetadata), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } } diff --git a/src/commands/configuration-management/node-diff.service.ts b/src/commands/configuration-management/node-diff.service.ts index 87341dfc..267a9d4c 100644 --- a/src/commands/configuration-management/node-diff.service.ts +++ b/src/commands/configuration-management/node-diff.service.ts @@ -1,16 +1,19 @@ import * as fs from "node:fs"; import { v4 as uuidv4 } from "uuid"; import { logger } from "../../core/utils/logger"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { Context } from "../../core/command/cli-context"; import { NodeDiffApi } from "./api/node-diff-api"; import { NodeConfigurationDiffTransport } from "./interfaces/node-diff.interfaces"; export class NodeDiffService { private nodeDiffApi: NodeDiffApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.nodeDiffApi = new NodeDiffApi(context); + this.cuiFileService = new CuiFileService(context); } public async diff( @@ -28,7 +31,7 @@ export class NodeDiffService { }); if (jsonResponse) { - this.exportDiffAsJson(nodeDiff); + await this.exportDiffAsJson(nodeDiff); } else { this.logDiff(nodeDiff); } @@ -49,16 +52,16 @@ export class NodeDiffService { }); if (jsonResponse) { - this.exportDiffAsJson(nodeDiff); + await this.exportDiffAsJson(nodeDiff); } else { this.logDiff(nodeDiff); } } - private exportDiffAsJson(nodeDiff: NodeConfigurationDiffTransport): void { + private async exportDiffAsJson(nodeDiff: NodeConfigurationDiffTransport): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(nodeDiff, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(nodeDiff, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } private logDiff(nodeDiff: NodeConfigurationDiffTransport): void { diff --git a/src/commands/configuration-management/single-package-import.service.ts b/src/commands/configuration-management/single-package-import.service.ts index fe32151e..78eaa2ad 100644 --- a/src/commands/configuration-management/single-package-import.service.ts +++ b/src/commands/configuration-management/single-package-import.service.ts @@ -5,6 +5,7 @@ import * as AdmZip from "adm-zip"; import * as fs from "node:fs"; import { Context } from "../../core/command/cli-context"; import { fileService, FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { GitService } from "../../core/git-profile/git/git.service"; import { SinglePackageImportApi } from "./api/single-package-import-api"; @@ -16,10 +17,12 @@ export class SinglePackageImportService { private readonly singlePackageImportApi: SinglePackageImportApi; private readonly gitService: GitService; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.singlePackageImportApi = new SinglePackageImportApi(context); this.gitService = new GitService(context); + this.cuiFileService = new CuiFileService(context); } public async importPackage(file: string, directory: string, overwrite: boolean, jsonResponse: boolean, gitBranch: string): Promise { @@ -44,7 +47,7 @@ export class SinglePackageImportService { const packageZip = new AdmZip(resolvedSource.zipPath); const formData = SinglePackageImportService.buildBodyForImport(packageZip, resolvedSource.zipPath); const result = await this.singlePackageImportApi.importPackage(formData, overwrite); - this.outputResult(result, jsonResponse); + await this.outputResult(result, jsonResponse); } finally { if (resolvedSource.isTemporary) { fs.rmSync(resolvedSource.zipPath); @@ -99,11 +102,11 @@ export class SinglePackageImportService { }); } - private outputResult(result: SinglePackageImportResult, jsonResponse: boolean): void { + private async outputResult(result: SinglePackageImportResult, jsonResponse: boolean): Promise { if (jsonResponse) { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(result, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(result, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); return; } diff --git a/src/commands/configuration-management/staging-package.service.ts b/src/commands/configuration-management/staging-package.service.ts index 16c60ebf..ee2bf686 100644 --- a/src/commands/configuration-management/staging-package.service.ts +++ b/src/commands/configuration-management/staging-package.service.ts @@ -1,22 +1,25 @@ import { v4 as uuidv4 } from "uuid"; import { Context } from "../../core/command/cli-context"; import { PackageExportTransport } from "./interfaces/package-export.interfaces"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { StagingPackageApi } from "./api/staging-package-api"; export class StagingPackageService { private stagingPackageApi: StagingPackageApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.stagingPackageApi = new StagingPackageApi(context); + this.cuiFileService = new CuiFileService(context); } public async listStagingPackages(flavors: string[], includeBranches: boolean, jsonResponse: boolean): Promise { const stagingPackages = await this.stagingPackageApi.findAllStagingPackages(flavors, includeBranches); if (jsonResponse) { - this.exportListOfPackages(stagingPackages); + await this.exportListOfPackages(stagingPackages); } else { stagingPackages.forEach(pkg => { logger.info(`${pkg.name} - Key: "${pkg.key}"`); @@ -24,9 +27,9 @@ export class StagingPackageService { } } - private exportListOfPackages(packages: PackageExportTransport[]): void { + private async exportListOfPackages(packages: PackageExportTransport[]): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(packages), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(packages), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } } diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index 462afcb8..45264999 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -3,6 +3,7 @@ import { Context } from "../../core/command/cli-context"; import { FatalError, logger } from "../../core/utils/logger"; import { fixConnectionVariables } from "./connection-variable.helper"; import { FileService, fileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { PackageKeyAndVersionPair, StagingVariableManifestTransport, VariableManifestTransport } from "./interfaces/package-export.interfaces"; import { VariableApi } from "./api/variable-api"; import { URLSearchParams } from "url"; @@ -14,11 +15,13 @@ export class VariableService { private variableApi: VariableApi; private variableAssignmentCandidatesApi: VariableAssignmentCandidatesApi; private readonly stagingPackageVariablesApi: StagingPackageVariablesApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.variableApi = new VariableApi(context); this.variableAssignmentCandidatesApi = new VariableAssignmentCandidatesApi(context); this.stagingPackageVariablesApi = new StagingPackageVariablesApi(context); + this.cuiFileService = new CuiFileService(context); } public async listVariables(keysByVersion: string[], keysByVersionFile: string): Promise { @@ -42,13 +45,13 @@ export class VariableService { const parsedParams = this.parseParams(params); const assignments = await this.variableAssignmentCandidatesApi.getCandidateAssignments(type, parsedParams); - this.exportToJson(assignments) + await this.exportToJson(assignments); } public async exportVariables(keysByVersion: string[], keysByVersionFile: string): Promise { const variableManifests = await this.getVersionedVariablesByKeyVersionPairs(keysByVersion, keysByVersionFile); - this.exportToJson(variableManifests); + await this.exportToJson(variableManifests); } public async listStagingVariables(packageKeys: string[]): Promise { @@ -60,7 +63,7 @@ export class VariableService { public async exportStagingVariables(packageKeys: string[]): Promise { const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys); - this.exportToJson(byPackage); + await this.exportToJson(byPackage); } private async fetchStagingVariablesByPackageKeys( @@ -100,10 +103,10 @@ export class VariableService { }); } - private exportToJson(data: any): void { + private async exportToJson(data: any): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(data), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(data), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } private parseParams(params?: string): URLSearchParams { diff --git a/src/commands/data-pipeline/data-pool/data-pool-service.ts b/src/commands/data-pipeline/data-pool/data-pool-service.ts index 38caf886..a70c6404 100644 --- a/src/commands/data-pipeline/data-pool/data-pool-service.ts +++ b/src/commands/data-pipeline/data-pool/data-pool-service.ts @@ -1,5 +1,6 @@ import { v4 as uuidv4 } from "uuid"; import { FileService, fileService } from "../../../core/utils/file-service"; +import { CuiFileService } from "../../../core/utils/cui-file-service"; import { logger } from "../../../core/utils/logger"; import { DataPoolSlimTransport } from "./data-pool-manager.interfaces"; import { Context } from "../../../core/command/cli-context"; @@ -8,9 +9,11 @@ import { DataPoolApi } from "./data-pool-api"; export class DataPoolService { private dataPoolApi: DataPoolApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.dataPoolApi = new DataPoolApi(context); + this.cuiFileService = new CuiFileService(context); } public async batchImportDataPools(requestFilePath: string, outputToJsonFile: boolean): Promise { @@ -49,7 +52,7 @@ export class DataPoolService { public async findAndExportAllPools(): Promise { const dataPools = await this.findAllPools(); - this.exportListOfPools(dataPools); + await this.exportListOfPools(dataPools); } private async findAllPools(): Promise { @@ -63,9 +66,9 @@ export class DataPoolService { return dataPools; } - private exportListOfPools(nodes: DataPoolSlimTransport[]): void { + private async exportListOfPools(nodes: DataPoolSlimTransport[]): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(nodes), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(nodes), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } } \ No newline at end of file diff --git a/src/commands/t2tc/diff.service.ts b/src/commands/t2tc/diff.service.ts index c07f03ba..a66ee068 100644 --- a/src/commands/t2tc/diff.service.ts +++ b/src/commands/t2tc/diff.service.ts @@ -3,7 +3,8 @@ import {Readable} from "stream"; import * as FormData from "form-data"; import {v4 as uuidv4} from "uuid"; import { logger } from "../../core/utils/logger"; -import { fileService, FileService } from "../../core/utils/file-service"; +import { FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { Context } from "../../core/command/cli-context"; import { PackageDiffMetadata, PackageDiffTransport } from "../configuration-management/interfaces/diff-package.interfaces"; import { DiffApi } from "./api/diff-api"; @@ -11,9 +12,11 @@ import { DiffApi } from "./api/diff-api"; export class DiffService { private diffApi: DiffApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.diffApi = new DiffApi(context); + this.cuiFileService = new CuiFileService(context); } public async diffPackages(file: string, hasChanges: boolean, baseVersion: string, jsonResponse: boolean): Promise { @@ -30,7 +33,7 @@ export class DiffService { const returnedHasChangesData = await this.diffApi.hasChanges(baseVersion, formData); if (jsonResponse) { - this.exportListOfPackageDiffMetadata(returnedHasChangesData); + await this.exportListOfPackageDiffMetadata(returnedHasChangesData); } else { logger.info(this.buildStringResponseForPackageDiffMetadataList(returnedHasChangesData)); } @@ -42,7 +45,7 @@ export class DiffService { const returnedHasChangesData = await this.diffApi.diffPackages(baseVersion, formData); if (jsonResponse) { - this.exportListOfPackageDiffs(returnedHasChangesData); + await this.exportListOfPackageDiffs(returnedHasChangesData); } else { logger.info(this.buildStringResponseForPackageDiffs(returnedHasChangesData)); } @@ -66,16 +69,16 @@ export class DiffService { }); } - private exportListOfPackageDiffs(packageDiffs: PackageDiffTransport[]): void { + private async exportListOfPackageDiffs(packageDiffs: PackageDiffTransport[]): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(packageDiffs), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(packageDiffs), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } - private exportListOfPackageDiffMetadata(packageDiffMetadata: PackageDiffMetadata[]): void { + private async exportListOfPackageDiffMetadata(packageDiffMetadata: PackageDiffMetadata[]): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(packageDiffMetadata), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(packageDiffMetadata), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } private buildStringResponseForPackageDiffs(packageDiffs: PackageDiffTransport[]): string { diff --git a/src/commands/t2tc/t2tc-package.service.ts b/src/commands/t2tc/t2tc-package.service.ts index 9b7d41ef..32de23fe 100644 --- a/src/commands/t2tc/t2tc-package.service.ts +++ b/src/commands/t2tc/t2tc-package.service.ts @@ -10,6 +10,7 @@ import { } from "../configuration-management/interfaces/package-export.interfaces"; import { BatchExportImportConstants } from "./batch-export-import.constants"; import { fileService, FileService } from "../../core/utils/file-service"; +import { CuiFileService } from "../../core/utils/cui-file-service"; import { logger } from "../../core/utils/logger"; import { parse, stringify } from "../../core/utils/json"; import { PackageApi } from "../studio/api/package-api"; @@ -30,6 +31,7 @@ export class T2tcPackageService { private studioPackageApi: PackageApi; private studioService: StudioService; private gitService: GitService; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.t2tcPackageApi = new T2tcPackageApi(context); @@ -38,6 +40,7 @@ export class T2tcPackageService { this.studioPackageApi = new PackageApi(context); this.studioService = new StudioService(context); this.gitService = new GitService(context); + this.cuiFileService = new CuiFileService(context); } public async listActivePackages(flavors: string[], includeBranches: boolean): Promise { @@ -60,7 +63,7 @@ export class T2tcPackageService { packagesToExport = await this.studioService.getExportPackagesWithStudioData(packagesToExport, withDependencies); - this.exportListOfPackages(packagesToExport); + await this.exportListOfPackages(packagesToExport); } public async listPackagesByKeysWithVersion(keysByVersion: string[], withDependencies: boolean): Promise { @@ -157,7 +160,7 @@ export class T2tcPackageService { packagesToExport = await this.studioService.getExportPackagesWithStudioData(packagesToExport, false); - this.exportListOfPackages(packagesToExport); + await this.exportListOfPackages(packagesToExport); } public async listActivePackagesByVariableValue(flavors: string[], variableValue: string, variableType: string, includeBranches: boolean) : Promise { @@ -167,10 +170,10 @@ export class T2tcPackageService { }); } - private exportListOfPackages(packages: PackageExportTransport[]): void { + private async exportListOfPackages(packages: PackageExportTransport[]): Promise { const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(packages), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(packages), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } private getVersionsByPackageKey(manifests: PackageManifestTransport[]): Map { diff --git a/tests/commands/configuration-management/list-assignments.spec.ts b/tests/commands/configuration-management/list-assignments.spec.ts index 26581dae..ff086679 100644 --- a/tests/commands/configuration-management/list-assignments.spec.ts +++ b/tests/commands/configuration-management/list-assignments.spec.ts @@ -1,4 +1,4 @@ -import { mockedAxiosInstance } from "../../utls/http-requests-mock"; +import { mockAxiosGet, mockedAxiosInstance } from "../../utls/http-requests-mock"; import { VariableCommandService } from "../../../src/commands/configuration-management/variable-command.service"; import { testContext } from "../../utls/test-context"; import { loggingTestTransport } from "../../jest.setup"; @@ -7,13 +7,15 @@ import { getJsonFromDownloadedFile } from "../../utls/fs-utils"; describe("List assignments", () => { + const DATA_MODEL_URL = "https://myTeam.celonis.cloud/package-manager/api/compute-pools/pools-with-data-models"; + const CONNECTIONS_URL = "https://myTeam.celonis.cloud/process-automation-v2/api/connections?param1=value1¶m2=value2"; + it("Should list assignments for supported type and non-json response", async () => { const mockAssignmentValues = [ {id: "id-1"}, {id: "id-2"} ]; - const resp = {data: mockAssignmentValues}; - (mockedAxiosInstance.get as jest.Mock).mockResolvedValue(resp); + mockAxiosGet(DATA_MODEL_URL, mockAssignmentValues); await new VariableCommandService(testContext).listAssignments("DATA_MODEL", false, ""); @@ -21,7 +23,7 @@ describe("List assignments", () => { expect(loggingTestTransport.logMessages[0].message).toContain('{"id":"id-1"}'); expect(loggingTestTransport.logMessages[1].message).toContain('{"id":"id-2"}'); - expect(mockedAxiosInstance.get).toHaveBeenCalledWith("https://myTeam.celonis.cloud/package-manager/api/compute-pools/pools-with-data-models", expect.anything()) + expect(mockedAxiosInstance.get).toHaveBeenCalledWith(DATA_MODEL_URL, expect.anything()) }) it("Should export assignments for supported type and json response", async () => { @@ -29,8 +31,7 @@ describe("List assignments", () => { {id: "id-1"}, {id: "id-2"} ]; - const resp = {data: mockAssignmentValues}; - (mockedAxiosInstance.get as jest.Mock).mockResolvedValue(resp); + mockAxiosGet(DATA_MODEL_URL, mockAssignmentValues); await new VariableCommandService(testContext).listAssignments("DATA_MODEL", true, ""); @@ -42,12 +43,11 @@ describe("List assignments", () => { it("Should contain url params in the url", async () => { const mockAssignmentValues = [{id: "id-1"}]; - const resp = {data: mockAssignmentValues}; - (mockedAxiosInstance.get as jest.Mock).mockResolvedValue(resp); + mockAxiosGet(CONNECTIONS_URL, mockAssignmentValues); await new VariableCommandService(testContext).listAssignments("CONNECTION", false, "param1=value1,param2=value2"); - expect(mockedAxiosInstance.get).toHaveBeenCalledWith("https://myTeam.celonis.cloud/process-automation-v2/api/connections?param1=value1¶m2=value2", expect.anything()) + expect(mockedAxiosInstance.get).toHaveBeenCalledWith(CONNECTIONS_URL, expect.anything()) }) it("Should throw error for unsupported variable types", async () => { From 8145f4a83e359fdc4100e8c95b4a8e5834486f8f Mon Sep 17 00:00:00 2001 From: denniswo Date: Mon, 10 Aug 2026 10:19:10 +0200 Subject: [PATCH 3/4] SP-1173: turn the branch writeJson helpers into async instance methods Both were private static, so they could not reach an instance field to get at the CuiFileService. Nine call sites move from the static form to await this.writeJson(...). Includes-AI-Code: true Co-authored-by: Cursor --- .../branch-export-import.command.service.ts | 17 ++++++++++------- .../branch/branch.command.service.ts | 19 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/commands/configuration-management/branch/branch-export-import.command.service.ts b/src/commands/configuration-management/branch/branch-export-import.command.service.ts index e70e4c37..1f3c765d 100644 --- a/src/commands/configuration-management/branch/branch-export-import.command.service.ts +++ b/src/commands/configuration-management/branch/branch-export-import.command.service.ts @@ -6,6 +6,7 @@ import AdmZip = require("adm-zip"); import { resolve } from "node:path"; import { Context } from "../../../core/command/cli-context"; import { fileService, FileService } from "../../../core/utils/file-service"; +import { CuiFileService } from "../../../core/utils/cui-file-service"; import { FatalError, logger } from "../../../core/utils/logger"; import { GitService } from "../../../core/git-profile/git/git.service"; import { SinglePackageExportApi } from "../api/single-package-export-api"; @@ -37,12 +38,14 @@ export class BranchExportImportCommandService { private readonly singlePackageImportApi: SinglePackageImportApi; private readonly branchApi: BranchApi; private readonly gitService: GitService; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.singlePackageExportApi = new SinglePackageExportApi(context); this.singlePackageImportApi = new SinglePackageImportApi(context); this.branchApi = new BranchApi(context); this.gitService = new GitService(context); + this.cuiFileService = new CuiFileService(context); } public async exportBranch(packageKey: string, branchKey: string, options: BranchExportOptions = {}): Promise { @@ -51,7 +54,7 @@ export class BranchExportImportCommandService { if (options.gitEnabled) { const pushedKey = await this.pushBranchToGit(packageKey, branchKey); if (jsonResponse) { - BranchExportImportCommandService.writeJson({ packageKey: pushedKey, branchName: branchKey }); + await this.writeJson({ packageKey: pushedKey, branchName: branchKey }); } else { logger.info(`Exported ${pushedKey} to Git branch '${branchKey}'.`); } @@ -63,7 +66,7 @@ export class BranchExportImportCommandService { try { const message = this.writeLocalArtifact(sourceDir, packageKey, !!options.zip); if (jsonResponse) { - BranchExportImportCommandService.writeJson({ packageKey: branchPackageKey, branchName: branchKey }); + await this.writeJson({ packageKey: branchPackageKey, branchName: branchKey }); } else { logger.info(message); } @@ -93,7 +96,7 @@ export class BranchExportImportCommandService { const summary: BranchSyncSummary = { packageKey: mainKey, branchName: BranchUtils.MAIN_BRANCH_KEY, synced }; if (jsonResponse) { - BranchExportImportCommandService.writeJson(summary); + await this.writeJson(summary); } else { logger.info(`Exported Git mirror for ${mainKey}: ${synced.length} package(s) pushed.`); } @@ -110,7 +113,7 @@ export class BranchExportImportCommandService { await this.importPackageSourceDir(workingDir, !!options.overwrite); if (options.jsonResponse) { - BranchExportImportCommandService.writeJson({ packageKey: branchPackageKey, branchName: branchKey }); + await this.writeJson({ packageKey: branchPackageKey, branchName: branchKey }); } else { const origin = options.gitEnabled ? `Git branch '${branchKey}'` : (options.file ?? options.directory); logger.info(`Imported ${origin} into ${branchPackageKey}.`); @@ -270,9 +273,9 @@ export class BranchExportImportCommandService { && branch.branchKey?.toLowerCase() !== BranchUtils.MAIN_BRANCH_KEY; } - private static writeJson(payload: unknown): void { + private async writeJson(payload: unknown): Promise { const filename = `${uuidv4()}.json`; - fileService.writeToFileWithGivenName(JSON.stringify(payload, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(payload, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } } diff --git a/src/commands/configuration-management/branch/branch.command.service.ts b/src/commands/configuration-management/branch/branch.command.service.ts index 2a11aa55..a57232b4 100644 --- a/src/commands/configuration-management/branch/branch.command.service.ts +++ b/src/commands/configuration-management/branch/branch.command.service.ts @@ -1,6 +1,7 @@ import { v4 as uuidv4 } from "uuid"; import { Context } from "../../../core/command/cli-context"; import { fileService, FileService } from "../../../core/utils/file-service"; +import { CuiFileService } from "../../../core/utils/cui-file-service"; import { logger } from "../../../core/utils/logger"; import { BranchApi } from "./api/branch.api"; import { @@ -20,9 +21,11 @@ import { export class BranchCommandService { private readonly branchApi: BranchApi; + private readonly cuiFileService: CuiFileService; constructor(context: Context) { this.branchApi = new BranchApi(context); + this.cuiFileService = new CuiFileService(context); } public async setBranchingEnabled(packageKey: string, enabled: boolean, jsonResponse: boolean): Promise { @@ -30,7 +33,7 @@ export class BranchCommandService { const result = await this.branchApi.configureBranchingSettings(packageKey, transport); if (jsonResponse) { - BranchCommandService.writeJson(result); + await this.writeJson(result); } else { logger.info(`Branching ${result.branchingEnabled ? "enabled" : "disabled"} for package ${packageKey}.`); } @@ -47,7 +50,7 @@ export class BranchCommandService { } if (jsonResponse) { - BranchCommandService.writeJson(result); + await this.writeJson(result); } else if (result) { BranchCommandService.printBranch(result); } @@ -58,7 +61,7 @@ export class BranchCommandService { const branches = await this.branchApi.listBranches(packageKey); if (jsonResponse) { - BranchCommandService.writeJson(branches); + await this.writeJson(branches); } else if (branches.length === 0) { logger.info(`No branches found for ${packageKey}.`); } else { @@ -82,7 +85,7 @@ export class BranchCommandService { const preview = await this.branchApi.mergePreview(targetPackageKey, transport); if (jsonResponse) { - BranchCommandService.writeJson(preview); + await this.writeJson(preview); } else { BranchCommandService.printPreviewSummary(targetPackageKey, sourceKey, sourceVersion, preview); } @@ -115,7 +118,7 @@ export class BranchCommandService { const result = await this.branchApi.merge(targetPackageKey, transport); if (options.jsonResponse) { - BranchCommandService.writeJson(result); + await this.writeJson(result); } else { logger.info( `Merge applied: published ${result.packageKey}@${result.version} from ${effectiveSourceKey}@${effectiveSourceVersion}.` @@ -153,10 +156,10 @@ export class BranchCommandService { return versionCreate; } - private static writeJson(payload: unknown): void { + private async writeJson(payload: unknown): Promise { const filename = `${uuidv4()}.json`; - fileService.writeToFileWithGivenName(JSON.stringify(payload, null, 2), filename); - logger.info(FileService.fileDownloadedMessage + filename); + const writtenFilename = await this.cuiFileService.writeToFileWithGivenName(JSON.stringify(payload, null, 2), filename); + logger.info(FileService.fileDownloadedMessage + writtenFilename); } private static printBranch(branch: BranchTransport): void { From 9e2a096617ccac9144b59146a777a795f544e576 Mon Sep 17 00:00:00 2001 From: denniswo Date: Mon, 10 Aug 2026 10:21:57 +0200 Subject: [PATCH 4/4] SP-1173: cover CUI marking of --json commands per service family One classified-outcome test per family, asserting the command produces "CUI - .zip" with the cover sheet inside and logs that name. The marking logic itself stays covered by the CuiFileService unit tests. Includes-AI-Code: true Co-authored-by: Cursor --- .../cui-marking-json-commands.spec.ts | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tests/commands/cui-marking-json-commands.spec.ts diff --git a/tests/commands/cui-marking-json-commands.spec.ts b/tests/commands/cui-marking-json-commands.spec.ts new file mode 100644 index 00000000..b4f28d6e --- /dev/null +++ b/tests/commands/cui-marking-json-commands.spec.ts @@ -0,0 +1,152 @@ +import { resolve } from "node:path"; +import { readFileSync } from "node:fs"; +import AdmZip = require("adm-zip"); +import { mockAxiosGet, mockAxiosGetWithStatus, mockAxiosPost } from "../utls/http-requests-mock"; +import { testContext } from "../utls/test-context"; +import { loggingTestTransport } from "../jest.setup"; +import { FileService } from "../../src/core/utils/file-service"; +import { CuiFileService } from "../../src/core/utils/cui-file-service"; +import { ConfigUtils } from "../utls/config-utils"; +import { zipToTempFolder } from "../utls/fs-utils"; +import { DeploymentService } from "../../src/commands/deployment/deployment.service"; +import { NodeService } from "../../src/commands/configuration-management/node.service"; +import { BranchCommandService } from "../../src/commands/configuration-management/branch/branch.command.service"; +import { ConfigCommandService } from "../../src/commands/configuration-management/config-command.service"; +import { AssetRegistryService } from "../../src/commands/asset-registry/asset-registry.service"; +import { T2tcCommandService } from "../../src/commands/t2tc/t2tc-command.service"; +import { DataPoolService } from "../../src/commands/data-pipeline/data-pool/data-pool-service"; +import { MetadataService } from "../../src/commands/configuration-management/metadata.service"; +import { PackageValidationService } from "../../src/commands/configuration-management/package-validation.service"; +import { PackageManifestTransport } from "../../src/commands/configuration-management/interfaces/package-export.interfaces"; + +const COVER_URL = "https://myTeam.celonis.cloud/api/team/cui-settings/cui-pdf-cover"; +const PDF_BYTES = Buffer.from("%PDF-1.4 cover sheet"); + +function markAsClassified(): void { + mockAxiosGetWithStatus(COVER_URL, 200, { + resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, + coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64" }, + }); +} + +function loggedFileName(prefix: string = FileService.fileDownloadedMessage): string { + const message = loggingTestTransport.logMessages.map(entry => entry.message).find(entry => entry.includes(prefix)); + return message.split(prefix)[1]; +} + +function payloadFromArchive(filename: string): any { + expect(filename.startsWith(CuiFileService.CLASSIFIED_PREFIX)).toBe(true); + expect(filename.endsWith(".zip")).toBe(true); + + const archive = new AdmZip(readFileSync(resolve(process.cwd(), filename))); + expect(archive.getEntry(CuiFileService.COVER_SHEET_FILE_NAME).getData().equals(PDF_BYTES)).toBe(true); + + const payloadEntry = archive.getEntries().map(entry => entry.entryName).find(entry => entry.endsWith(".json")); + return JSON.parse(archive.getEntry(payloadEntry).getData().toString()); +} + +function markedPayload(prefix?: string): any { + return payloadFromArchive(loggedFileName(prefix)); +} + +describe("CUI marking of --json commands", () => { + + beforeEach(() => { + markAsClassified(); + }); + + it("Should mark deployment listings", async () => { + const targets = [{ id: "target-1", name: "First target" }]; + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/deployments/targets?deployableType=app-package&packageKey=package-key", targets); + + await new DeploymentService(testContext).getTargets(true, "app-package", "package-key"); + + expect(markedPayload()).toEqual(targets); + }); + + it("Should mark configuration node listings", async () => { + const nodes = [{ id: "node-id-1", key: "node-key-1", name: "Node 1" }]; + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/packages/package-key/nodes?version=1.0.0&withConfiguration=false&limit=10", nodes); + + await new NodeService(testContext).listNodes("package-key", "1.0.0", 10, 0, false, true); + + expect(markedPayload()).toEqual(nodes); + }); + + it("Should mark branch listings", async () => { + const branches = [{ projectKey: "my-package", branchKey: "feature-a", packageKey: "my-package@feature-a" }]; + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/packages/my-package/branches", branches); + + await new BranchCommandService(testContext).listBranches("my-package", true); + + expect(markedPayload()).toEqual(branches); + }); + + it("Should mark staging variable exports", async () => { + const manifests = [{ packageKey: "pkg-a", variables: [{ key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id", metadata: {} }] }]; + mockAxiosPost("https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys", manifests); + + await new ConfigCommandService(testContext).listVariables(true, [], "", ["pkg-a"]); + + expect(markedPayload()).toEqual(manifests); + }); + + it("Should mark asset registry responses", async () => { + const schema = { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object" }; + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/schemas/BOARD_V2", schema); + + await new AssetRegistryService(testContext).getSchema("BOARD_V2", true); + + expect(markedPayload()).toEqual(schema); + }); + + it("Should mark t2tc package listings", async () => { + const packages = [{ key: "key-1", name: "Package 1", flavor: "STUDIO" }]; + const urlParams = new URLSearchParams({ includeBranches: "false" }); + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/staging/packages/export/list?" + urlParams.toString(), packages); + + await new T2tcCommandService(testContext).listPackages(true, null, false, [], undefined, null, null, false, true); + + expect(markedPayload()).toEqual(packages); + }); + + it("Should mark t2tc package diffs", async () => { + const diff = [{ packageKey: "package-key", hasChanges: true }]; + mockAxiosPost("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration/has-changes", diff); + + const manifest: PackageManifestTransport[] = [ConfigUtils.buildManifestForKeyAndFlavor("package-key", "STUDIO")]; + const source = zipToTempFolder(ConfigUtils.buildBatchExportZip(manifest, [])); + + await new T2tcCommandService(testContext).diffPackages(source, true, null, true); + + expect(markedPayload()).toEqual(diff); + }); + + it("Should mark data pool listings", async () => { + const pool = { id: "pool-1", name: "Pool 1" }; + mockAxiosGet("https://myTeam.celonis.cloud/integration/api/pools/paged?limit=100&page=0", { pageNumber: 0, totalCount: 1, content: [pool] }); + mockAxiosGet("https://myTeam.celonis.cloud/integration/api/pools/paged?limit=100&page=1", { pageNumber: 1, totalCount: 1, content: [] }); + + await new DataPoolService(testContext).findAndExportAllPools(); + + expect(markedPayload()).toEqual([pool]); + }); + + it("Should mark package metadata exports", async () => { + const metadata = [{ key: "package-key-1", hasUnpublishedChanges: true }]; + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/metadata/export?packageKeys=package-key-1", metadata); + + await new MetadataService(testContext).exportPackagesMetadata(["package-key-1"], true); + + expect(markedPayload()).toEqual(metadata); + }); + + it("Should mark package validation reports", async () => { + const report = { packageKey: "my-package", valid: true, summary: { errors: 0, warnings: 0, info: 0 }, results: [] }; + mockAxiosPost("https://myTeam.celonis.cloud/pacman/api/core/packages/my-package/validate", report); + + await new PackageValidationService(testContext).validatePackage("my-package", ["SCHEMA"], null, true); + + expect(markedPayload("Validation report file: ")).toEqual(report); + }); +});