From f3e04408139f659bb629f7a06c86c26d5ec24f80 Mon Sep 17 00:00:00 2001 From: denniswo Date: Wed, 12 Aug 2026 14:14:07 +0200 Subject: [PATCH 1/4] SP-1173: decide CUI marking from the cover response status alone The cover response categories no longer take part in the decision: 403 leaves the artifact untouched, 204 marks it Unclassified, and 200 marks it CUI and attaches the cover sheet. Anything else fails the command without writing output. Includes-AI-Code: true Co-authored-by: Cursor --- docs/cui-marking.md | 9 ++++----- src/core/utils/cui-api.ts | 22 ++++++++++++++++------ src/core/utils/cui-file-service.ts | 14 +++++--------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/cui-marking.md b/docs/cui-marking.md index 5c71903b..0baf1dc4 100644 --- a/docs/cui-marking.md +++ b/docs/cui-marking.md @@ -11,12 +11,11 @@ Example: `list packages --json` would otherwise write `packages.json`. | Cover response | Meaning | Outcome | |---|---|---| | **403** | Feature flag disabled | `packages.json` | -| **204** | Team has CUI disabled | `packages.json` | -| **200**, no categories | Marking applies, unclassified | `Unclassified - packages.json` | -| **200**, with categories | Marking applies, classified | `CUI - packages.zip` containing `packages.json` and `CUI_Cover_Sheet.pdf` | -| Unexpected response | Fail closed | Nothing written; the command errors | +| **204** | Unclassified | `Unclassified - packages.json` | +| **200** | Classified | `CUI - packages.zip` containing `packages.json` and `CUI_Cover_Sheet.pdf` | +| Any other response | Fail closed | Nothing written; the command errors | -**403** and **204** both leave the artifact unmarked. They are not the same as **200 with no categories**, which still renames it to `Unclassified - …`. +The status code alone decides the outcome. Any failure of the cover call, including an unexpected status or a **200** without a usable cover page, aborts the command and leaves no output behind. ## Scope: how the write is triggered diff --git a/src/core/utils/cui-api.ts b/src/core/utils/cui-api.ts index e17254e1..ababc38c 100644 --- a/src/core/utils/cui-api.ts +++ b/src/core/utils/cui-api.ts @@ -3,10 +3,20 @@ import { FatalError, logger } from "./logger"; import { Context } from "../command/cli-context"; export interface CuiPdfCoverResponse { - resolvedCuiMarking?: { categories?: unknown[] }; coverPage?: { pdfContent: string; encoding: string }; } +export enum CuiMarking { + DISABLED = "DISABLED", + UNCLASSIFIED = "UNCLASSIFIED", + CLASSIFIED = "CLASSIFIED", +} + +export type CuiMarkingDecision = + | { marking: CuiMarking.DISABLED } + | { marking: CuiMarking.UNCLASSIFIED } + | { marking: CuiMarking.CLASSIFIED; cover: CuiPdfCoverResponse }; + export class CuiApi { private static readonly CUI_PDF_COVER_SHEET_URL = "/api/team/cui-settings/cui-pdf-cover"; @@ -20,21 +30,21 @@ export class CuiApi { this.httpClient = () => context.httpClient; } - public async getCuiPdfCover(): Promise { + public async getCuiMarking(): Promise { const { status, data } = await this.httpClient().getStatusAndData(CuiApi.CUI_PDF_COVER_SHEET_URL); if (status === CuiApi.STATUS_FORBIDDEN) { logger.debug("CUI marking does not apply, the feature flag is disabled"); - return null; + return { marking: CuiMarking.DISABLED }; } if (status === CuiApi.STATUS_NO_CONTENT) { - logger.debug("CUI marking does not apply, the team has CUI disabled"); - return null; + logger.debug("CUI marking applies, the content is unclassified"); + return { marking: CuiMarking.UNCLASSIFIED }; } if (status === CuiApi.STATUS_OK && data) { - return data as CuiPdfCoverResponse; + return { marking: CuiMarking.CLASSIFIED, cover: data as CuiPdfCoverResponse }; } throw new FatalError("Problem fetching cui pdf cover"); diff --git a/src/core/utils/cui-file-service.ts b/src/core/utils/cui-file-service.ts index 7a9b01f1..19cf5338 100644 --- a/src/core/utils/cui-file-service.ts +++ b/src/core/utils/cui-file-service.ts @@ -1,7 +1,7 @@ import * as path from "node:path"; import AdmZip = require("adm-zip"); import { Context } from "../command/cli-context"; -import { CuiApi, CuiPdfCoverResponse } from "./cui-api"; +import { CuiApi, CuiMarking, CuiPdfCoverResponse } from "./cui-api"; import { fileService } from "./file-service"; import { FileConstants } from "./file.constants"; import { FatalError } from "./logger"; @@ -60,21 +60,21 @@ export class CuiFileService { filename: string, onClassified: (cover: CuiPdfCoverResponse) => string ): Promise { - const cover = await this.cuiApi.getCuiPdfCover(); + const decision = await this.cuiApi.getCuiMarking(); - if (!cover) { + if (decision.marking === CuiMarking.DISABLED) { write(filename); return filename; } - if (!this.isClassified(cover)) { + if (decision.marking === CuiMarking.UNCLASSIFIED) { const unclassifiedName = this.prefixFileName(filename, CuiFileService.UNCLASSIFIED_PREFIX); write(unclassifiedName); return unclassifiedName; } - return onClassified(cover); + return onClassified(decision.cover); } private writeClassifiedArchive(filename: string, data: string, cover: CuiPdfCoverResponse): string { @@ -113,10 +113,6 @@ export class CuiFileService { return Buffer.from(coverPage.pdfContent, CuiFileService.BASE64_ENCODING); } - private isClassified(cover: CuiPdfCoverResponse): boolean { - return (cover.resolvedCuiMarking?.categories?.length ?? 0) > 0; - } - private buildClassifiedArchiveName(filename: string): string { const baseName = path.basename(filename); const nameWithoutExtension = baseName.slice(0, baseName.length - path.extname(baseName).length); From 7e4ff79d9c1fce146c9e5f8da842bfd9793bdbff Mon Sep 17 00:00:00 2001 From: denniswo Date: Wed, 12 Aug 2026 14:14:21 +0200 Subject: [PATCH 2/4] SP-1173: cover the status-driven CUI marking outcomes Unclassified is now reached through 204 and classified through 200, so the specs drop the category fixtures. The shared mock answers 403 so unrelated tests keep their original filenames. Includes-AI-Code: true Co-authored-by: Cursor --- .../cui-marking-directory-exports.spec.ts | 1 - .../cui-marking-json-commands.spec.ts | 1 - .../cui-marking-output-to-json-file.spec.ts | 1 - .../cui-marking-single-file-exports.spec.ts | 1 - .../commands/cui-marking-zip-commands.spec.ts | 1 - .../commands/studio/list-cui-marking.spec.ts | 31 +++++++++++--- tests/core/utils/cui-file-service.spec.ts | 42 ++++++------------- tests/utls/http-requests-mock.ts | 4 +- 8 files changed, 40 insertions(+), 42 deletions(-) diff --git a/tests/commands/cui-marking-directory-exports.spec.ts b/tests/commands/cui-marking-directory-exports.spec.ts index abedef92..6f0822d4 100644 --- a/tests/commands/cui-marking-directory-exports.spec.ts +++ b/tests/commands/cui-marking-directory-exports.spec.ts @@ -23,7 +23,6 @@ const BRANCH = "feature-a"; function markAsClassified(): void { mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64" }, }); } diff --git a/tests/commands/cui-marking-json-commands.spec.ts b/tests/commands/cui-marking-json-commands.spec.ts index b4f28d6e..cccde411 100644 --- a/tests/commands/cui-marking-json-commands.spec.ts +++ b/tests/commands/cui-marking-json-commands.spec.ts @@ -24,7 +24,6 @@ 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" }, }); } diff --git a/tests/commands/cui-marking-output-to-json-file.spec.ts b/tests/commands/cui-marking-output-to-json-file.spec.ts index 0940a6fc..5a4ff707 100644 --- a/tests/commands/cui-marking-output-to-json-file.spec.ts +++ b/tests/commands/cui-marking-output-to-json-file.spec.ts @@ -21,7 +21,6 @@ const POOL_ID = "pool-1"; function markAsClassified(): void { mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64" }, }); } diff --git a/tests/commands/cui-marking-single-file-exports.spec.ts b/tests/commands/cui-marking-single-file-exports.spec.ts index 736138cf..e574f7dd 100644 --- a/tests/commands/cui-marking-single-file-exports.spec.ts +++ b/tests/commands/cui-marking-single-file-exports.spec.ts @@ -26,7 +26,6 @@ const PACKAGE_KEY = "my-package"; function markAsClassified(): void { mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64" }, }); } diff --git a/tests/commands/cui-marking-zip-commands.spec.ts b/tests/commands/cui-marking-zip-commands.spec.ts index bf66ad3f..840c5682 100644 --- a/tests/commands/cui-marking-zip-commands.spec.ts +++ b/tests/commands/cui-marking-zip-commands.spec.ts @@ -27,7 +27,6 @@ const T2TC_DOWNLOAD_MESSAGE = "File downloaded successfully. New filename: "; function markAsClassified(): void { mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64" }, }); } diff --git a/tests/commands/studio/list-cui-marking.spec.ts b/tests/commands/studio/list-cui-marking.spec.ts index f07dc53c..df7968c5 100644 --- a/tests/commands/studio/list-cui-marking.spec.ts +++ b/tests/commands/studio/list-cui-marking.spec.ts @@ -1,7 +1,7 @@ import { resolve } from "node:path"; import { readFileSync } from "node:fs"; import AdmZip = require("adm-zip"); -import { mockAxiosGet, mockAxiosGetWithStatus, mockedAxiosInstance } from "../../utls/http-requests-mock"; +import { mockAxiosGet, mockAxiosGetError, mockAxiosGetWithStatus, mockedAxiosInstance } from "../../utls/http-requests-mock"; import { SpaceCommandService } from "../../../src/commands/studio/command-service/space-command.service"; import { PackageCommandService } from "../../../src/commands/studio/command-service/package-command.service"; import { testContext } from "../../utls/test-context"; @@ -20,7 +20,6 @@ const PDF_BYTES = Buffer.from("%PDF-1.4 cover sheet"); function classifiedCover(): object { return { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64" }, }; } @@ -68,8 +67,8 @@ describe("CUI marking of Studio listings", () => { expect(payloadFromArchive(filename)).toEqual(SPACES); }); - it("Should keep the original filename when no marking applies", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); + it("Should keep the original filename when the feature flag is disabled", async () => { + mockAxiosGetError(COVER_URL, 403, { errorCode: "feature-disabled" }); await listSpaces(); @@ -79,6 +78,16 @@ describe("CUI marking of Studio listings", () => { expect(readWrittenJson(filename)).toEqual(SPACES); }); + it("Should only prefix the listing when the content is unclassified", async () => { + mockAxiosGetWithStatus(COVER_URL, 204, ""); + + await listSpaces(); + + const filename = loggedFileName(); + expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); + expect(readWrittenJson(filename)).toEqual(SPACES); + }); + it("Should not probe CUI when the listing only goes to the console", async () => { await new SpaceCommandService(testContext).listSpaces(false); @@ -105,8 +114,8 @@ describe("CUI marking of Studio listings", () => { expect(payloadFromArchive(filename)).toEqual(LISTED_PACKAGES); }); - it("Should keep the original filename when no marking applies", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); + it("Should keep the original filename when the feature flag is disabled", async () => { + mockAxiosGetError(COVER_URL, 403, { errorCode: "feature-disabled" }); await listPackages(); @@ -116,6 +125,16 @@ describe("CUI marking of Studio listings", () => { expect(readWrittenJson(filename)).toEqual(LISTED_PACKAGES); }); + it("Should only prefix the listing when the content is unclassified", async () => { + mockAxiosGetWithStatus(COVER_URL, 204, ""); + + await listPackages(); + + const filename = loggedFileName(); + expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); + expect(readWrittenJson(filename)).toEqual(LISTED_PACKAGES); + }); + it("Should not probe CUI when the listing only goes to the console", async () => { await new PackageCommandService(testContext).listPackages(false, false, []); diff --git a/tests/core/utils/cui-file-service.spec.ts b/tests/core/utils/cui-file-service.spec.ts index f05250c4..2cd69f32 100644 --- a/tests/core/utils/cui-file-service.spec.ts +++ b/tests/core/utils/cui-file-service.spec.ts @@ -13,8 +13,7 @@ describe("CuiFileService", () => { let cuiFileService: CuiFileService; - const coverResponse = (categories: Array<{ code: string; name: string }>) => ({ - resolvedCuiMarking: { categories }, + const coverResponse = () => ({ coverPage: { pdfContent: PDF_BYTES.toString("base64"), encoding: "base64", @@ -37,15 +36,6 @@ describe("CuiFileService", () => { expect(readFile("report.json").toString()).toEqual(PAYLOAD); }); - it("Should keep the original filename when the team has CUI disabled", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); - - const filename = await cuiFileService.writeToFileWithGivenName(PAYLOAD, "no-marking.json"); - - expect(filename).toEqual("no-marking.json"); - expect(readFile("no-marking.json").toString()).toEqual(PAYLOAD); - }); - it("Should fail on an unexpected backend error", async () => { mockAxiosGetError(COVER_URL, 500, { message: "boom" }); @@ -63,7 +53,7 @@ describe("CuiFileService", () => { describe("when the content is unclassified", () => { it("Should only prefix the filename and write no cover sheet", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, coverResponse([])); + mockAxiosGetWithStatus(COVER_URL, 204, ""); const filename = await cuiFileService.writeToFileWithGivenName(PAYLOAD, "packages.json"); @@ -75,7 +65,7 @@ describe("CuiFileService", () => { describe("when the content is classified", () => { it("Should wrap the payload and the decoded cover sheet into a CUI archive", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, coverResponse([{ code: "PRVCY", name: "Privacy" }])); + mockAxiosGetWithStatus(COVER_URL, 200, coverResponse()); const filename = await cuiFileService.writeToFileWithGivenName(PAYLOAD, "packages.json"); @@ -89,7 +79,7 @@ describe("CuiFileService", () => { }); it("Should fail when the cover page uses an unsupported encoding", async () => { - const response = coverResponse([{ code: "PRVCY", name: "Privacy" }]); + const response = coverResponse(); response.coverPage.encoding = "hex"; mockAxiosGetWithStatus(COVER_URL, 200, response); @@ -98,9 +88,7 @@ describe("CuiFileService", () => { }); it("Should fail when the marking applies but no cover page was returned", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, - }); + mockAxiosGetWithStatus(COVER_URL, 200, { teamId: "team-1" }); await expect(cuiFileService.writeToFileWithGivenName(PAYLOAD, "packages.json")) .rejects.toThrow("CUI marking applies but the response contained no cover page."); @@ -119,7 +107,7 @@ describe("CuiFileService", () => { new AdmZip(readFile(filename)).getEntries().map(entry => entry.entryName).sort(); it("Should keep the archive untouched when no marking applies", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); + mockAxiosGetError(COVER_URL, 403, { errorCode: "feature-disabled" }); const exportZip = buildExportZip(); const filename = await cuiFileService.writeZipToFileWithGivenName(exportZip, "export.zip"); @@ -129,7 +117,7 @@ describe("CuiFileService", () => { }); it("Should only prefix the archive when the content is unclassified", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, coverResponse([])); + mockAxiosGetWithStatus(COVER_URL, 204, ""); const exportZip = buildExportZip(); const filename = await cuiFileService.writeZipToFileWithGivenName(exportZip, "export.zip"); @@ -139,7 +127,7 @@ describe("CuiFileService", () => { }); it("Should add the cover sheet into the given archive instead of nesting it", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, coverResponse([{ code: "PRVCY", name: "Privacy" }])); + mockAxiosGetWithStatus(COVER_URL, 200, coverResponse()); const filename = await cuiFileService.writeZipToFileWithGivenName(buildExportZip(), "export.zip"); @@ -156,9 +144,7 @@ describe("CuiFileService", () => { }); it("Should fail when the marking applies but no cover page was returned", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, - }); + mockAxiosGetWithStatus(COVER_URL, 200, { teamId: "team-1" }); await expect(cuiFileService.writeZipToFileWithGivenName(buildExportZip(), "export.zip")) .rejects.toThrow("CUI marking applies but the response contained no cover page."); @@ -175,7 +161,7 @@ describe("CuiFileService", () => { const exists = (...segments: string[]): boolean => existsSync(resolve(process.cwd(), ...segments)); it("Should keep the original directory name when no marking applies", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); + mockAxiosGetError(COVER_URL, 403, { errorCode: "feature-disabled" }); const directoryName = await cuiFileService.writeDirectoryWithGivenName(writeTree, "unmarked-export"); @@ -185,7 +171,7 @@ describe("CuiFileService", () => { }); it("Should only prefix the directory when the content is unclassified", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, coverResponse([])); + mockAxiosGetWithStatus(COVER_URL, 204, ""); const directoryName = await cuiFileService.writeDirectoryWithGivenName(writeTree, "plain-export"); @@ -196,7 +182,7 @@ describe("CuiFileService", () => { }); it("Should prefix the directory and write the cover sheet into it", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, coverResponse([{ code: "PRVCY", name: "Privacy" }])); + mockAxiosGetWithStatus(COVER_URL, 200, coverResponse()); const directoryName = await cuiFileService.writeDirectoryWithGivenName(writeTree, "classified-export"); @@ -209,9 +195,7 @@ describe("CuiFileService", () => { }); it("Should fail without writing anything when no cover page was returned", async () => { - mockAxiosGetWithStatus(COVER_URL, 200, { - resolvedCuiMarking: { categories: [{ code: "PRVCY", name: "Privacy" }] }, - }); + mockAxiosGetWithStatus(COVER_URL, 200, { teamId: "team-1" }); await expect(cuiFileService.writeDirectoryWithGivenName(writeTree, "broken-export")) .rejects.toThrow("CUI marking applies but the response contained no cover page."); diff --git a/tests/utls/http-requests-mock.ts b/tests/utls/http-requests-mock.ts index 3878734e..50734e5b 100644 --- a/tests/utls/http-requests-mock.ts +++ b/tests/utls/http-requests-mock.ts @@ -45,9 +45,9 @@ const mockAxios = () : void => { } } // CUI marking is probed on every user-facing write. Unless a test opts in, - // answer 204 so the CLI keeps the original filename. + // answer 403 so the CLI keeps the original filename. if (requestUrl.endsWith(CUI_PDF_COVER_PATH)) { - return Promise.resolve({ status: 204, data: "" }); + return Promise.resolve({ status: 403, data: "" }); } fail("API call not mocked.") }); From 595ec0b0d9f71cefa65815bfbcf81395546dbe4e Mon Sep 17 00:00:00 2001 From: denniswo Date: Wed, 12 Aug 2026 14:42:02 +0200 Subject: [PATCH 3/4] SP-1173: cover the unclassified and fail-closed paths at command level The command specs only exercised classified writes, so the 204 branch was reached by the unit spec alone. Each artifact shape now has an unclassified case, and a failing cover call is asserted to abort the command without producing a file. Includes-AI-Code: true Co-authored-by: Cursor --- .../cui-marking-directory-exports.spec.ts | 24 ++++++++++++++ .../cui-marking-json-commands.spec.ts | 33 ++++++++++++++++++- .../cui-marking-output-to-json-file.spec.ts | 22 +++++++++++++ .../cui-marking-single-file-exports.spec.ts | 21 ++++++++++++ .../commands/cui-marking-zip-commands.spec.ts | 20 +++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) diff --git a/tests/commands/cui-marking-directory-exports.spec.ts b/tests/commands/cui-marking-directory-exports.spec.ts index 6f0822d4..f851fc11 100644 --- a/tests/commands/cui-marking-directory-exports.spec.ts +++ b/tests/commands/cui-marking-directory-exports.spec.ts @@ -46,6 +46,17 @@ function exists(...segments: string[]): boolean { return existsSync(resolve(process.cwd(), ...segments)); } +function markAsUnclassified(): void { + mockAxiosGetWithStatus(COVER_URL, 204, ""); +} + +function unclassifiedDirectory(prefix: string, expectedName: string): string { + const directoryName = loggedDirectoryName(prefix); + expect(directoryName).toEqual(`${CuiFileService.UNCLASSIFIED_PREFIX}${expectedName}`); + + return directoryName; +} + function buildPackageZip(packageKey: string): Buffer { const zip = new AdmZip(); zip.addFile("package.json", Buffer.from(JSON.stringify({ key: packageKey, name: "My Package" }))); @@ -83,6 +94,19 @@ describe("CUI marking of directory exports", () => { expect(exists(packageKey)).toBe(false); }); + it("Should only prefix the directory when the content is unclassified", async () => { + markAsUnclassified(); + const packageKey = "pkg-unclassified"; + mockAxiosGet(`https://myTeam.celonis.cloud/pacman/api/core/staging/packages/${packageKey}/export-file`, buildPackageZip(packageKey)); + + await new SinglePackageExportService(testContext).exportPackage(packageKey, false, null); + + const directoryName = unclassifiedDirectory(EXPORT_MESSAGE, packageKey); + expect(exists(directoryName, "nodes", "node-1.json")).toBe(true); + expect(exists(directoryName, CuiFileService.COVER_SHEET_FILE_NAME)).toBe(false); + expect(exists(packageKey)).toBe(false); + }); + it("Should mark the directory of config branch export", async () => { const packageKey = "pkg-branch"; const branchPackageKey = `${packageKey}@${BRANCH}`; diff --git a/tests/commands/cui-marking-json-commands.spec.ts b/tests/commands/cui-marking-json-commands.spec.ts index cccde411..e42f8bc9 100644 --- a/tests/commands/cui-marking-json-commands.spec.ts +++ b/tests/commands/cui-marking-json-commands.spec.ts @@ -1,11 +1,12 @@ 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 { mockAxiosGet, mockAxiosGetError, 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 { FatalError } from "../../src/core/utils/logger"; import { ConfigUtils } from "../utls/config-utils"; import { zipToTempFolder } from "../utls/fs-utils"; import { DeploymentService } from "../../src/commands/deployment/deployment.service"; @@ -48,6 +49,18 @@ function markedPayload(prefix?: string): any { return payloadFromArchive(loggedFileName(prefix)); } +function markAsUnclassified(): void { + mockAxiosGetWithStatus(COVER_URL, 204, ""); +} + +function unclassifiedPayload(prefix?: string): any { + const filename = loggedFileName(prefix); + expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); + expect(filename.endsWith(".json")).toBe(true); + + return JSON.parse(readFileSync(resolve(process.cwd(), filename), "utf-8")); +} + describe("CUI marking of --json commands", () => { beforeEach(() => { @@ -63,6 +76,24 @@ describe("CUI marking of --json commands", () => { expect(markedPayload()).toEqual(targets); }); + it("Should only prefix the listing when the content is unclassified", async () => { + markAsUnclassified(); + 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(unclassifiedPayload()).toEqual(targets); + }); + + it("Should fail the command without writing anything when the cover call fails", async () => { + mockAxiosGetError(COVER_URL, 500, { message: "boom" }); + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/deployments/targets?deployableType=app-package&packageKey=package-key", []); + + await expect(new DeploymentService(testContext).getTargets(true, "app-package", "package-key")).rejects.toThrow(FatalError); + expect(loggingTestTransport.logMessages.some(entry => entry.message.includes(FileService.fileDownloadedMessage))).toBe(false); + }); + 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); diff --git a/tests/commands/cui-marking-output-to-json-file.spec.ts b/tests/commands/cui-marking-output-to-json-file.spec.ts index 5a4ff707..13a62b65 100644 --- a/tests/commands/cui-marking-output-to-json-file.spec.ts +++ b/tests/commands/cui-marking-output-to-json-file.spec.ts @@ -45,6 +45,18 @@ function markedPayload(prefix?: string): any { return payloadFromArchive(loggedFileName(prefix)); } +function markAsUnclassified(): void { + mockAxiosGetWithStatus(COVER_URL, 204, ""); +} + +function unclassifiedPayload(prefix?: string): any { + const filename = loggedFileName(prefix); + expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); + expect(filename.endsWith(".json")).toBe(true); + + return JSON.parse(readFileSync(resolve(process.cwd(), filename), "utf-8")); +} + describe("CUI marking of --outputToJsonFile commands", () => { beforeEach(() => { @@ -78,6 +90,16 @@ describe("CUI marking of --outputToJsonFile commands", () => { expect(markedPayload()).toEqual(dataPool); }); + it("Should only prefix the report when the content is unclassified", async () => { + markAsUnclassified(); + const dataPool = { id: POOL_ID, name: "Pool 1", objects: [] }; + mockAxiosGet(`https://myTeam.celonis.cloud/integration/api/pools/${POOL_ID}/v2/export`, dataPool); + + await new DataPoolCommandService(testContext).exportDataPool(POOL_ID, true); + + expect(unclassifiedPayload()).toEqual(dataPool); + }); + it("Should mark the data pool batch import report", async () => { const report = { installedVersions: [{ poolId: POOL_ID, version: "1.0.0" }] }; mockAxiosPost("https://myTeam.celonis.cloud/integration/api/pool/batch-import", report); diff --git a/tests/commands/cui-marking-single-file-exports.spec.ts b/tests/commands/cui-marking-single-file-exports.spec.ts index e574f7dd..1fd420e3 100644 --- a/tests/commands/cui-marking-single-file-exports.spec.ts +++ b/tests/commands/cui-marking-single-file-exports.spec.ts @@ -53,6 +53,17 @@ function markedJson(expectedName: string, entryName: string): any { return JSON.parse(markedEntry(expectedName, entryName)); } +function markAsUnclassified(): void { + mockAxiosGetWithStatus(COVER_URL, 204, ""); +} + +function unclassifiedFile(expectedName: string): string { + const filename = loggedFileName(); + expect(filename).toEqual(`${CuiFileService.UNCLASSIFIED_PREFIX}${expectedName}`); + + return readFileSync(resolve(process.cwd(), filename), "utf-8"); +} + describe("CUI marking of single-file exports", () => { beforeEach(() => { @@ -68,6 +79,16 @@ describe("CUI marking of single-file exports", () => { expect(parse(markedEntry("asset_asset-1", "asset_asset-1.yml"))).toEqual(asset); }); + it("Should only prefix the export when the content is unclassified", async () => { + markAsUnclassified(); + const asset = { key: "asset-1", name: "My Asset", rootNodeKey: PACKAGE_KEY }; + mockAxiosGet(`https://myTeam.celonis.cloud/package-manager/api/nodes/asset/export/${PACKAGE_KEY}.asset-1`, asset); + + await new AssetCommandService(testContext).pullAsset(`${PACKAGE_KEY}.asset-1`); + + expect(parse(unclassifiedFile("asset_asset-1.yml"))).toEqual(asset); + }); + it("Should mark the exported skill", async () => { const skill = { id: SKILL_ID, name: "My Skill" }; mockAxiosGet(`https://myTeam.celonis.cloud/action-engine/api/projects/${PROJECT_ID}/skills/${SKILL_ID}/export`, skill); diff --git a/tests/commands/cui-marking-zip-commands.spec.ts b/tests/commands/cui-marking-zip-commands.spec.ts index 840c5682..82729362 100644 --- a/tests/commands/cui-marking-zip-commands.spec.ts +++ b/tests/commands/cui-marking-zip-commands.spec.ts @@ -51,6 +51,17 @@ function entryNames(archive: AdmZip): string[] { return archive.getEntries().map(entry => entry.entryName).sort(); } +function markAsUnclassified(): void { + mockAxiosGetWithStatus(COVER_URL, 204, ""); +} + +function unclassifiedArchive(expectedName: string): AdmZip { + const filename = loggedFileName(); + expect(filename).toEqual(`${CuiFileService.UNCLASSIFIED_PREFIX}${expectedName}`); + + return new AdmZip(readFileSync(resolve(process.cwd(), filename))); +} + function buildPackageZip(): Buffer { const zip = new AdmZip(); zip.addFile("package.json", Buffer.from(JSON.stringify({ key: PACKAGE_KEY, name: "My Package" }))); @@ -89,6 +100,15 @@ describe("CUI marking of archive exports", () => { ]); }); + it("Should only prefix the archive when the content is unclassified", async () => { + markAsUnclassified(); + mockAxiosGet(`https://myTeam.celonis.cloud/pacman/api/core/staging/packages/${PACKAGE_KEY}/export-file`, buildPackageZip()); + + await new SinglePackageExportService(testContext).exportPackage(PACKAGE_KEY, true, null); + + expect(entryNames(unclassifiedArchive(`${PACKAGE_KEY}.zip`))).toEqual(["nodes/node-1.json", "package.json"]); + }); + it("Should mark the archive of config branch export --zip", async () => { const branchPackageKey = `${PACKAGE_KEY}@${BRANCH}`; mockAxiosGet(`https://myTeam.celonis.cloud/pacman/api/core/staging/packages/${branchPackageKey}/export-file`, buildPackageZip()); From 49ee9f466634791c808b2fbfa15a2d4a77cafcb6 Mon Sep 17 00:00:00 2001 From: denniswo Date: Thu, 13 Aug 2026 09:38:44 +0200 Subject: [PATCH 4/4] SP-1173: drop the unclassified CUI path Marked content is always classified, so the CLI recognises only 403 and 200. A 204 is no longer a use case and now fails the command like any other unusable answer, leaving no output behind. Includes-AI-Code: true Co-authored-by: Cursor --- docs/cui-marking.md | 23 ++++++------- src/core/utils/cui-api.ts | 8 ----- src/core/utils/cui-file-service.ts | 8 ----- .../cui-marking-directory-exports.spec.ts | 24 ------------- .../cui-marking-json-commands.spec.ts | 22 ------------ .../cui-marking-output-to-json-file.spec.ts | 22 ------------ .../cui-marking-single-file-exports.spec.ts | 21 ------------ .../commands/cui-marking-zip-commands.spec.ts | 20 ----------- .../commands/studio/list-cui-marking.spec.ts | 22 ------------ tests/core/utils/cui-file-service.spec.ts | 34 +++---------------- 10 files changed, 16 insertions(+), 188 deletions(-) diff --git a/docs/cui-marking.md b/docs/cui-marking.md index 0baf1dc4..88996bae 100644 --- a/docs/cui-marking.md +++ b/docs/cui-marking.md @@ -11,20 +11,19 @@ Example: `list packages --json` would otherwise write `packages.json`. | Cover response | Meaning | Outcome | |---|---|---| | **403** | Feature flag disabled | `packages.json` | -| **204** | Unclassified | `Unclassified - packages.json` | | **200** | Classified | `CUI - packages.zip` containing `packages.json` and `CUI_Cover_Sheet.pdf` | -| Any other response | Fail closed | Nothing written; the command errors | +| Any other response, including **204** | Fail closed | Nothing written; the command errors | -The status code alone decides the outcome. Any failure of the cover call, including an unexpected status or a **200** without a usable cover page, aborts the command and leaves no output behind. +The status code alone decides the outcome. Marked content is always classified: there is no unclassified artifact. Any other answer, whether a **204**, an unexpected status, a transport failure, or a **200** without a usable cover page, aborts the command and leaves no output behind. ## Scope: how the write is triggered -| Trigger | Commands | Example (unclassified) | Example (classified) | -|---|---|---|---| -| `--json` listings and reports | `list spaces`, `list packages`, `list assets` / `assignments` / `data-pools`, `config *`, `t2tc package list` / `diff`, `deployment *`, `asset-registry *` | `list packages --json` → `Unclassified - packages.json` | `list packages --json` → `CUI - packages.zip` containing `packages.json` and `CUI_Cover_Sheet.pdf` | -| `-o, --outputToJsonFile` reports | `analyze` / `import action-flows`, `export data-pool`, `import data-pools`, `t2tc package import` report | `export data-pool -o` → `Unclassified - .json` | `export data-pool -o` → `CUI - .zip` containing the JSON and `CUI_Cover_Sheet.pdf` | -| Artifact is already an archive | `config package export --zip`, `config branch export --zip`, `t2tc package export`, `export action-flows`, `pull package` | `config package export --zip` → `Unclassified - my-package.zip` | `config package export --zip` → `CUI - my-package.zip` with `CUI_Cover_Sheet.pdf` inside the archive | -| Single non-archive export | `pull asset` / `skill` / `data-pool` / `view-bookmarks` / `bookmarks`, `export bookmarks` | `pull asset` → `Unclassified - asset_.yml` | `pull asset` → `CUI - asset_.zip` containing the YAML and `CUI_Cover_Sheet.pdf` | -| Output is a directory | `config package export`, `config branch export`, `t2tc package export --unzip` | `config package export` → `Unclassified - my-package/` | `config package export` → `CUI - my-package/` with `CUI_Cover_Sheet.pdf` inside | -| `--gitBranch` variants | `config package export`, `config branch export`, `t2tc package export` | Out of scope | Out of scope | -| No output flag | Console-only listings, profile / git-profile / log files | Out of scope | Out of scope | +| Trigger | Commands | Example when classified | +|---|---|---| +| `--json` listings and reports | `list spaces`, `list packages`, `list assets` / `assignments` / `data-pools`, `config *`, `t2tc package list` / `diff`, `deployment *`, `asset-registry *` | `list packages --json` → `CUI - packages.zip` containing `packages.json` and `CUI_Cover_Sheet.pdf` | +| `-o, --outputToJsonFile` reports | `analyze` / `import action-flows`, `export data-pool`, `import data-pools`, `t2tc package import` report | `export data-pool -o` → `CUI - .zip` containing the JSON and `CUI_Cover_Sheet.pdf` | +| Artifact is already an archive | `config package export --zip`, `config branch export --zip`, `t2tc package export`, `export action-flows`, `pull package` | `config package export --zip` → `CUI - my-package.zip` with `CUI_Cover_Sheet.pdf` inside the archive | +| Single non-archive export | `pull asset` / `skill` / `data-pool` / `view-bookmarks` / `bookmarks`, `export bookmarks` | `pull asset` → `CUI - asset_.zip` containing the YAML and `CUI_Cover_Sheet.pdf` | +| Output is a directory | `config package export`, `config branch export`, `t2tc package export --unzip` | `config package export` → `CUI - my-package/` with `CUI_Cover_Sheet.pdf` inside | +| `--gitBranch` variants | `config package export`, `config branch export`, `t2tc package export` | Out of scope | +| No output flag | Console-only listings, profile / git-profile / log files | Out of scope | diff --git a/src/core/utils/cui-api.ts b/src/core/utils/cui-api.ts index ababc38c..9011c568 100644 --- a/src/core/utils/cui-api.ts +++ b/src/core/utils/cui-api.ts @@ -8,20 +8,17 @@ export interface CuiPdfCoverResponse { export enum CuiMarking { DISABLED = "DISABLED", - UNCLASSIFIED = "UNCLASSIFIED", CLASSIFIED = "CLASSIFIED", } export type CuiMarkingDecision = | { marking: CuiMarking.DISABLED } - | { marking: CuiMarking.UNCLASSIFIED } | { marking: CuiMarking.CLASSIFIED; cover: CuiPdfCoverResponse }; export class CuiApi { private static readonly CUI_PDF_COVER_SHEET_URL = "/api/team/cui-settings/cui-pdf-cover"; private static readonly STATUS_OK = 200; - private static readonly STATUS_NO_CONTENT = 204; private static readonly STATUS_FORBIDDEN = 403; private readonly httpClient: () => HttpClient; @@ -38,11 +35,6 @@ export class CuiApi { return { marking: CuiMarking.DISABLED }; } - if (status === CuiApi.STATUS_NO_CONTENT) { - logger.debug("CUI marking applies, the content is unclassified"); - return { marking: CuiMarking.UNCLASSIFIED }; - } - if (status === CuiApi.STATUS_OK && data) { return { marking: CuiMarking.CLASSIFIED, cover: data as CuiPdfCoverResponse }; } diff --git a/src/core/utils/cui-file-service.ts b/src/core/utils/cui-file-service.ts index 19cf5338..5b069ccf 100644 --- a/src/core/utils/cui-file-service.ts +++ b/src/core/utils/cui-file-service.ts @@ -9,7 +9,6 @@ import { FatalError } from "./logger"; export class CuiFileService { public static readonly COVER_SHEET_FILE_NAME = "CUI_Cover_Sheet.pdf"; public static readonly CLASSIFIED_PREFIX = "CUI - "; - public static readonly UNCLASSIFIED_PREFIX = "Unclassified - "; private static readonly BASE64_ENCODING = "base64"; @@ -67,13 +66,6 @@ export class CuiFileService { return filename; } - if (decision.marking === CuiMarking.UNCLASSIFIED) { - const unclassifiedName = this.prefixFileName(filename, CuiFileService.UNCLASSIFIED_PREFIX); - write(unclassifiedName); - - return unclassifiedName; - } - return onClassified(decision.cover); } diff --git a/tests/commands/cui-marking-directory-exports.spec.ts b/tests/commands/cui-marking-directory-exports.spec.ts index f851fc11..6f0822d4 100644 --- a/tests/commands/cui-marking-directory-exports.spec.ts +++ b/tests/commands/cui-marking-directory-exports.spec.ts @@ -46,17 +46,6 @@ function exists(...segments: string[]): boolean { return existsSync(resolve(process.cwd(), ...segments)); } -function markAsUnclassified(): void { - mockAxiosGetWithStatus(COVER_URL, 204, ""); -} - -function unclassifiedDirectory(prefix: string, expectedName: string): string { - const directoryName = loggedDirectoryName(prefix); - expect(directoryName).toEqual(`${CuiFileService.UNCLASSIFIED_PREFIX}${expectedName}`); - - return directoryName; -} - function buildPackageZip(packageKey: string): Buffer { const zip = new AdmZip(); zip.addFile("package.json", Buffer.from(JSON.stringify({ key: packageKey, name: "My Package" }))); @@ -94,19 +83,6 @@ describe("CUI marking of directory exports", () => { expect(exists(packageKey)).toBe(false); }); - it("Should only prefix the directory when the content is unclassified", async () => { - markAsUnclassified(); - const packageKey = "pkg-unclassified"; - mockAxiosGet(`https://myTeam.celonis.cloud/pacman/api/core/staging/packages/${packageKey}/export-file`, buildPackageZip(packageKey)); - - await new SinglePackageExportService(testContext).exportPackage(packageKey, false, null); - - const directoryName = unclassifiedDirectory(EXPORT_MESSAGE, packageKey); - expect(exists(directoryName, "nodes", "node-1.json")).toBe(true); - expect(exists(directoryName, CuiFileService.COVER_SHEET_FILE_NAME)).toBe(false); - expect(exists(packageKey)).toBe(false); - }); - it("Should mark the directory of config branch export", async () => { const packageKey = "pkg-branch"; const branchPackageKey = `${packageKey}@${BRANCH}`; diff --git a/tests/commands/cui-marking-json-commands.spec.ts b/tests/commands/cui-marking-json-commands.spec.ts index e42f8bc9..cd186f25 100644 --- a/tests/commands/cui-marking-json-commands.spec.ts +++ b/tests/commands/cui-marking-json-commands.spec.ts @@ -49,18 +49,6 @@ function markedPayload(prefix?: string): any { return payloadFromArchive(loggedFileName(prefix)); } -function markAsUnclassified(): void { - mockAxiosGetWithStatus(COVER_URL, 204, ""); -} - -function unclassifiedPayload(prefix?: string): any { - const filename = loggedFileName(prefix); - expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); - expect(filename.endsWith(".json")).toBe(true); - - return JSON.parse(readFileSync(resolve(process.cwd(), filename), "utf-8")); -} - describe("CUI marking of --json commands", () => { beforeEach(() => { @@ -76,16 +64,6 @@ describe("CUI marking of --json commands", () => { expect(markedPayload()).toEqual(targets); }); - it("Should only prefix the listing when the content is unclassified", async () => { - markAsUnclassified(); - 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(unclassifiedPayload()).toEqual(targets); - }); - it("Should fail the command without writing anything when the cover call fails", async () => { mockAxiosGetError(COVER_URL, 500, { message: "boom" }); mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/deployments/targets?deployableType=app-package&packageKey=package-key", []); diff --git a/tests/commands/cui-marking-output-to-json-file.spec.ts b/tests/commands/cui-marking-output-to-json-file.spec.ts index 13a62b65..5a4ff707 100644 --- a/tests/commands/cui-marking-output-to-json-file.spec.ts +++ b/tests/commands/cui-marking-output-to-json-file.spec.ts @@ -45,18 +45,6 @@ function markedPayload(prefix?: string): any { return payloadFromArchive(loggedFileName(prefix)); } -function markAsUnclassified(): void { - mockAxiosGetWithStatus(COVER_URL, 204, ""); -} - -function unclassifiedPayload(prefix?: string): any { - const filename = loggedFileName(prefix); - expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); - expect(filename.endsWith(".json")).toBe(true); - - return JSON.parse(readFileSync(resolve(process.cwd(), filename), "utf-8")); -} - describe("CUI marking of --outputToJsonFile commands", () => { beforeEach(() => { @@ -90,16 +78,6 @@ describe("CUI marking of --outputToJsonFile commands", () => { expect(markedPayload()).toEqual(dataPool); }); - it("Should only prefix the report when the content is unclassified", async () => { - markAsUnclassified(); - const dataPool = { id: POOL_ID, name: "Pool 1", objects: [] }; - mockAxiosGet(`https://myTeam.celonis.cloud/integration/api/pools/${POOL_ID}/v2/export`, dataPool); - - await new DataPoolCommandService(testContext).exportDataPool(POOL_ID, true); - - expect(unclassifiedPayload()).toEqual(dataPool); - }); - it("Should mark the data pool batch import report", async () => { const report = { installedVersions: [{ poolId: POOL_ID, version: "1.0.0" }] }; mockAxiosPost("https://myTeam.celonis.cloud/integration/api/pool/batch-import", report); diff --git a/tests/commands/cui-marking-single-file-exports.spec.ts b/tests/commands/cui-marking-single-file-exports.spec.ts index 1fd420e3..e574f7dd 100644 --- a/tests/commands/cui-marking-single-file-exports.spec.ts +++ b/tests/commands/cui-marking-single-file-exports.spec.ts @@ -53,17 +53,6 @@ function markedJson(expectedName: string, entryName: string): any { return JSON.parse(markedEntry(expectedName, entryName)); } -function markAsUnclassified(): void { - mockAxiosGetWithStatus(COVER_URL, 204, ""); -} - -function unclassifiedFile(expectedName: string): string { - const filename = loggedFileName(); - expect(filename).toEqual(`${CuiFileService.UNCLASSIFIED_PREFIX}${expectedName}`); - - return readFileSync(resolve(process.cwd(), filename), "utf-8"); -} - describe("CUI marking of single-file exports", () => { beforeEach(() => { @@ -79,16 +68,6 @@ describe("CUI marking of single-file exports", () => { expect(parse(markedEntry("asset_asset-1", "asset_asset-1.yml"))).toEqual(asset); }); - it("Should only prefix the export when the content is unclassified", async () => { - markAsUnclassified(); - const asset = { key: "asset-1", name: "My Asset", rootNodeKey: PACKAGE_KEY }; - mockAxiosGet(`https://myTeam.celonis.cloud/package-manager/api/nodes/asset/export/${PACKAGE_KEY}.asset-1`, asset); - - await new AssetCommandService(testContext).pullAsset(`${PACKAGE_KEY}.asset-1`); - - expect(parse(unclassifiedFile("asset_asset-1.yml"))).toEqual(asset); - }); - it("Should mark the exported skill", async () => { const skill = { id: SKILL_ID, name: "My Skill" }; mockAxiosGet(`https://myTeam.celonis.cloud/action-engine/api/projects/${PROJECT_ID}/skills/${SKILL_ID}/export`, skill); diff --git a/tests/commands/cui-marking-zip-commands.spec.ts b/tests/commands/cui-marking-zip-commands.spec.ts index 82729362..840c5682 100644 --- a/tests/commands/cui-marking-zip-commands.spec.ts +++ b/tests/commands/cui-marking-zip-commands.spec.ts @@ -51,17 +51,6 @@ function entryNames(archive: AdmZip): string[] { return archive.getEntries().map(entry => entry.entryName).sort(); } -function markAsUnclassified(): void { - mockAxiosGetWithStatus(COVER_URL, 204, ""); -} - -function unclassifiedArchive(expectedName: string): AdmZip { - const filename = loggedFileName(); - expect(filename).toEqual(`${CuiFileService.UNCLASSIFIED_PREFIX}${expectedName}`); - - return new AdmZip(readFileSync(resolve(process.cwd(), filename))); -} - function buildPackageZip(): Buffer { const zip = new AdmZip(); zip.addFile("package.json", Buffer.from(JSON.stringify({ key: PACKAGE_KEY, name: "My Package" }))); @@ -100,15 +89,6 @@ describe("CUI marking of archive exports", () => { ]); }); - it("Should only prefix the archive when the content is unclassified", async () => { - markAsUnclassified(); - mockAxiosGet(`https://myTeam.celonis.cloud/pacman/api/core/staging/packages/${PACKAGE_KEY}/export-file`, buildPackageZip()); - - await new SinglePackageExportService(testContext).exportPackage(PACKAGE_KEY, true, null); - - expect(entryNames(unclassifiedArchive(`${PACKAGE_KEY}.zip`))).toEqual(["nodes/node-1.json", "package.json"]); - }); - it("Should mark the archive of config branch export --zip", async () => { const branchPackageKey = `${PACKAGE_KEY}@${BRANCH}`; mockAxiosGet(`https://myTeam.celonis.cloud/pacman/api/core/staging/packages/${branchPackageKey}/export-file`, buildPackageZip()); diff --git a/tests/commands/studio/list-cui-marking.spec.ts b/tests/commands/studio/list-cui-marking.spec.ts index df7968c5..2da0c536 100644 --- a/tests/commands/studio/list-cui-marking.spec.ts +++ b/tests/commands/studio/list-cui-marking.spec.ts @@ -73,21 +73,10 @@ describe("CUI marking of Studio listings", () => { await listSpaces(); const filename = loggedFileName(); - expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(false); expect(filename.startsWith(CuiFileService.CLASSIFIED_PREFIX)).toBe(false); expect(readWrittenJson(filename)).toEqual(SPACES); }); - it("Should only prefix the listing when the content is unclassified", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); - - await listSpaces(); - - const filename = loggedFileName(); - expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); - expect(readWrittenJson(filename)).toEqual(SPACES); - }); - it("Should not probe CUI when the listing only goes to the console", async () => { await new SpaceCommandService(testContext).listSpaces(false); @@ -120,21 +109,10 @@ describe("CUI marking of Studio listings", () => { await listPackages(); const filename = loggedFileName(); - expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(false); expect(filename.startsWith(CuiFileService.CLASSIFIED_PREFIX)).toBe(false); expect(readWrittenJson(filename)).toEqual(LISTED_PACKAGES); }); - it("Should only prefix the listing when the content is unclassified", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); - - await listPackages(); - - const filename = loggedFileName(); - expect(filename.startsWith(CuiFileService.UNCLASSIFIED_PREFIX)).toBe(true); - expect(readWrittenJson(filename)).toEqual(LISTED_PACKAGES); - }); - it("Should not probe CUI when the listing only goes to the console", async () => { await new PackageCommandService(testContext).listPackages(false, false, []); diff --git a/tests/core/utils/cui-file-service.spec.ts b/tests/core/utils/cui-file-service.spec.ts index 2cd69f32..826b9925 100644 --- a/tests/core/utils/cui-file-service.spec.ts +++ b/tests/core/utils/cui-file-service.spec.ts @@ -35,7 +35,9 @@ describe("CuiFileService", () => { expect(filename).toEqual("report.json"); expect(readFile("report.json").toString()).toEqual(PAYLOAD); }); + }); + describe("when the cover response cannot be used", () => { it("Should fail on an unexpected backend error", async () => { mockAxiosGetError(COVER_URL, 500, { message: "boom" }); @@ -49,17 +51,12 @@ describe("CuiFileService", () => { await expect(cuiFileService.writeToFileWithGivenName(PAYLOAD, "empty-cover.json")).rejects.toThrow(FatalError); expect(() => accessSync(resolve(process.cwd(), "empty-cover.json"))).toThrow(); }); - }); - describe("when the content is unclassified", () => { - it("Should only prefix the filename and write no cover sheet", async () => { + it("Should fail when the backend answers with no content", async () => { mockAxiosGetWithStatus(COVER_URL, 204, ""); - const filename = await cuiFileService.writeToFileWithGivenName(PAYLOAD, "packages.json"); - - expect(filename).toEqual("Unclassified - packages.json"); - expect(readFile(filename).toString()).toEqual(PAYLOAD); - expect(() => accessSync(resolve(process.cwd(), CuiFileService.COVER_SHEET_FILE_NAME))).toThrow(); + await expect(cuiFileService.writeToFileWithGivenName(PAYLOAD, "no-content.json")).rejects.toThrow(FatalError); + expect(() => accessSync(resolve(process.cwd(), "no-content.json"))).toThrow(); }); }); @@ -116,16 +113,6 @@ describe("CuiFileService", () => { expect(readFile(filename).equals(exportZip)).toBe(true); }); - it("Should only prefix the archive when the content is unclassified", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); - const exportZip = buildExportZip(); - - const filename = await cuiFileService.writeZipToFileWithGivenName(exportZip, "export.zip"); - - expect(filename).toEqual("Unclassified - export.zip"); - expect(readFile(filename).equals(exportZip)).toBe(true); - }); - it("Should add the cover sheet into the given archive instead of nesting it", async () => { mockAxiosGetWithStatus(COVER_URL, 200, coverResponse()); @@ -170,17 +157,6 @@ describe("CuiFileService", () => { expect(exists(directoryName, CuiFileService.COVER_SHEET_FILE_NAME)).toBe(false); }); - it("Should only prefix the directory when the content is unclassified", async () => { - mockAxiosGetWithStatus(COVER_URL, 204, ""); - - const directoryName = await cuiFileService.writeDirectoryWithGivenName(writeTree, "plain-export"); - - expect(directoryName).toEqual("Unclassified - plain-export"); - expect(exists(directoryName, "nodes", "node-1.json")).toBe(true); - expect(exists(directoryName, CuiFileService.COVER_SHEET_FILE_NAME)).toBe(false); - expect(exists("plain-export")).toBe(false); - }); - it("Should prefix the directory and write the cover sheet into it", async () => { mockAxiosGetWithStatus(COVER_URL, 200, coverResponse());