diff --git a/lib/Utils/CommonUtils.js b/lib/Utils/CommonUtils.js index 6989e084..2f3c12d7 100644 --- a/lib/Utils/CommonUtils.js +++ b/lib/Utils/CommonUtils.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -32,7 +42,44 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getAllFileErrors = exports.validateTestRunParamsFromPipeline = exports.getDefaultRunDescription = exports.getDefaultTestRunName = exports.getDefaultTestName = exports.inValidEngineInstances = exports.invalidName = exports.validateUrlcert = exports.validateUrl = exports.validateOutputParametervariableName = exports.validateOverRideParameters = exports.validateAndGetSegregatedManagedIdentities = exports.validateAutoStop = exports.getSubscriptionIdFromResourceId = exports.getResourceGroupFromResourceId = exports.getResourceNameFromResourceId = exports.getResourceTypeFromResourceId = exports.invalidDescription = exports.invalidDisplayName = exports.getFileName = exports.getResultObj = exports.isStatusFailed = exports.isTerminalFileStatusSucceeded = exports.isTerminalFileStatus = exports.isTerminalTestStatus = exports.removeUnits = exports.indexOfFirstDigit = exports.getReportFolder = exports.getResultFolder = exports.getUniqueId = exports.sleep = exports.printClientMetrics = exports.errorCorrection = exports.printCriteria = exports.printTestDuration = exports.checkFileTypes = exports.checkFileType = void 0; +exports.checkFileType = checkFileType; +exports.checkFileTypes = checkFileTypes; +exports.printTestDuration = printTestDuration; +exports.printCriteria = printCriteria; +exports.errorCorrection = errorCorrection; +exports.printClientMetrics = printClientMetrics; +exports.sleep = sleep; +exports.getUniqueId = getUniqueId; +exports.getResultFolder = getResultFolder; +exports.getReportFolder = getReportFolder; +exports.indexOfFirstDigit = indexOfFirstDigit; +exports.removeUnits = removeUnits; +exports.isTerminalTestStatus = isTerminalTestStatus; +exports.isTerminalFileStatus = isTerminalFileStatus; +exports.isTerminalFileStatusSucceeded = isTerminalFileStatusSucceeded; +exports.isStatusFailed = isStatusFailed; +exports.getResultObj = getResultObj; +exports.getFileName = getFileName; +exports.invalidDisplayName = invalidDisplayName; +exports.invalidDescription = invalidDescription; +exports.getResourceTypeFromResourceId = getResourceTypeFromResourceId; +exports.getResourceNameFromResourceId = getResourceNameFromResourceId; +exports.getResourceGroupFromResourceId = getResourceGroupFromResourceId; +exports.getSubscriptionIdFromResourceId = getSubscriptionIdFromResourceId; +exports.validateAutoStop = validateAutoStop; +exports.validateAndGetSegregatedManagedIdentities = validateAndGetSegregatedManagedIdentities; +exports.validateOverRideParameters = validateOverRideParameters; +exports.validateOutputParametervariableName = validateOutputParametervariableName; +exports.validateUrl = validateUrl; +exports.validateUrlcert = validateUrlcert; +exports.invalidName = invalidName; +exports.inValidEngineInstances = inValidEngineInstances; +exports.getDefaultTestName = getDefaultTestName; +exports.getDefaultTestRunName = getDefaultTestRunName; +exports.getDefaultRunDescription = getDefaultRunDescription; +exports.validateTestRunParamsFromPipeline = validateTestRunParamsFromPipeline; +exports.getAllFileErrors = getAllFileErrors; +exports.sanitisePipelineNameHeader = sanitisePipelineNameHeader; const { v4: uuidv4 } = require('uuid'); const util_1 = require("util"); const GeneralConstants_1 = require("../Constants/GeneralConstants"); @@ -47,7 +94,6 @@ function checkFileType(filePath, fileExtToValidate) { let split = filePath.split('.'); return split[split.length - 1].toLowerCase() == fileExtToValidate.toLowerCase(); } -exports.checkFileType = checkFileType; function checkFileTypes(filePath, fileExtsToValidate) { var _a; if ((0, util_1.isNullOrUndefined)(filePath)) { @@ -57,7 +103,6 @@ function checkFileTypes(filePath, fileExtsToValidate) { let fileExtsToValidateLower = fileExtsToValidate.map(ext => ext.toLowerCase()); return fileExtsToValidateLower.includes((_a = split[split.length - 1]) === null || _a === void 0 ? void 0 : _a.toLowerCase()); } -exports.checkFileTypes = checkFileTypes; function printTestDuration(testRunObj) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; @@ -70,7 +115,6 @@ function printTestDuration(testRunObj) { return; }); } -exports.printTestDuration = printTestDuration; function printCriteria(criteria) { if (Object.keys(criteria).length == 0) return; @@ -99,11 +143,9 @@ function printCriteria(criteria) { } console.log("\n"); } -exports.printCriteria = printCriteria; function errorCorrection(result) { return "Unable to fetch the response. Please re-run or contact support if the issue persists. " + "Status code :" + result.message.statusCode; } -exports.errorCorrection = errorCorrection; function printTestResult(criteria) { var _a, _b; let pass = 0; @@ -142,7 +184,6 @@ function printClientMetrics(obj) { } }); } -exports.printClientMetrics = printClientMetrics; function getAbsVal(data) { if ((0, util_1.isNullOrUndefined)(data)) { return "undefined"; @@ -156,46 +197,39 @@ function sleep(ms) { setTimeout(resolve, ms); }); } -exports.sleep = sleep; function getUniqueId() { return uuidv4(); } -exports.getUniqueId = getUniqueId; function getResultFolder(testArtifacts) { if ((0, util_1.isNullOrUndefined)(testArtifacts) || (0, util_1.isNullOrUndefined)(testArtifacts.outputArtifacts)) return null; var outputurl = testArtifacts.outputArtifacts; return !(0, util_1.isNullOrUndefined)(outputurl.resultFileInfo) ? outputurl.resultFileInfo.url : null; } -exports.getResultFolder = getResultFolder; function getReportFolder(testArtifacts) { if ((0, util_1.isNullOrUndefined)(testArtifacts) || (0, util_1.isNullOrUndefined)(testArtifacts.outputArtifacts)) return null; var outputurl = testArtifacts.outputArtifacts; return !(0, util_1.isNullOrUndefined)(outputurl.reportFileInfo) ? outputurl.reportFileInfo.url : null; } -exports.getReportFolder = getReportFolder; function indexOfFirstDigit(input) { let i = 0; for (; input[i] < '0' || input[i] > '9'; i++) ; return i == input.length ? -1 : i; } -exports.indexOfFirstDigit = indexOfFirstDigit; function removeUnits(input) { let i = 0; for (; input[i] >= '0' && input[i] <= '9'; i++) ; return i == input.length ? input : input.substring(0, i); } -exports.removeUnits = removeUnits; function isTerminalTestStatus(testStatus) { if (testStatus == "DONE" || testStatus === "FAILED" || testStatus === "CANCELLED") { return true; } return false; } -exports.isTerminalTestStatus = isTerminalTestStatus; function isTerminalFileStatus(fileStatus) { let fileStatusEnum = fileStatus; if (fileStatusEnum == PayloadModels_1.FileStatus.VALIDATION_INITIATED) { @@ -203,7 +237,6 @@ function isTerminalFileStatus(fileStatus) { } return true; } -exports.isTerminalFileStatus = isTerminalFileStatus; function isTerminalFileStatusSucceeded(fileStatus) { let fileStatusEnum = fileStatus; if ((0, util_1.isNullOrUndefined)(fileStatusEnum) || fileStatusEnum == PayloadModels_1.FileStatus.VALIDATION_SUCCESS || fileStatusEnum == PayloadModels_1.FileStatus.NOT_VALIDATED) { @@ -211,14 +244,12 @@ function isTerminalFileStatusSucceeded(fileStatus) { } return false; } -exports.isTerminalFileStatusSucceeded = isTerminalFileStatusSucceeded; function isStatusFailed(testStatus) { if (testStatus === "FAILED" || testStatus === "CANCELLED") { return true; } return false; } -exports.isStatusFailed = isStatusFailed; function getResultObj(data) { return __awaiter(this, void 0, void 0, function* () { let dataString; @@ -233,40 +264,32 @@ function getResultObj(data) { } }); } -exports.getResultObj = getResultObj; function getFileName(filepath) { const filename = path.basename(filepath); return filename; } -exports.getFileName = getFileName; function invalidDisplayName(value) { if (value.length < 2 || value.length > 50) return true; return false; } -exports.invalidDisplayName = invalidDisplayName; function invalidDescription(value) { if (value.length > 100) return true; return false; } -exports.invalidDescription = invalidDescription; function getResourceTypeFromResourceId(resourceId) { return resourceId && resourceId.split("/").length > 7 ? resourceId.split("/")[6] + "/" + resourceId.split("/")[7] : null; } -exports.getResourceTypeFromResourceId = getResourceTypeFromResourceId; function getResourceNameFromResourceId(resourceId) { return resourceId && resourceId.split("/").length > 8 ? resourceId.split("/")[8] : null; } -exports.getResourceNameFromResourceId = getResourceNameFromResourceId; function getResourceGroupFromResourceId(resourceId) { return resourceId && resourceId.split("/").length > 4 ? resourceId.split("/")[4] : null; } -exports.getResourceGroupFromResourceId = getResourceGroupFromResourceId; function getSubscriptionIdFromResourceId(resourceId) { return resourceId && resourceId.split("/").length > 2 ? resourceId.split("/")[2] : null; } -exports.getSubscriptionIdFromResourceId = getSubscriptionIdFromResourceId; function validateAutoStop(autoStop, isPipelineParam = false) { if (typeof autoStop != 'string') { if ((0, util_1.isNullOrUndefined)(autoStop.errorPercentage) || isNaN(autoStop.errorPercentage) || autoStop.errorPercentage > 100 || autoStop.errorPercentage < 0) { @@ -290,7 +313,6 @@ function validateAutoStop(autoStop, isPipelineParam = false) { } return { valid: true, error: "" }; } -exports.validateAutoStop = validateAutoStop; function validateAndGetSegregatedManagedIdentities(referenceIdentities, keyVaultGivenOutOfReferenceIdentities = false) { let referenceIdentityValuesUAMIMap = { [UtilModels_1.ReferenceIdentityKinds.KeyVault]: [], @@ -337,7 +359,6 @@ function validateAndGetSegregatedManagedIdentities(referenceIdentities, keyVault } return { referenceIdentityValuesUAMIMap, referenceIdentiesSystemAssignedCount }; } -exports.validateAndGetSegregatedManagedIdentities = validateAndGetSegregatedManagedIdentities; function validateOverRideParameters(overRideParams) { try { if (!(0, util_1.isNullOrUndefined)(overRideParams)) { @@ -393,64 +414,54 @@ function validateOverRideParameters(overRideParams) { } return { valid: true, error: "" }; } -exports.validateOverRideParameters = validateOverRideParameters; function validateOutputParametervariableName(outputVarName) { if ((0, util_1.isNullOrUndefined)(outputVarName) || typeof outputVarName != 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(outputVarName)) { return { valid: false, error: `Invalid output variable name '${outputVarName}'. Use only letters, numbers, and underscores.` }; } return { valid: true, error: "" }; } -exports.validateOutputParametervariableName = validateOutputParametervariableName; function validateUrl(url) { var r = new RegExp(/(http|https):\/\/.*\/secrets\/.+$/); return r.test(url); } -exports.validateUrl = validateUrl; function validateUrlcert(url) { var r = new RegExp(/(http|https):\/\/.*\/certificates\/.+$/); return r.test(url); } -exports.validateUrlcert = validateUrlcert; function invalidName(value) { if (value.length < 2 || value.length > 50) return true; var r = new RegExp(/[^a-z0-9_-]+/); return r.test(value); } -exports.invalidName = invalidName; function inValidEngineInstances(engines) { if (engines > 400 || engines < 1) { return true; } return false; } -exports.inValidEngineInstances = inValidEngineInstances; function getDefaultTestName() { const a = (new Date(Date.now())).toLocaleString(); const b = a.split(", "); const c = a.split(" "); return "Test_" + b[0] + "_" + c[1] + c[2]; } -exports.getDefaultTestName = getDefaultTestName; function getDefaultTestRunName() { const a = (new Date(Date.now())).toLocaleString(); const b = a.split(", "); const c = a.split(" "); return "TestRun_" + b[0] + "_" + c[1] + c[2]; } -exports.getDefaultTestRunName = getDefaultTestRunName; function getDefaultRunDescription() { const pipelineName = process.env.GITHUB_WORKFLOW || "Unknown Pipeline"; return "Started using GH workflows" + (pipelineName ? "-" + pipelineName : ""); } -exports.getDefaultRunDescription = getDefaultRunDescription; function validateTestRunParamsFromPipeline(runTimeParams) { if (runTimeParams.runDisplayName && invalidDisplayName(runTimeParams.runDisplayName)) throw new Error("Invalid test run name. Test run name must be between 2 to 50 characters."); if (runTimeParams.runDescription && invalidDescription(runTimeParams.runDescription)) throw new Error("Invalid test run description. Test run description must be less than 100 characters."); } -exports.validateTestRunParamsFromPipeline = validateTestRunParamsFromPipeline; function getAllFileErrors(testObj) { var _a, _b, _c, _d, _e, _f; let allArtifacts = []; @@ -474,4 +485,26 @@ function getAllFileErrors(testObj) { } return fileErrors; } -exports.getAllFileErrors = getAllFileErrors; +/** + * This function returns the string with only ascii charaters, removing the non-ascii characters. + * @param pipelineName - original pipeline name + * @returns sanitised pipeline name with only ascii characters + */ +function sanitisePipelineNameHeader(pipelineName) { + if (!pipelineName) { + return pipelineName; + } + let result = ""; + for (const ch of pipelineName) { + const code = ch.codePointAt(0); + const allowed = (code >= 32 && code <= 126); // ASCII characters range, the only allowed characters in headers. + if (allowed) { + result += ch; + } + } + result = result.trim(); + if (result.length == 0) { + result = "-"; // this is what GH does when i try to give all non-ascii characters in the repo name. + } + return result; +} diff --git a/lib/Utils/FetchUtils.js b/lib/Utils/FetchUtils.js index b16ea1e4..153476f2 100644 --- a/lib/Utils/FetchUtils.js +++ b/lib/Utils/FetchUtils.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -32,7 +42,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.httpClientRetries = void 0; +exports.httpClientRetries = httpClientRetries; const CommonUtils_1 = require("./CommonUtils"); const UtilModels_1 = require("./../models/UtilModels"); const httpc = __importStar(require("typed-rest-client/HttpClient")); @@ -47,8 +57,8 @@ const methodEnumToString = { [UtilModels_1.FetchCallType.patch]: "patch" }; // (note mohit): shift to the enum later. -function httpClientRetries(urlSuffix, header, method, retries = 1, data, isUploadCall = true, log = true) { - return __awaiter(this, void 0, void 0, function* () { +function httpClientRetries(urlSuffix_1, header_1, method_1) { + return __awaiter(this, arguments, void 0, function* (urlSuffix, header, method, retries = 1, data, isUploadCall = true, log = true) { let httpResponse; const retrriableCodes = [408, 429, 500, 502, 503, 504]; // 408 - Request Timeout, 429 - Too Many Requests, 500 - Internal Server Error, 502 - Bad Gateway, 503 - Service Unavailable, 504 - Gateway Timeout let backOffTimeForRetry = 5; // seconds @@ -74,7 +84,7 @@ function httpClientRetries(urlSuffix, header, method, retries = 1, data, isUploa const runId = process.env.GITHUB_RUN_ID; const pipelineName = process.env.GITHUB_WORKFLOW || "Unknown Pipeline"; const pipelineUri = `${githubBaseUrl}/${repository}/actions/runs/${runId}`; - header['x-ms-pipeline-name'] = pipelineName; // setting these for patch calls. + header['x-ms-pipeline-name'] = (0, CommonUtils_1.sanitisePipelineNameHeader)(pipelineName); // setting these for patch calls. header['x-ms-pipeline-uri'] = pipelineUri; httpResponse = yield httpClient.request(methodEnumToString[method], urlSuffix, data, header); } @@ -106,4 +116,3 @@ function httpClientRetries(urlSuffix, header, method, retries = 1, data, isUploa } }); } -exports.httpClientRetries = httpClientRetries; diff --git a/src/Utils/CommonUtils.ts b/src/Utils/CommonUtils.ts index 377d0076..68f2d5eb 100644 --- a/src/Utils/CommonUtils.ts +++ b/src/Utils/CommonUtils.ts @@ -444,3 +444,27 @@ export function getAllFileErrors(testObj:TestModel | null): { [key: string]: str return fileErrors; } + +/** + * This function returns the string with only ascii charaters, removing the non-ascii characters. + * @param pipelineName - original pipeline name + * @returns sanitised pipeline name with only ascii characters + */ +export function sanitisePipelineNameHeader(pipelineName: string | null): string | null { + if(!pipelineName) { + return pipelineName; + } + let result = ""; + for (const ch of pipelineName) { + const code = ch.codePointAt(0)!; + const allowed = (code >= 32 && code <= 126); // ASCII characters range, the only allowed characters in headers. + if(allowed) { + result += ch; + } + } + result = result.trim(); + if(result.length == 0) { + result = "-"; // this is what GH does when i try to give all non-ascii characters in the repo name. + } + return result; +} \ No newline at end of file diff --git a/src/Utils/FetchUtils.ts b/src/Utils/FetchUtils.ts index 41501724..9ed506b1 100644 --- a/src/Utils/FetchUtils.ts +++ b/src/Utils/FetchUtils.ts @@ -1,5 +1,5 @@ import { IHeaders, IHttpClientResponse } from 'typed-rest-client/Interfaces'; -import { errorCorrection, getResultObj, getUniqueId, sleep } from './CommonUtils'; +import { errorCorrection, getResultObj, getUniqueId, sanitisePipelineNameHeader, sleep } from './CommonUtils'; import { FetchCallType, correlationHeader } from './../models/UtilModels'; import * as httpc from 'typed-rest-client/HttpClient'; import { uploadFileData } from './FileUtils'; @@ -39,7 +39,7 @@ export async function httpClientRetries(urlSuffix : string, header : IHeaders, m const pipelineName = process.env.GITHUB_WORKFLOW || "Unknown Pipeline"; const pipelineUri = `${githubBaseUrl}/${repository}/actions/runs/${runId}`; - header['x-ms-pipeline-name'] = pipelineName; // setting these for patch calls. + header['x-ms-pipeline-name'] = sanitisePipelineNameHeader(pipelineName); // setting these for patch calls. header['x-ms-pipeline-uri'] = pipelineUri; httpResponse = await httpClient.request(methodEnumToString[method], urlSuffix, data, header); } diff --git a/test/CommonUtils.test.ts b/test/CommonUtils.test.ts new file mode 100644 index 00000000..e9f7bde2 --- /dev/null +++ b/test/CommonUtils.test.ts @@ -0,0 +1,48 @@ +import { sanitisePipelineNameHeader } from "../src/Utils/CommonUtils"; +describe("CommonUtils tests", () => { + it.each([ + { + input: "Pipeline@2025#Release$!", + expected: "Pipeline@2025#Release$!" + }, + { + input: "Build_Definition-01 (Test) ", + expected: "Build_Definition-01 (Test)" + }, + { + input: "Normal Name", + expected: "Normal Name" + }, + { + input: "Special*&^%$#@!Characters", + expected: "Special*&^%$#@!Characters" + }, + { + input: "", + expected: "" + }, + { + input: " ", + expected: "-" + }, + { + input: "Name_with_underscores_and-dashes", + expected: "Name_with_underscores_and-dashes" + }, + { + input: null, + expected: null + }, + { + input: "🚀 Deploy", + expected: "Deploy" + }, + { + input: "流水线-test-𰻞", + expected: "-test-" + } + ])("sanitisePipelineNameHeader removes special characters", ({ input, expected }) => { + const result = sanitisePipelineNameHeader(input); + expect(result).toBe(expected); + }); +}); \ No newline at end of file