UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

196 lines (195 loc) 9.11 kB
"use strict"; 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) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestCloudUploader = void 0; const interaction_1 = require("./interaction"); const test_manifest_1 = require("./test-manifest"); const test_manifest_reader_1 = require("./test-manifest-reader"); const app_validator_1 = require("./app-validator"); const PortalHelper = require("../../../util/portal/portal-helper"); const _ = require("lodash"); const fs = require("fs"); // import * as http from "http"; const path = require("path"); const node_fetch_1 = require("node-fetch"); const FormData = require("form-data"); const debug = require("debug")("appcenter-cli:commands:test:lib:test-cloud-uploader"); const pLimit = require("p-limit"); const paralleRequests = 10; class TestCloudUploader { constructor(client, userName, appName, manifestPath, devices, portalBaseUrl) { if (!client) { throw new Error("Argument client is required"); } if (!userName) { throw new Error("Argument userName is required"); } if (!appName) { throw new Error("Argument appName is required"); } if (!manifestPath) { throw new Error("Argument manifestPath is required"); } if (!devices) { throw new Error("Argument devices is required"); } if (!portalBaseUrl) { throw new Error("Argument portalBaseUrl is required"); } this._client = client; this._manifestPath = manifestPath; this._devices = devices; this._userName = userName; this._appName = appName; this._portalBaseUrl = portalBaseUrl; } uploadAndStart() { return __awaiter(this, void 0, void 0, function* () { const app = yield this._client.apps.get(this._userName, this._appName); const isOrg = app.owner.type === "org"; const manifest = yield interaction_1.progressWithResult("Validating arguments", this.validateAndParseManifest()); const testRun = yield interaction_1.progressWithResult("Creating new test run", this.createTestRun(isOrg)); debug(`Test run id: ${testRun.testRunId}`); const appFile = yield interaction_1.progressWithResult("Validating application file", this.validateAndCreateAppFile(manifest)); const allFiles = manifest.testFiles.concat(appFile); yield interaction_1.progressWithResult("Uploading files", this.uploadFilesUsingBatch(testRun.testRunId, allFiles)); const startResult = yield interaction_1.progressWithResult("Starting test run", this.startTestRun(testRun.testRunId, manifest)); testRun.acceptedDevices = startResult.acceptedDevices || []; testRun.rejectedDevices = startResult.rejectedDevices || []; return testRun; }); } validateAndParseManifest() { return __awaiter(this, void 0, void 0, function* () { return yield test_manifest_reader_1.TestManifestReader.readFromFile(this._manifestPath); }); } validateAndCreateAppFile(manifest) { return __awaiter(this, void 0, void 0, function* () { const result = manifest.applicationFile ? manifest.applicationFile : yield test_manifest_1.TestRunFile.create(this.appPath, path.basename(this.appPath), "app-file"); if (!result) { throw new Error("If test manifest doesn't contain path to application file, it must be provided using --app-path option"); } yield app_validator_1.AppValidator.validate(result.sourcePath); return result; }); } createTestRun(isOrg) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { var _a; let testRunHeaders; try { testRunHeaders = yield this._client.test.createTestRun(this._userName, this._appName); } catch (error) { if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) { error.message = `The app named ${this._appName} does not exist in the organization or user: ${this._userName}`; } reject(error); } const testRunId = _.last(testRunHeaders.location.split("/")); resolve({ acceptedDevices: [], rejectedDevices: [], testRunId: testRunId, testRunUrl: PortalHelper.getPortalTestLink(this._portalBaseUrl, isOrg, this._userName, this._appName, this.testSeries, testRunId), }); })); } uploadFilesUsingBatch(testRunId, files) { return __awaiter(this, void 0, void 0, function* () { const checkHashesResult = yield this.uploadHashesBatch(testRunId, files.map((f) => { return { file: f }; })); const limit = pLimit(paralleRequests); const uploadNewFilesTasks = checkHashesResult .filter((r) => r.response.uploadStatus.statusCode === 412) .map((r) => limit(() => this.uploadFile(testRunId, r.file))); yield Promise.all(uploadNewFilesTasks); }); } uploadHashesBatch(testRunId, files) { return __awaiter(this, void 0, void 0, function* () { const mappedFiles = files.map((f) => this.testRunFileToFileHash(f.file, f.byteRange)); //TODO: Test carefully const result = yield this._client.test.uploadHashesBatch(testRunId, this._userName, this._appName, mappedFiles); return _.zip(files, result).map((fr) => { return { file: fr[0].file, response: fr[1] }; }); }); } testRunFileToFileHash(file, byteRange = null) { return { checksum: file.sha256, fileType: file.fileType, relativePath: file.targetRelativePath, }; } uploadFile(testRunId, file) { return __awaiter(this, void 0, void 0, function* () { const directUrl = yield this.getDirectUploadUrl(this._client, testRunId, file); yield this.makeDirectUpload(directUrl, file); }); } getDirectUploadUrl(client, testRunId, file) { return new Promise((resolve, reject) => { try { client.test.startUploadingFile(testRunId, this._userName, this._appName).then((result) => resolve(result.location)); } catch (err) { reject(err); } }); } makeDirectUpload(directUrl, file) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { try { const formData = new FormData(); formData.append("relative_path", file.targetRelativePath); formData.append("file", fs.createReadStream(file.sourcePath)); formData.append("file_type", file.fileType); node_fetch_1.default(directUrl, { method: "POST", body: formData, }) .then((response) => { if (response.status >= 400) { reject(new Error(`Cannot upload file. Response: ${response.status}; Message: ${response.body}`)); } else { resolve(); } }) .catch((error) => reject(error)); } catch (err) { reject(err); } }); }); } startTestRun(testRunId, manifest) { const allTestParameters = _.merge(manifest.testFramework.data || {}, this.testParameters || {}); const startOptions = { testFramework: manifest.testFramework.name, deviceSelection: this._devices, locale: this.locale, language: this.language, testSeries: this.testSeries, testParameters: allTestParameters, }; return this._client.test.startTestRun(testRunId, this._userName, this._appName, startOptions); } } exports.TestCloudUploader = TestCloudUploader;