Skip to content

Commit 50dcd8f

Browse files
committed
[Test-Fragments] Add NOT_VALIDATED in file validation status terminal statuses (#105)
* Add NOT_VALIDATED in file validation status terminal statuses * Compiled files
1 parent 3e6fb15 commit 50dcd8f

File tree

15 files changed

+447
-20
lines changed

15 files changed

+447
-20
lines changed

lib/constants.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3-
exports.defaultYaml = void 0;
3+
exports.APIRoute = exports.testmanagerApiVersion = exports.defaultYaml = void 0;
44
exports.defaultYaml = {
55
version: 'v0.1',
66
testId: 'SampleTest',
@@ -48,3 +48,13 @@ exports.defaultYaml = {
4848
}
4949
]
5050
};
51+
exports.testmanagerApiVersion = "2024-07-01-preview";
52+
var BaseAPIRoute;
53+
(function (BaseAPIRoute) {
54+
BaseAPIRoute.featureFlag = "featureFlags";
55+
})(BaseAPIRoute || (BaseAPIRoute = {}));
56+
var APIRoute;
57+
(function (APIRoute) {
58+
const latestVersion = "api-version=" + exports.testmanagerApiVersion;
59+
APIRoute.FeatureFlags = (flag) => `${BaseAPIRoute.featureFlag}/${flag}?${latestVersion}`;
60+
})(APIRoute || (exports.APIRoute = APIRoute = {}));

