diff --git a/docs/cui-marking.md b/docs/cui-marking.md index 5c71903..0baf1dc 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 e17254e..ababc38 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 7a9b01f..19cf533 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); diff --git a/tests/commands/cui-marking-directory-exports.spec.ts b/tests/commands/cui-marking-directory-exports.spec.ts index abedef9..f851fc1 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" }, }); } @@ -47,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" }))); @@ -84,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 b4f28d6..e42f8bc 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"; @@ -24,7 +25,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" }, }); } @@ -49,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(() => { @@ -64,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 0940a6f..13a62b6 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" }, }); } @@ -46,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(() => { @@ -79,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 736138c..1fd420e 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" }, }); } @@ -54,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(() => { @@ -69,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 bf66ad3..8272936 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" }, }); } @@ -52,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" }))); @@ -90,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()); diff --git a/tests/commands/studio/list-cui-marking.spec.ts b/tests/commands/studio/list-cui-marking.spec.ts index f07dc53..df7968c 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 f05250c..2cd69f3 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 3878734..50734e5 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.") });