diff --git a/.eslintrc.yml b/.eslintrc.yml index 5740c11..82cfa87 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -3,7 +3,7 @@ root: true # https://eslint.org/docs/rules/ env: node: true - es2020: true + es2022: true plugins: - jest extends: diff --git a/src/submodules/capMultitenancy.js b/src/submodules/capMultitenancy.js index 0e3935e..b74c78f 100644 --- a/src/submodules/capMultitenancy.js +++ b/src/submodules/capMultitenancy.js @@ -25,6 +25,14 @@ const CDS_JOB_POLL_FREQUENCY_FALLBACK = 15000; const CDS_CHANGE_TIMEOUT = 30 * 60 * 1000; const CDS_CHANGE_TIMEOUT_TEXT = "30min"; +const JOB_STATUS = Object.freeze({ + QUEUED: "QUEUED", + RUNNING: "RUNNING", + FAILED: "FAILED", + FINISHED: "FINISHED", +}); +const TASK_STATUS = JOB_STATUS; + const writeFileAsync = promisify(writeFile); const cdsRequestConcurrency = parseIntWithFallback(process.env[ENV.CDS_CONCURRENCY], CDS_REQUEST_CONCURRENCY_FALLBACK); const cdsPollFrequency = parseIntWithFallback(process.env[ENV.CDS_FREQUENCY], CDS_JOB_POLL_FREQUENCY_FALLBACK); @@ -142,19 +150,19 @@ const _getTaskSummary = (tasks) => tasks.reduce( (accumulator, { status }) => { switch (status) { - case "QUEUED": { + case TASK_STATUS.QUEUED: { accumulator[0]++; break; } - case "RUNNING": { + case TASK_STATUS.RUNNING: { accumulator[1]++; break; } - case "FAILED": { + case TASK_STATUS.FAILED: { accumulator[2]++; break; } - case "FINISHED": { + case TASK_STATUS.FINISHED: { accumulator[3]++; break; } @@ -228,7 +236,7 @@ const _cdsUpgradeMtxs = async ( lastTimeOfChange = currentTime; } - if (status !== "RUNNING") { + if (status !== JOB_STATUS.RUNNING) { break; } } @@ -243,7 +251,7 @@ const _cdsUpgradeMtxs = async ( const table = [["tenantId", "status", "message"]].concat( upgradeTenantEntries.map(([tenantId, { ID: taskId }]) => { const { status, error } = taskMap[taskId]; - hasError |= !status || error; + hasError ||= !status || error; return [tenantId, status, error || ""]; }) ); @@ -292,7 +300,7 @@ const _cdsUpgradeMtx = async ( assert(status, "no status retrieved for jobId %s", jobId); console.log("job %s is %s", jobId, status); - if (status !== "RUNNING") { + if (status !== JOB_STATUS.RUNNING) { break; } } diff --git a/test/capMultitenancy.test.js b/test/capMultitenancy.test.js new file mode 100644 index 0000000..b319023 --- /dev/null +++ b/test/capMultitenancy.test.js @@ -0,0 +1,205 @@ +"use strict"; + +jest.mock("../src/shared/request", () => ({ + request: jest.fn(), +})); +jest.mock("../src/shared/static", () => { + const staticLib = jest.requireActual("../src/shared/static"); + return { + ...staticLib, + sleep: jest.fn(), + }; +}); + +const mockRequest = require("../src/shared/request"); + +const cds = require("../src/submodules/capMultitenancy"); +const { outputFromLoggerPartitionFetch } = require("./util/static"); + +let loggerSpy = { + info: jest.spyOn(console, "log").mockImplementation(), + error: jest.spyOn(console, "error").mockImplementation(), +}; + +const fakeContext = { + getCdsInfo: () => ({ + cfAppName: "app-mtx-name", + cfAppGuid: "app-mtx-guid", + cfRouteUrl: "route-url", + cfProcess: { instances: 1 }, + }), + getCachedUaaToken: () => "token", + getCachedUaaTokenFromCredentials: () => "token", +}; + +describe("cds tests", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test("cds upgrade request fails", async () => { + mockRequest.request.mockReturnValueOnce({ status: 200 }); + + mockRequest.request.mockReturnValueOnce({ + text: () => + JSON.stringify({ + ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + createdAt: "2024-06-03T12:33:51.877Z", + op: "upgrade", + tenants: { + "00000000-0000-4000-8000-000000000103": { + ID: "aab16377-e8ce-4d30-b50a-f5ff657b6120", + }, + "00000000-0000-4000-8000-000000000102": { + ID: "7bad7b8a-febd-4897-9dfe-82850b552dae", + }, + "00000000-0000-4000-8000-000000000101": { + ID: "5496dbe8-6596-4d57-9b4b-c5ad4f8ba8de", + }, + "5ecc7413-2b7e-414a-9496-ad4a61f6cccf": { + ID: "6fa9f693-590b-446a-ae23-6c3b194cafaa", + }, + "dde70ec5-983d-4848-b50c-fb2cdac7d359": { + ID: "a1f37350-65a3-4e74-96cf-f38886268996", + }, + "6917dfd6-7590-4033-af2a-140b75263b0d": { + ID: "8735e793-ae88-45e5-b38d-c16d9ac435a5", + }, + }, + tasks: { + "00000000-0000-4000-8000-000000000101": { + job_ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + ID: "5496dbe8-6596-4d57-9b4b-c5ad4f8ba8de", + tenant: "00000000-0000-4000-8000-000000000101", + op: "upgrade", + }, + "00000000-0000-4000-8000-000000000102": { + job_ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + ID: "7bad7b8a-febd-4897-9dfe-82850b552dae", + tenant: "00000000-0000-4000-8000-000000000102", + op: "upgrade", + }, + "00000000-0000-4000-8000-000000000103": { + job_ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + ID: "aab16377-e8ce-4d30-b50a-f5ff657b6120", + tenant: "00000000-0000-4000-8000-000000000103", + op: "upgrade", + }, + "5ecc7413-2b7e-414a-9496-ad4a61f6cccf": { + job_ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + ID: "6fa9f693-590b-446a-ae23-6c3b194cafaa", + tenant: "5ecc7413-2b7e-414a-9496-ad4a61f6cccf", + op: "upgrade", + }, + "6917dfd6-7590-4033-af2a-140b75263b0d": { + job_ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + ID: "8735e793-ae88-45e5-b38d-c16d9ac435a5", + tenant: "6917dfd6-7590-4033-af2a-140b75263b0d", + op: "upgrade", + }, + "dde70ec5-983d-4848-b50c-fb2cdac7d359": { + job_ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + ID: "a1f37350-65a3-4e74-96cf-f38886268996", + tenant: "dde70ec5-983d-4848-b50c-fb2cdac7d359", + op: "upgrade", + }, + }, + }), + }); + + mockRequest.request.mockReturnValueOnce({ + text: () => + JSON.stringify({ + ID: "8fd6894a-91d6-4eed-b772-1be05b8ac6ed", + op: "upgrade", + error: null, + status: "FAILED", + tasks: [ + { + ID: "5496dbe8-6596-4d57-9b4b-c5ad4f8ba8de", + status: "FAILED", + tenant: "00000000-0000-4000-8000-000000000101", + error: "HDI deployment failed with exit code 1", + }, + { + ID: "7bad7b8a-febd-4897-9dfe-82850b552dae", + status: "FAILED", + tenant: "00000000-0000-4000-8000-000000000102", + error: "HDI deployment failed with exit code 1", + }, + { + ID: "aab16377-e8ce-4d30-b50a-f5ff657b6120", + status: "FAILED", + tenant: "00000000-0000-4000-8000-000000000103", + error: "HDI deployment failed with exit code 1", + }, + { + ID: "6fa9f693-590b-446a-ae23-6c3b194cafaa", + status: "FAILED", + tenant: "5ecc7413-2b7e-414a-9496-ad4a61f6cccf", + error: "HDI deployment failed with exit code 1", + }, + { + ID: "8735e793-ae88-45e5-b38d-c16d9ac435a5", + status: "FAILED", + tenant: "6917dfd6-7590-4033-af2a-140b75263b0d", + error: "HDI deployment failed with exit code 1", + }, + { + ID: "a1f37350-65a3-4e74-96cf-f38886268996", + status: "FAILED", + tenant: "dde70ec5-983d-4848-b50c-fb2cdac7d359", + error: "HDI deployment failed with exit code 1", + }, + ], + tenants: { + "00000000-0000-4000-8000-000000000101": { + status: "FAILED", + error: "HDI deployment failed with exit code 1", + }, + "00000000-0000-4000-8000-000000000102": { + status: "FAILED", + error: "HDI deployment failed with exit code 1", + }, + "00000000-0000-4000-8000-000000000103": { + status: "FAILED", + error: "HDI deployment failed with exit code 1", + }, + "5ecc7413-2b7e-414a-9496-ad4a61f6cccf": { + status: "FAILED", + error: "HDI deployment failed with exit code 1", + }, + "6917dfd6-7590-4033-af2a-140b75263b0d": { + status: "FAILED", + error: "HDI deployment failed with exit code 1", + }, + "dde70ec5-983d-4848-b50c-fb2cdac7d359": { + status: "FAILED", + error: "HDI deployment failed with exit code 1", + }, + }, + }), + }); + + await expect(cds.cdsUpgradeAll(fakeContext, [], [])).rejects.toMatchInlineSnapshot( + `[Error: error happened during tenant upgrade]` + ); + + expect(outputFromLoggerPartitionFetch(loggerSpy.info.mock.calls)).toMatchInlineSnapshot(` + "using cds-mtxs apis + started upgrade on server with jobId 8fd6894a-91d6-4eed-b772-1be05b8ac6ed polling interval 15sec + job 8fd6894a-91d6-4eed-b772-1be05b8ac6ed is FAILED with tasks queued/running: 0/0 | failed/finished: 6/0 + # tenantId status message + 1 00000000-0000-4000-8000-000000000101 FAILED HDI deployment failed with exit code 1 + 2 00000000-0000-4000-8000-000000000102 FAILED HDI deployment failed with exit code 1 + 3 00000000-0000-4000-8000-000000000103 FAILED HDI deployment failed with exit code 1 + 4 5ecc7413-2b7e-414a-9496-ad4a61f6cccf FAILED HDI deployment failed with exit code 1 + 5 6917dfd6-7590-4033-af2a-140b75263b0d FAILED HDI deployment failed with exit code 1 + 6 dde70ec5-983d-4848-b50c-fb2cdac7d359 FAILED HDI deployment failed with exit code 1 + + " + `); + + expect(loggerSpy.error).toHaveBeenCalledTimes(0); + }); +}); diff --git a/test/tenantRegistry.test.js b/test/tenantRegistry.test.js index ee50950..c509104 100644 --- a/test/tenantRegistry.test.js +++ b/test/tenantRegistry.test.js @@ -7,7 +7,6 @@ jest.mock("../src/shared/static", () => { const staticLib = jest.requireActual("../src/shared/static"); return { ...staticLib, - // sleep: jest.fn(async () => new Promise((resolve) => setImmediate(resolve))), sleep: jest.fn(), }; });