lib/main.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const util = __importStar(require("./util"));
3939
const TestKind_1 = require("./engine/TestKind");
4040
const fs = __importStar(require("fs"));
4141
const util_1 = require("util");
42+
const FeatureFlagService_1 = require("./services/FeatureFlagService");
43+
const FeatureFlags_1 = require("./services/FeatureFlags");
4244
const resultFolder = 'loadTest';
4345
const reportZipFileName = 'report.zip';
4446
const resultZipFileName = 'results.zip';
@@ -74,8 +76,8 @@ function run() {
7476
}
7577
});
7678
}
77-
function getTestAPI(validate) {
78-
return __awaiter(this, void 0, void 0, function* () {
79+
function getTestAPI(validate_1) {
80+
return __awaiter(this, arguments, void 0, function* (validate, returnTestObj = false) {
7981
var _a, _b;
8082
var urlSuffix = "tests/" + testId + "?api-version=" + util.apiConstants.latestVersion;
8183
urlSuffix = baseURL + urlSuffix;
@@ -113,6 +115,9 @@ function getTestAPI(validate) {
113115
}
114116
var inputScriptFileInfo = testObj.kind == TestKind_1.TestKind.URL ? testObj.inputArtifacts.urlTestConfigFileInfo : testObj.inputArtifacts.testScriptFileInfo;
115117
if (validate) {
118+
if (returnTestObj) {
119+
return [inputScriptFileInfo.validationStatus, testObj];
120+
}
116121
return inputScriptFileInfo.validationStatus;
117122
}
118123
else {
@@ -223,9 +228,10 @@ function uploadTestPlan() {
223228
var startTime = new Date();
224229
var maxAllowedTime = new Date(startTime.getTime() + minutesToAdd * 60000);
225230
var validationStatus = "VALIDATION_INITIATED";
231+
var testObj;
226232
while (maxAllowedTime > (new Date()) && (validationStatus == "VALIDATION_INITIATED" || validationStatus == "NOT_VALIDATED" || validationStatus == null)) {
227233
try {
228-
validationStatus = yield getTestAPI(true);
234+
[validationStatus, testObj] = yield getTestAPI(true, true);
229235
}
230236
catch (e) {
231237
retry--;
@@ -235,8 +241,18 @@ function uploadTestPlan() {
235241
}
236242
yield util.sleep(5000);
237243
}
244+
console.log("Validation status of the test plan: " + validationStatus);
238245
if (validationStatus == null || validationStatus == "VALIDATION_SUCCESS") {
239246
console.log(`Validated test plan for the test successfully.`);
247+
// Get errors from all files
248+
var fileErrors = util.getAllFileErrors(testObj);
249+
if (Object.keys(fileErrors).length > 0) {
250+
console.log("Validation failed for the following files:");
251+
for (const [file, error] of Object.entries(fileErrors)) {
252+
console.log(`File: ${file}, Error: ${error}`);
253+
}
254+
throw new Error("Validation of one or more files failed. Please correct the errors and try again.");
255+
}
240256
yield createTestRun();
241257
}
242258
else if (validationStatus == "VALIDATION_INITIATED" || validationStatus == "NOT_VALIDATED")
@@ -252,7 +268,7 @@ function uploadConfigFile() {
252268
if (configFiles != undefined && configFiles.length > 0) {
253269
for (let filepath of configFiles) {
254270
let filename = map.getFileName(filepath);
255-
var urlSuffix = "tests/" + testId + "/files/" + filename + "?api-version=" + util.apiConstants.latestVersion;
271+
var urlSuffix = "tests/" + testId + "/files/" + filename + "?api-version=" + util.apiConstants.latestVersion + ("&fileType=" + FileType.ADDITIONAL_ARTIFACTS);
256272
urlSuffix = baseURL + urlSuffix;
257273
let headers = yield map.UploadAndValidateHeader();
258274
let uploadresult = yield util.httpClientRetries(urlSuffix, headers, 'put', 3, filepath, true);
@@ -291,6 +307,13 @@ function uploadZipArtifacts() {
291307
let flagValidationPending = true;
292308
let zipInvalid = false;
293309
let zipFailureReason = "";
310+
// TODO(harshanb): Remove this check once the feature flag is enabled by default.
311+
let isTestScriptFragmentEnabled = yield FeatureFlagService_1.FeatureFlagService.isFeatureEnabledAsync(FeatureFlags_1.FeatureFlags.enableTestScriptFragments, baseURL);
312+
let zipValidationTerminateStates = ["VALIDATION_SUCCESS", "VALIDATION_FAILURE"];
313+
if (isTestScriptFragmentEnabled) {
314+
// NOT_VALIDATED is a terminal state for the file validation and actual validation will be performed during test script upload
315+
zipValidationTerminateStates.push("NOT_VALIDATED");
316+
}
294317
while (maxAllowedTime > (new Date()) && flagValidationPending) {
295318
var urlSuffix = "tests/" + testId + "?api-version=" + util.apiConstants.latestVersion;
296319
urlSuffix = baseURL + urlSuffix;
@@ -304,7 +327,7 @@ function uploadZipArtifacts() {
304327
flagValidationPending = false;
305328
if (testObj && testObj.inputArtifacts && testObj.inputArtifacts.additionalFileInfo) {
306329
for (const file of testObj.inputArtifacts.additionalFileInfo) {
307-
if (file.fileType == FileType.ZIPPED_ARTIFACTS && (file.validationStatus != "VALIDATION_SUCCESS" && file.validationStatus != "VALIDATION_FAILURE")) {
330+
if (file.fileType == FileType.ZIPPED_ARTIFACTS && (file.validationStatus in zipValidationTerminateStates)) {
308331
flagValidationPending = true;
309332
break;
310333
}
@@ -329,7 +352,12 @@ function uploadZipArtifacts() {
329352
else if (flagValidationPending) {
330353
throw new Error("Validation of one or more zip artifacts timed out. Please retry.");
331354
}
332-
console.log(`Uploaded and validated ${zipFiles.length} zip artifact(s) for the test successfully.`);
355+
if (isTestScriptFragmentEnabled) {
356+
console.log(`Uploaded ${zipFiles.length} zip artifact(s) for the test successfully.`);
357+
}
358+
else {
359+
console.log(`Uploaded and validated ${zipFiles.length} zip artifact(s) for the test successfully.`);
360+
}
333361
}
334362
var statuscode = yield uploadPropertyFile();
335363
if (statuscode == 201) {

lib/mappers.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
3232
});
3333
};
3434
Object.defineProperty(exports, "__esModule", { value: true });
35-
exports.getARMEndpoint = exports.getTenantId = exports.getFileName = exports.getTestId = exports.getZipFiles = exports.getConfigFiles = exports.getPropertyFile = exports.getTestFile = exports.getYamlPath = exports.getTestKind = exports.getDefaultTestRunName = exports.getDefaultTestName = exports.getSubName = exports.getInputParams = exports.getResourceId = exports.getTestHeader = exports.getTestRunHeader = exports.startTestData = exports.armTokenHeader = exports.UploadAndValidateHeader = exports.createTestHeader = exports.createTestData = void 0;
35+
exports.getARMEndpoint = exports.getTenantId = exports.getFileName = exports.getTestId = exports.getZipFiles = exports.getConfigFiles = exports.getPropertyFile = exports.getTestFile = exports.getYamlPath = exports.getTestKind = exports.getDefaultTestRunName = exports.getDefaultTestName = exports.getSubName = exports.getInputParams = exports.getResourceId = exports.getTestHeader = exports.getTestRunHeader = exports.startTestData = exports.armTokenHeader = exports.UploadAndValidateHeader = exports.createTestHeader = exports.createTestData = exports.getToken = void 0;
3636
const core = __importStar(require("@actions/core"));
3737
const yaml = require("js-yaml");
3838
const jwt_decode = __importStar(require("jwt-decode"));
@@ -100,6 +100,10 @@ function getExistingData() {
100100
envYaml[key] = null;
101101
}
102102
}
103+
function getToken() {
104+
return token;
105+
}
106+
exports.getToken = getToken;
103107
function createTestData() {
104108
getExistingData();
105109
var data = {

lib/models/APIResponseModel.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });

lib/services/FeatureFlagService.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"use strict";
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
var desc = Object.getOwnPropertyDescriptor(m, k);
5+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6+
desc = { enumerable: true, get: function() { return m[k]; } };
7+
}
8+
Object.defineProperty(o, k2, desc);
9+
}) : (function(o, m, k, k2) {
10+
if (k2 === undefined) k2 = k;
11+
o[k2] = m[k];
12+
}));
13+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14+
Object.defineProperty(o, "default", { enumerable: true, value: v });
15+
}) : function(o, v) {
16+
o["default"] = v;
17+
});
18+
var __importStar = (this && this.__importStar) || function (mod) {
19+
if (mod && mod.__esModule) return mod;
20+
var result = {};
21+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22+
__setModuleDefault(result, mod);
23+
return result;
24+
};
25+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27+
return new (P || (P = Promise))(function (resolve, reject) {
28+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31+
step((generator = generator.apply(thisArg, _arguments || [])).next());
32+
});
33+
};
34+
Object.defineProperty(exports, "__esModule", { value: true });
35+
exports.FeatureFlagService = void 0;
36+
const constants_1 = require("../constants");
37+
const map = __importStar(require("../mappers"));
38+
const util = __importStar(require("../util"));
39+
class FeatureFlagService {
40+
static getFeatureFlagAsync(flag_1, baseUrl_1) {
41+
return __awaiter(this, arguments, void 0, function* (flag, baseUrl, useCache = true) {
42+
if (useCache && flag in this.featureFlagCache) {
43+
return { featureFlag: flag, enabled: this.featureFlagCache[flag.toString()] };
44+
}
45+
let uri = baseUrl + constants_1.APIRoute.FeatureFlags(flag.toString());
46+
let headers = {
47+
'content-type': 'application/json',
48+
'Authorization': 'Bearer ' + map.getToken()
49+
};
50+
let flagResponse = yield util.httpClientRetries(uri, headers, 'get', 3, "", false, false);
51+
try {
52+
let flagObj = (yield util.getResultObj(flagResponse));
53+
this.featureFlagCache[flag.toString()] = flagObj.enabled;
54+
return flagObj;
55+
}
56+
catch (error) {
57+
// remove item from dict
58+
// handle in case getFlag was called with cache true once and then with cache false, and failed during second call
59+
// remove the item from cache so that it can be fetched again rather than using old value
60+
delete this.featureFlagCache[flag.toString()];
61+
return null;
62+
}
63+
});
64+
}
65+
static isFeatureEnabledAsync(flag_1, baseUrl_1) {
66+
return __awaiter(this, arguments, void 0, function* (flag, baseUrl, useCache = true) {
67+
let flagObj = yield this.getFeatureFlagAsync(flag, baseUrl, useCache);
68+
return flagObj ? flagObj.enabled : false;
69+
});
70+
}
71+
}
72+
exports.FeatureFlagService = FeatureFlagService;
73+
FeatureFlagService.featureFlagCache = {};

lib/services/FeatureFlags.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.FeatureFlags = void 0;
4+
var FeatureFlags;
5+
(function (FeatureFlags) {
6+
FeatureFlags["enableTestScriptFragments"] = "enableTestScriptFragments";
7+
})(FeatureFlags || (exports.FeatureFlags = FeatureFlags = {}));

lib/util.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
3232
});
3333
};
3434
Object.defineProperty(exports, "__esModule", { value: true });
35-
exports.ErrorCorrection = exports.getResultObj = exports.validCriteria = exports.isTerminalTestStatus = exports.removeUnits = exports.indexOfFirstDigit = exports.deleteFile = exports.getReportFolder = exports.getResultFolder = exports.checkValidityYaml = exports.invalidDescription = exports.invalidDisplayName = exports.invalidName = exports.getUniqueId = exports.sleep = exports.printClientMetrics = exports.uploadFileToResultsFolder = exports.printCriteria = exports.printTestDuration = exports.checkFileTypes = exports.checkFileType = exports.httpClientRetries = exports.uploadFileData = exports.ManagedIdentityType = exports.apiConstants = void 0;
35+
exports.getAllFileErrors = exports.ErrorCorrection = exports.getResultObj = exports.validCriteria = exports.isTerminalTestStatus = exports.removeUnits = exports.indexOfFirstDigit = exports.deleteFile = exports.getReportFolder = exports.getResultFolder = exports.checkValidityYaml = exports.invalidDescription = exports.invalidDisplayName = exports.invalidName = exports.getUniqueId = exports.sleep = exports.printClientMetrics = exports.uploadFileToResultsFolder = exports.printCriteria = exports.printTestDuration = exports.checkFileTypes = exports.checkFileType = exports.httpClientRetries = exports.uploadFileData = exports.ManagedIdentityType = exports.apiConstants = void 0;
3636
const fs = __importStar(require("fs"));
3737
var path = require('path');
3838
var AdmZip = require("adm-zip");
@@ -87,7 +87,7 @@ function uploadFileData(filepath) {
8787
exports.uploadFileData = uploadFileData;
8888
const correlationHeader = 'x-ms-correlation-request-id';
8989
function httpClientRetries(urlSuffix_1, header_1, method_1) {
90-
return __awaiter(this, arguments, void 0, function* (urlSuffix, header, method, retries = 1, data, isUploadCall = true) {
90+
return __awaiter(this, arguments, void 0, function* (urlSuffix, header, method, retries = 1, data, isUploadCall = true, log = true) {
9191
let httpResponse;
9292
try {
9393
let correlationId = `gh-actions-${getUniqueId()}`;
@@ -118,7 +118,9 @@ function httpClientRetries(urlSuffix_1, header_1, method_1) {
118118
if (retries) {
119119
let sleeptime = (5 - retries) * 1000 + Math.floor(Math.random() * 5001);
120120
yield sleep(sleeptime);
121-
console.log(`Failed to connect to ${urlSuffix} due to ${err.message}, retrying in ${sleeptime / 1000} seconds`);
121+
if (log) {
122+
console.log(`Failed to connect to ${urlSuffix} due to ${err.message}, retrying in ${sleeptime / 1000} seconds`);
123+
}
122124
return httpClientRetries(urlSuffix, header, method, retries - 1, data);
123125
}
124126
else
@@ -542,3 +544,23 @@ function ErrorCorrection(result) {
542544
return "Unable to fetch the response. Please re-run or contact support if the issue persists. " + "Status code: " + result.message.statusCode;
543545
}
544546
exports.ErrorCorrection = ErrorCorrection;
547+
function getAllFileErrors(testObj) {
548+
var allArtifacts = [];
549+
for (var key in testObj.inputArtifacts) {
550+
var artifacts = testObj.inputArtifacts[key];
551+
if (artifacts instanceof Array) {
552+
allArtifacts = allArtifacts.concat(artifacts.filter((artifact) => artifact !== null && artifact !== undefined));
553+
}
554+
else if (artifacts !== null && artifacts !== undefined) {
555+
allArtifacts.push(artifacts);
556+
}
557+
}
558+
var fileErrors = {};
559+
for (const file of allArtifacts) {
560+
if (file.validationStatus === "VALIDATION_FAILURE") {
561+
fileErrors[file.fileName] = file.validationFailureDetails;
562+
}
563+
}
564+
return fileErrors;
565+
}
566+
exports.getAllFileErrors = getAllFileErrors;

src/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,14 @@ export const defaultYaml: any =
4646
}
4747
]
4848
}
49+
50+
export const testmanagerApiVersion = "2024-07-01-preview";
51+
52+
namespace BaseAPIRoute {
53+
export const featureFlag = "featureFlags";
54+
}
55+
56+
export namespace APIRoute {
57+
const latestVersion = "api-version="+testmanagerApiVersion;
58+
export const FeatureFlags = (flag: string) => `${BaseAPIRoute.featureFlag}/${flag}?${latestVersion}`;
59+
}

0 commit comments

Comments
 (0)