-
Notifications
You must be signed in to change notification settings - Fork 4
SP-1173: extend CUI marking to single-file exports #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Dennis Woditsch (dwoditsch)
wants to merge
4
commits into
feat/SP-1173-cui-marking-zip-exports
from
feat/SP-1173-cui-marking-single-file-exports
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
54cd601
SP-1173: mark the single files the pull commands write
dwoditsch 16e6218
SP-1173: cover CUI marking of single-file exports
dwoditsch 6a10299
SP-1173: cover the update path of BaseManager
dwoditsch 64b65ef
SP-1173: cover the BaseManager error paths
dwoditsch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| import { resolve } from "node:path"; | ||
| import { readFileSync } from "node:fs"; | ||
| import AdmZip = require("adm-zip"); | ||
| import { mockAxiosGet, mockAxiosGetWithStatus } 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 { parse } from "../../src/core/utils/yaml"; | ||
| import { AssetCommandService } from "../../src/commands/studio/command-service/asset-command.service"; | ||
| import { SkillCommandService } from "../../src/commands/action-flows/skill/skill-command.service"; | ||
| import { DataPoolCommandService } from "../../src/commands/data-pipeline/data-pool/data-pool-command.service"; | ||
| import { ViewBookmarksCommandService } from "../../src/commands/view/view-bookmarks-command.service"; | ||
| import { AnalysisBookmarksCommandService } from "../../src/commands/analysis/analysis-bookmarks-command.service"; | ||
| import { BookmarksCommandService } from "../../src/commands/bookmarks/bookmarks-command.service"; | ||
|
|
||
| const COVER_URL = "https://myTeam.celonis.cloud/api/team/cui-settings/cui-pdf-cover"; | ||
| const PDF_BYTES = Buffer.from("%PDF-1.4 cover sheet"); | ||
|
|
||
| const PROJECT_ID = "project-1"; | ||
| const SKILL_ID = "skill-1"; | ||
| const POOL_ID = "pool-1"; | ||
| const BOARD_ID = "board-1"; | ||
| const ANALYSIS_ID = "analysis-1"; | ||
| 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" }, | ||
| }); | ||
| } | ||
|
|
||
| function loggedFileName(): string { | ||
| const prefix = FileService.fileDownloadedMessage; | ||
| const message = loggingTestTransport.logMessages.map(entry => entry.message).find(entry => entry.includes(prefix)); | ||
| return message.split(prefix)[1]; | ||
| } | ||
|
|
||
| function markedArchive(expectedName: string): AdmZip { | ||
| const filename = loggedFileName(); | ||
| expect(filename).toEqual(`${CuiFileService.CLASSIFIED_PREFIX}${expectedName}.zip`); | ||
|
|
||
| const archive = new AdmZip(readFileSync(resolve(process.cwd(), filename))); | ||
| expect(archive.getEntry(CuiFileService.COVER_SHEET_FILE_NAME).getData().equals(PDF_BYTES)).toBe(true); | ||
| return archive; | ||
| } | ||
|
|
||
| function markedEntry(expectedName: string, entryName: string): string { | ||
| return markedArchive(expectedName).getEntry(entryName).getData().toString(); | ||
| } | ||
|
|
||
| function markedJson(expectedName: string, entryName: string): any { | ||
| return JSON.parse(markedEntry(expectedName, entryName)); | ||
| } | ||
|
|
||
| describe("CUI marking of single-file exports", () => { | ||
|
|
||
| beforeEach(() => { | ||
| markAsClassified(); | ||
| }); | ||
|
|
||
| it("Should mark the exported asset as YAML", async () => { | ||
| 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(markedEntry("asset_asset-1", "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); | ||
|
|
||
| await new SkillCommandService(testContext).pullSkill(null, PROJECT_ID, SKILL_ID); | ||
|
|
||
| expect(markedJson(`skill_${SKILL_ID}`, `skill_${SKILL_ID}.json`)).toEqual(skill); | ||
| }); | ||
|
|
||
| it("Should mark the exported data pool", async () => { | ||
| const dataPool = { id: POOL_ID, name: "Pool 1" }; | ||
| mockAxiosGet(`https://myTeam.celonis.cloud/integration/api/pools/${POOL_ID}/export`, dataPool); | ||
|
|
||
| await new DataPoolCommandService(testContext).pullDataPool(POOL_ID); | ||
|
|
||
| expect(markedJson(`data-pool_${POOL_ID}`, `data-pool_${POOL_ID}.json`)).toEqual(dataPool); | ||
| }); | ||
|
|
||
| it("Should mark the exported view bookmarks", async () => { | ||
| const bookmarks = [{ bookmark: { name: "My View Bookmark" } }]; | ||
| mockAxiosGet(`https://myTeam.celonis.cloud/blueprint/api/bookmarks/export?boardId=${BOARD_ID}&type=USER`, bookmarks); | ||
|
|
||
| await new ViewBookmarksCommandService(testContext).pullViewBookmarks(BOARD_ID, undefined); | ||
|
|
||
| expect(markedJson(`studio_view_bookmarks_${BOARD_ID}`, `studio_view_bookmarks_${BOARD_ID}.json`)).toEqual(bookmarks); | ||
| }); | ||
|
|
||
| it("Should mark the exported analysis bookmarks", async () => { | ||
| const bookmarks = [{ bookmark: { name: "My Analysis Bookmark" } }]; | ||
| mockAxiosGet(`https://myTeam.celonis.cloud/process-analytics/api/bookmarks/export?analysisId=${ANALYSIS_ID}&type=USER`, bookmarks); | ||
|
|
||
| await new AnalysisBookmarksCommandService(testContext).pullAnalysisBookmarks(ANALYSIS_ID, "USER"); | ||
|
|
||
| expect(markedJson(`studio_analysis_bookmarks_${ANALYSIS_ID}`, `studio_analysis_bookmarks_${ANALYSIS_ID}.json`)).toEqual(bookmarks); | ||
| }); | ||
|
|
||
| it("Should mark the exported package bookmarks under the user-chosen name", async () => { | ||
| const bookmarks = { packageKey: PACKAGE_KEY, entries: [] }; | ||
| mockAxiosGet(`https://myTeam.celonis.cloud/package-manager/api/packages/${PACKAGE_KEY}/bookmarks/export`, bookmarks); | ||
|
|
||
| await new BookmarksCommandService(testContext).exportBookmarks(PACKAGE_KEY, "custom.json"); | ||
|
|
||
| expect(markedJson("custom", "custom.json")).toEqual(bookmarks); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { mockAxiosPut, mockAxiosPutError, mockedAxiosInstance, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; | ||
| import { DataPoolCommandService } from "../../../src/commands/data-pipeline/data-pool/data-pool-command.service"; | ||
| import { testContext } from "../../utls/test-context"; | ||
| import { loggingTestTransport } from "../../jest.setup"; | ||
| import { writeJsonTempFile } from "../../utls/fs-utils"; | ||
|
|
||
| describe("Update data pool", () => { | ||
|
|
||
| const poolId = "pool-1"; | ||
| const updateUrl = `https://myTeam.celonis.cloud/integration/api/pools/${poolId}`; | ||
| const file = "data-pool-update.json"; | ||
|
|
||
| const dataPool = { | ||
| id: poolId, | ||
| name: "Updated Pool", | ||
| objects: [], | ||
| }; | ||
|
|
||
| beforeEach(() => { | ||
| writeJsonTempFile(file, { dataPool }); | ||
| }); | ||
|
|
||
| it("Should call the update API and log success", async () => { | ||
| mockAxiosPut(updateUrl, dataPool); | ||
|
|
||
| await new DataPoolCommandService(testContext).updateDataPool(poolId, file); | ||
|
|
||
| expect(mockedAxiosInstance.put).toHaveBeenCalledWith(updateUrl, expect.anything(), expect.anything()); | ||
| expect(loggingTestTransport.logMessages).toHaveLength(1); | ||
| expect(loggingTestTransport.logMessages[0].message).toContain("Data Pool was updated successfully!"); | ||
| }); | ||
|
|
||
| it("Should send the data pool from the file as request body", async () => { | ||
| mockAxiosPut(updateUrl, dataPool); | ||
|
|
||
| await new DataPoolCommandService(testContext).updateDataPool(poolId, file); | ||
|
|
||
| expect(JSON.parse(mockedPostRequestBodyByUrl.get(updateUrl))).toEqual(dataPool); | ||
| }); | ||
|
|
||
| it("Should log a fatal error and reject when the update API fails", async () => { | ||
| const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never); | ||
| mockAxiosPutError(updateUrl, 500, { message: "Internal Server Error" }); | ||
|
|
||
| await expect(new DataPoolCommandService(testContext).updateDataPool(poolId, file)).rejects.toBeUndefined(); | ||
|
|
||
| expect(exitSpy).toHaveBeenCalledWith(1); | ||
| expect(loggingTestTransport.logMessages[0].message).toContain("Internal Server Error"); | ||
|
|
||
| exitSpy.mockRestore(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import { BaseManager } from "../../../src/core/http/http-shared/base.manager"; | ||
| import { ManagerConfig } from "../../../src/core/http/http-shared/manager-config.interface"; | ||
| import { FatalError } from "../../../src/core/utils/logger"; | ||
| import { loggingTestTransport } from "../../jest.setup"; | ||
| import { mockAxiosGetError, mockAxiosPostError } from "../../utls/http-requests-mock"; | ||
| import { testContext } from "../../utls/test-context"; | ||
|
|
||
| const TEAM_URL = "https://myTeam.celonis.cloud"; | ||
| const PULL_PATH = "/api/test/pull"; | ||
| const PUSH_PATH = "/api/test/push"; | ||
| const FIND_ALL_PATH = "/api/test/find-all"; | ||
| const ERROR_BODY = { message: "Internal Server Error" }; | ||
|
|
||
| class TestManager extends BaseManager { | ||
| public constructor() { | ||
| super(testContext); | ||
| } | ||
|
|
||
| protected getConfig(): ManagerConfig { | ||
| return { | ||
| pullUrl: PULL_PATH, | ||
| pushUrl: PUSH_PATH, | ||
| findAllUrl: FIND_ALL_PATH, | ||
| exportFileName: "test-export.json", | ||
| onPushSuccessMessage: () => "Pushed successfully", | ||
| onFindAll: () => undefined, | ||
| }; | ||
| } | ||
|
|
||
| protected getBody(): object { | ||
| return { key: "test" }; | ||
| } | ||
|
|
||
| protected getSerializedFileContent(data: any): string { | ||
| return JSON.stringify(data); | ||
| } | ||
| } | ||
|
|
||
| describe("BaseManager error handling", () => { | ||
|
|
||
| let manager: TestManager; | ||
| let exitSpy: jest.SpyInstance; | ||
|
|
||
| beforeEach(() => { | ||
| manager = new TestManager(); | ||
| exitSpy = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| exitSpy.mockRestore(); | ||
| }); | ||
|
|
||
| const expectFatalErrorLogged = (): void => { | ||
| expect(exitSpy).toHaveBeenCalledWith(1); | ||
| expect(loggingTestTransport.logMessages[0].message).toContain("Internal Server Error"); | ||
| }; | ||
|
|
||
| it("Should log a fatal error and reject when the pull API fails", async () => { | ||
| mockAxiosGetError(TEAM_URL + PULL_PATH, 500, ERROR_BODY); | ||
|
|
||
| await expect(manager.pull()).rejects.toThrow(FatalError); | ||
|
|
||
| expectFatalErrorLogged(); | ||
| }); | ||
|
|
||
| it("Should log a fatal error and reject when the file download fails", async () => { | ||
| mockAxiosPostError(TEAM_URL + PULL_PATH, 500, ERROR_BODY); | ||
|
|
||
| await expect(manager.pullFile()).rejects.toEqual(JSON.stringify(ERROR_BODY)); | ||
|
|
||
| expectFatalErrorLogged(); | ||
| }); | ||
|
|
||
| it("Should log a fatal error and reject when the push API fails", async () => { | ||
| mockAxiosPostError(TEAM_URL + PUSH_PATH, 500, ERROR_BODY); | ||
|
|
||
| await expect(manager.push()).rejects.toBeUndefined(); | ||
|
|
||
| expectFatalErrorLogged(); | ||
| }); | ||
|
|
||
| it("Should log a fatal error and reject when the find all API fails", async () => { | ||
| mockAxiosGetError(TEAM_URL + FIND_ALL_PATH, 500, ERROR_BODY); | ||
|
|
||
| await expect(manager.findAll()).rejects.toThrow(FatalError); | ||
|
|
||
| expectFatalErrorLogged(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.