From 52c5a4d7e1791633229523252c6a90a586438c95 Mon Sep 17 00:00:00 2001 From: Anastasia Kubova Date: Mon, 29 Jun 2020 18:07:13 +0300 Subject: [PATCH 1/2] fix retrying when http status not 2xx and fix the uploadRelease task for v2 --- Tasks/AppCenterDistributeV1/Tests/L0.ts | 10 + .../Tests/L0FailsHttpStatusNot2xx.ts | 209 +++++++++++++++++ .../appcenterdistribute.ts | 23 +- Tasks/AppCenterDistributeV2/Tests/L0.ts | 8 + .../Tests/L0FailsHttpStatusNot2xx.ts | 210 ++++++++++++++++++ .../appcenterdistribute.ts | 92 ++++---- Tasks/AppCenterDistributeV3/Tests/L0.ts | 10 + .../Tests/L0FailsHttpStatusNot2xx.ts | 199 +++++++++++++++++ .../appcenterdistribute.ts | 11 +- 9 files changed, 723 insertions(+), 49 deletions(-) create mode 100644 Tasks/AppCenterDistributeV1/Tests/L0FailsHttpStatusNot2xx.ts create mode 100644 Tasks/AppCenterDistributeV2/Tests/L0FailsHttpStatusNot2xx.ts create mode 100644 Tasks/AppCenterDistributeV3/Tests/L0FailsHttpStatusNot2xx.ts diff --git a/Tasks/AppCenterDistributeV1/Tests/L0.ts b/Tasks/AppCenterDistributeV1/Tests/L0.ts index 88f70090bc69..02dd80d1ba95 100644 --- a/Tasks/AppCenterDistributeV1/Tests/L0.ts +++ b/Tasks/AppCenterDistributeV1/Tests/L0.ts @@ -45,6 +45,16 @@ describe('AppCenterDistribute L0 Suite', function () { assert(tr.failed, 'task should have failed'); }); + it('Negative path: failed when HTTP status is not 2xx', function () { + this.timeout(4000); + + let tp = path.join(__dirname, 'L0FailsHttpStatusNot2xx.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.run(); + assert(tr.succeeded, 'task should have succeeded'); + }); + it('Negative path: cannot continue upload without symbols', function () { this.timeout(4000); diff --git a/Tasks/AppCenterDistributeV1/Tests/L0FailsHttpStatusNot2xx.ts b/Tasks/AppCenterDistributeV1/Tests/L0FailsHttpStatusNot2xx.ts new file mode 100644 index 000000000000..fb5982a83fbf --- /dev/null +++ b/Tasks/AppCenterDistributeV1/Tests/L0FailsHttpStatusNot2xx.ts @@ -0,0 +1,209 @@ + +import ma = require('vsts-task-lib/mock-answer'); +import tmrm = require('vsts-task-lib/mock-run'); +import path = require('path'); +import fs = require('fs'); +import azureBlobUploadHelper = require('../azure-blob-upload-helper'); + +var Readable = require('stream').Readable +var Stats = require('fs').Stats + +var nock = require('nock'); + +let taskPath = path.join(__dirname, '..', 'appcenterdistribute.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('serverEndpoint', 'MyTestEndpoint'); +tmr.setInput('appSlug', 'testuser/testapp'); +tmr.setInput('app', '/Users/anastasia.kubova/Downloads/test.ipa'); +tmr.setInput('releaseNotesSelection', 'releaseNotesInput'); +tmr.setInput('releaseNotesInput', 'my release notes'); +tmr.setInput('isMandatory', 'True'); +tmr.setInput('symbolsType', 'AndroidJava'); +tmr.setInput('mappingTxtPath', '/test/path/to/mappings.txt'); + +process.env['BUILD_BUILDID'] = '2'; +process.env['BUILD_SOURCEBRANCH'] = 'refs/heads/master'; +process.env['BUILD_SOURCEVERSION'] = 'commitsha'; + +// upload +nock('https://example.upload.test') + .post('/release_upload') + .reply(201, { + status: 'success' + }); + +nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/uploads/releases') + .reply(201, { + id: 1, + upload_url: "https://upload.example.test/upload/upload_chunk/00000000-0000-0000-0000-000000000000", + package_asset_id: 1, + upload_domain: 'https://example.upload.test/release_upload', + url_encoded_token: "test" + }).log(console.log); + +// finishing upload, commit the package +nock('https://example.test') + .patch("/v0.1/apps/testuser/testapp/uploads/releases/1", { + status: 'committed' + }) + .reply(200, { + release_url: 'my_release_location' + }); + +nock('https://example.upload.test') + .post('/release_upload/upload/set_metadata/1') + .query(true) + .reply(200, { + resume_restart: false, + chunk_list: [1], + chunk_size: 100, + blob_partitions: 1 + }); + +nock('https://example.upload.test') + .post('/release_upload/upload/upload_chunk/1') + .query(true) + .reply(200, { + }); + +nock('https://example.upload.test') + .post('/release_upload/upload/finished/1') + .query(true) + .reply(200, { + error: false, + state: "Done", + }); + +nock('https://example.test') + .patch('/v0.1/apps/testuser/testapp/uploads/releases/1', { + upload_status: "uploadFinished", + }) + .query(true) + .reply(200, { + upload_status: "uploadFinished", + release_url: 'https://example.upload.test/release_upload', + }); + +nock('https://example.test') + .get('/v0.1/apps/testuser/testapp/uploads/releases/1') + .query(true) + .reply(500, { + release_distinct_id: 1, + upload_status: "readyToBePublished", + }); + +nock('https://example.test') + .patch('/v0.1/apps/testuser/testapp/uploads/releases/1', { + upload_status: "committed", + }) + .query(true) + .reply(200, { + upload_status: "committed", + release_url: 'https://example.upload.test/release_upload', + }); + +nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1') + .query(true) + .reply(200, { + version: '1', + short_version: '1.0', + }); + +nock('https://example.test') + .patch("/v0.1/apps/testuser/testapp/releases/1", { + }) + .reply(201, { + }); + +nock('https://example.test') + .post("/v0.1/apps/testuser/testapp/releases/1", { + id: "00000000-0000-0000-0000-000000000000" + }) + .reply(200); + +nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1', JSON.stringify({ + release_notes: 'my release notes' + })) + .reply(200); + +// make it available +// JSON.stringify to verify exact match of request body: https://github.com/node-nock/nock/issues/571 +nock('https://example.test') + .patch("/my_release_location", JSON.stringify({ + status: "available", + release_notes: "my release notes", + mandatory_update: true, + destinations: [{ id: "00000000-0000-0000-0000-000000000000" }], + build: { + id: '2', + branch: 'master', + commit_hash: 'commitsha' + } + })) + .reply(200); + +// begin symbol upload +nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/symbol_uploads', { + symbol_type: "AndroidJava" + }) + .reply(201, { + symbol_upload_id: 100, + upload_url: 'https://example.upload.test/symbol_upload', + expiration_date: 1234567 + }); + +// finishing symbol upload, commit the symbol +nock('https://example.test') + .patch("/v0.1/apps/testuser/testapp/symbol_uploads/100", { + status: 'committed' + }) + .reply(200); + +// provide answers for task mock +let a: ma.TaskLibAnswers = { + "checkPath" : { + "/Users/anastasia.kubova/Downloads/test.ipa": true, + "/test/path/to/mappings.txt": true + }, + "findMatch" : { + "/test/path/to/mappings.txt": [ + "/test/path/to/mappings.txt" + ], + "/Users/anastasia.kubova/Downloads/test.ipa": [ + "/Users/anastasia.kubova/Downloads/test.ipa" + ] + } +}; +tmr.setAnswers(a); + +fs.createReadStream = (s: string) => { + let stream = new Readable; + stream.push(s); + stream.push(null); + return stream; +}; + +fs.statSync = (s: string) => { + let stat = new Stats; + stat.isFile = () => { + return !s.toLowerCase().endsWith(".dsym"); + } + stat.isDirectory = () => { + return s.toLowerCase().endsWith(".dsym"); + } + stat.size = 100; + return stat; +} + +azureBlobUploadHelper.AzureBlobUploadHelper.prototype.upload = async () => { + return Promise.resolve(); +} + +tmr.registerMock('azure-blob-upload-helper', azureBlobUploadHelper); +tmr.registerMock('fs', fs); +tmr.run(); diff --git a/Tasks/AppCenterDistributeV1/appcenterdistribute.ts b/Tasks/AppCenterDistributeV1/appcenterdistribute.ts index aa777f25b9ff..1df468cb08fd 100644 --- a/Tasks/AppCenterDistributeV1/appcenterdistribute.ts +++ b/Tasks/AppCenterDistributeV1/appcenterdistribute.ts @@ -106,7 +106,7 @@ function beginReleaseUpload(apiServer: string, apiVersion: string, appSlug: stri request.post({ url: beginUploadUrl, headers: headers }, (err, res, body) => { responseHandler(defer, err, res, body, () => { let response = JSON.parse(body); - if (!response.package_asset_id || (response.statusCode && response.statusCode !== 200)) { + if (!response.package_asset_id || (response.statusCode && (response.statusCode < 200 || response.statusCode >= 300))) { defer.reject(`failed to create release upload. ${response.message}`) } @@ -120,7 +120,13 @@ function beginReleaseUpload(apiServer: string, apiVersion: string, appSlug: stri function loadReleaseIdUntilSuccess(apiServer: string, apiVersion: string, appSlug: string, uploadId: string, token: string, userAgent: string): Q.Promise { let defer = Q.defer(); const timerId = setInterval(async () => { - const response = await getReleaseId(apiServer, apiVersion, appSlug, uploadId, token, userAgent); + let response; + try { + response = await getReleaseId(apiServer, apiVersion, appSlug, uploadId, token, userAgent); + } catch (error) { + clearInterval(timerId); + defer.reject(new Error(`Loading release id failed with: ${error}`)); + } const releaseId = response.release_distinct_id; tl.debug(`Received release id is ${releaseId}`); if (response.upload_status === "readyToBePublished" && releaseId) { @@ -146,20 +152,20 @@ function uploadRelease(releaseUploadParams: UploadInfo, file: string): Q.Promise uploadDomain: uploadDomain, tenant: "distribution", onProgressChanged: (progress: IProgress) => { - tl.debug("onProgressChanged: " + progress.percentCompleted); + tl.debug("---- onProgressChanged: " + progress.percentCompleted); }, onMessage: (message: string, properties: LogProperties, level: McFusMessageLevel) => { - tl.debug(`onMessage: ${message} \nMessage properties: ${JSON.stringify(properties)}`); + tl.debug(`---- onMessage: ${message} \nMessage properties: ${JSON.stringify(properties)}`); if (level === McFusMessageLevel.Error) { mcFusUploader.cancel(); defer.reject(new Error(`Uploading file error: ${message}`)); } }, onStateChanged: (status: McFusUploadState): void => { - tl.debug(`onStateChanged: ${status.toString()}`); + tl.debug(`---- onStateChanged: ${status.toString()}`); }, onCompleted: (uploadStats: IUploadStats) => { - tl.debug("Upload completed, total time: " + uploadStats.totalTimeInSeconds); + tl.debug("---- Upload completed, total time: " + uploadStats.totalTimeInSeconds); defer.resolve(); }, }; @@ -211,7 +217,10 @@ function getReleaseId(apiServer: string, apiVersion: string, appSlug: string, re request.get({ url: getReleaseUrl, headers: headers }, (err, res, body) => { responseHandler(defer, err, res, body, () => { - defer.resolve(JSON.parse(body)); + if (res["status"] < 200 || res["status"] >= 300) { + defer.reject(new Error(`HTTP status ${res["status"]}`)); + } + defer.resolve(JSON.parse(body)); }); }) diff --git a/Tasks/AppCenterDistributeV2/Tests/L0.ts b/Tasks/AppCenterDistributeV2/Tests/L0.ts index e0d6810c0be4..c13ac24adc5f 100644 --- a/Tasks/AppCenterDistributeV2/Tests/L0.ts +++ b/Tasks/AppCenterDistributeV2/Tests/L0.ts @@ -48,6 +48,14 @@ describe('AppCenterDistribute L0 Suite', function () { assert(tr.failed, 'task should have failed'); }); + it.only('Negative path: failed when HTTP status is not 2xx', function () { + let tp = path.join(__dirname, 'L0FailsHttpStatusNot2xx.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.run(); + assert(tr.succeeded, 'task should have succeeded'); + }); + it('Postiive path: can continue upload without symbols if variable VSMobileCenterUpload.ContinueIfSymbolsNotFound is true', function () { let tp = path.join(__dirname, 'L0NoSymbolsConditionallyPass.js'); let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); diff --git a/Tasks/AppCenterDistributeV2/Tests/L0FailsHttpStatusNot2xx.ts b/Tasks/AppCenterDistributeV2/Tests/L0FailsHttpStatusNot2xx.ts new file mode 100644 index 000000000000..13d89d18c8a2 --- /dev/null +++ b/Tasks/AppCenterDistributeV2/Tests/L0FailsHttpStatusNot2xx.ts @@ -0,0 +1,210 @@ + +import ma = require('vsts-task-lib/mock-answer'); +import tmrm = require('vsts-task-lib/mock-run'); +import path = require('path'); +import fs = require('fs'); +import azureBlobUploadHelper = require('../azure-blob-upload-helper'); + +var Readable = require('stream').Readable +var Stats = require('fs').Stats + +var nock = require('nock'); + +let taskPath = path.join(__dirname, '..', 'appcenterdistribute.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('serverEndpoint', 'MyTestEndpoint'); +tmr.setInput('appSlug', 'testuser/testapp'); +tmr.setInput('app', '/Users/anastasia.kubova/Downloads/test.ipa'); +tmr.setInput('releaseNotesSelection', 'releaseNotesInput'); +tmr.setInput('releaseNotesInput', 'my release notes'); +tmr.setInput('isMandatory', 'True'); +tmr.setInput('symbolsType', 'AndroidJava'); +tmr.setInput('mappingTxtPath', '/test/path/to/mappings.txt'); + +process.env['BUILD_BUILDID'] = '2'; +process.env['BUILD_SOURCEBRANCH'] = 'refs/heads/master'; +process.env['BUILD_SOURCEVERSION'] = 'commitsha'; + +// upload +nock('https://example.upload.test') + .post('/release_upload') + .reply(201, { + status: 'success' + }); + +nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/uploads/releases') + .reply(201, { + id: 1, + upload_url: "https://upload.example.test/upload/upload_chunk/00000000-0000-0000-0000-000000000000", + package_asset_id: 1, + upload_domain: 'https://example.upload.test/release_upload', + url_encoded_token: "test" + }).log(console.log); + +// finishing upload, commit the package +nock('https://example.test') + .patch("/v0.1/apps/testuser/testapp/uploads/releases/1", { + status: 'committed' + }) + .reply(200, { + release_url: 'my_release_location' + }); + +nock('https://example.upload.test') + .post('/release_upload/upload/set_metadata/1') + .query(true) + .reply(200, { + resume_restart: false, + chunk_list: [1], + chunk_size: 100, + blob_partitions: 1 + }); + +nock('https://example.upload.test') + .post('/release_upload/upload/upload_chunk/1') + .query(true) + .reply(200, { + }); + +nock('https://example.upload.test') + .post('/release_upload/upload/finished/1') + .query(true) + .reply(200, { + error: false, + state: "Done", + }); + +nock('https://example.test') + .patch('/v0.1/apps/testuser/testapp/uploads/releases/1', { + upload_status: "uploadFinished", + }) + .query(true) + .reply(200, { + upload_status: "uploadFinished", + release_url: 'https://example.upload.test/release_upload', + }); + +nock('https://example.test') + .get('/v0.1/apps/testuser/testapp/uploads/releases/1') + .query(true) + .reply(500, { + release_distinct_id: 1, + upload_status: "readyToBePublished", + }); + +nock('https://example.test') + .patch('/v0.1/apps/testuser/testapp/uploads/releases/1', { + upload_status: "committed", + }) + .query(true) + .reply(200, { + upload_status: "committed", + release_url: 'https://example.upload.test/release_upload', + }); + +nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1') + .query(true) + .reply(200, { + version: '1', + short_version: '1.0', + }); + +nock('https://example.test') + .patch("/v0.1/apps/testuser/testapp/releases/1", { + }) + .reply(201, { + }); + +nock('https://example.test') + .post("/v0.1/apps/testuser/testapp/releases/1", { + id: "00000000-0000-0000-0000-000000000000" + }) + .reply(200); + +nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1', JSON.stringify({ + release_notes: 'my release notes' + })) + .reply(200); + +// make it available +// JSON.stringify to verify exact match of request body: https://github.com/node-nock/nock/issues/571 +nock('https://example.test') + .patch("/my_release_location", JSON.stringify({ + status: "available", + release_notes: "my release notes", + mandatory_update: true, + destinations: [{ id: "00000000-0000-0000-0000-000000000000" }], + build: { + id: '2', + branch: 'master', + commit_hash: 'commitsha' + } + })) + .reply(200); + +// begin symbol upload +nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/symbol_uploads', { + symbol_type: "AndroidJava" + }) + .reply(201, { + symbol_upload_id: 100, + upload_url: 'https://example.upload.test/symbol_upload', + expiration_date: 1234567 + }); + +// finishing symbol upload, commit the symbol +nock('https://example.test') + .patch("/v0.1/apps/testuser/testapp/symbol_uploads/100", { + status: 'committed' + }) + .reply(200); + +// provide answers for task mock +let a: ma.TaskLibAnswers = { + "checkPath" : { + "/Users/anastasia.kubova/Downloads/test.ipa": true, + "/test/path/to/mappings.txt": true + }, + "findMatch" : { + "/test/path/to/mappings.txt": [ + "/test/path/to/mappings.txt" + ], + "/Users/anastasia.kubova/Downloads/test.ipa": [ + "/Users/anastasia.kubova/Downloads/test.ipa" + ] + } +}; +tmr.setAnswers(a); + +fs.createReadStream = (s: string) => { + let stream = new Readable; + stream.push(s); + stream.push(null); + return stream; +}; + +fs.statSync = (s: string) => { + let stat = new Stats; + stat.isFile = () => { + return !s.toLowerCase().endsWith(".dsym"); + } + stat.isDirectory = () => { + return s.toLowerCase().endsWith(".dsym"); + } + stat.size = 100; + return stat; +} + +azureBlobUploadHelper.AzureBlobUploadHelper.prototype.upload = async () => { + return Promise.resolve(); +} + +tmr.registerMock('azure-blob-upload-helper', azureBlobUploadHelper); +tmr.registerMock('fs', fs); +tmr.run(); + diff --git a/Tasks/AppCenterDistributeV2/appcenterdistribute.ts b/Tasks/AppCenterDistributeV2/appcenterdistribute.ts index 25d9185cc77a..eccb25cf8ec9 100644 --- a/Tasks/AppCenterDistributeV2/appcenterdistribute.ts +++ b/Tasks/AppCenterDistributeV2/appcenterdistribute.ts @@ -137,45 +137,44 @@ function abortReleaseUpload(apiServer: string, apiVersion: string, appSlug: stri return defer.promise; } -function uploadRelease(releaseUploadParams: any, file: string): Promise { - return new Promise((resolve, reject) => { - const assetId = releaseUploadParams.package_asset_id; - const urlEncodedToken = releaseUploadParams.url_encoded_token; - const uploadDomain = releaseUploadParams.upload_domain; - tl.debug("-- Uploading release..."); - const uploadSettings: IInitializeSettings = { - assetId: assetId, - urlEncodedToken: urlEncodedToken, - uploadDomain: uploadDomain, - tenant: "distribution", - onProgressChanged: (progress: IProgress) => { - tl.debug("---- onProgressChanged: " + progress.percentCompleted); - }, - onMessage: (message: string, properties: LogProperties, level: McFusMessageLevel) => { - tl.debug(`---- onMessage: ${message} \nMessage properties: ${JSON.stringify(properties)}`); - if (level === McFusMessageLevel.Error) { - mcFusUploader.cancel(); - reject(new Error(`Uploading file error: ${message}`)); - } - }, - onStateChanged: (status: McFusUploadState): void => { - tl.debug(`---- onStateChanged: ${status.toString()}`); - }, - onCompleted: (uploadStats: IUploadStats) => { - tl.debug("---- Upload completed, total time: " + uploadStats.totalTimeInSeconds); - resolve(); - }, - }; - mcFusUploader = new McFusNodeUploader(uploadSettings); - const fullFile = path.resolve(file); - const appFile = new McFile(fullFile); - mcFusUploader.start(appFile); - }); +function uploadRelease(releaseUploadParams: any, file: string): Q.Promise { + const assetId = releaseUploadParams.package_asset_id; + const urlEncodedToken = releaseUploadParams.url_encoded_token; + const uploadDomain = releaseUploadParams.upload_domain; + tl.debug("-- Uploading release..."); + let defer = Q.defer(); + const uploadSettings: IInitializeSettings = { + assetId: assetId, + urlEncodedToken: urlEncodedToken, + uploadDomain: uploadDomain, + tenant: "distribution", + onProgressChanged: (progress: IProgress) => { + tl.debug("---- onProgressChanged: " + progress.percentCompleted); + }, + onMessage: (message: string, properties: LogProperties, level: McFusMessageLevel) => { + tl.debug(`---- onMessage: ${message} \nMessage properties: ${JSON.stringify(properties)}`); + if (level === McFusMessageLevel.Error) { + mcFusUploader.cancel(); + defer.reject(new Error(`Uploading file error: ${message}`)); + } + }, + onStateChanged: (status: McFusUploadState): void => { + tl.debug(`---- onStateChanged: ${status.toString()}`); + }, + onCompleted: (uploadStats: IUploadStats) => { + tl.debug("---- Upload completed, total time: " + uploadStats.totalTimeInSeconds); + defer.resolve(); + }, + }; + mcFusUploader = new McFusNodeUploader(uploadSettings); + const appFile = new McFile(file); + mcFusUploader.start(appFile); + return defer.promise; } -function patchRelease(apiServer: string, apiVersion: string, appSlug: string, upload_id: string, token: string, userAgent: string): Q.Promise { +function patchRelease(apiServer: string, apiVersion: string, appSlug: string, upload_id: string, token: string, userAgent: string): Q.Promise { tl.debug("-- Finishing uploading release..."); - let defer = Q.defer(); + let defer = Q.defer(); let patchReleaseUrl: string = `${apiServer}/${apiVersion}/apps/${appSlug}/uploads/releases/${upload_id}`; tl.debug(`---- url: ${patchReleaseUrl}`); let headers = { @@ -185,11 +184,13 @@ function patchRelease(apiServer: string, apiVersion: string, appSlug: string, up }; let uploadFinishedBody = { "upload_status": "uploadFinished" }; request.patch({ url: patchReleaseUrl, headers: headers, json: uploadFinishedBody }, (err, res, body) => { + tl.debug(`---- patchRelease body : ${body}`); responseHandler(defer, err, res, body, () => { - const { upload_status, message } = body; - if (upload_status !== "uploadFinished") { - defer.reject(`Failed to patch release upload: ${message}`); - } + const { upload_status, message } = body; + if (upload_status !== "uploadFinished") { + defer.reject(`Failed to patch release upload: ${message}`); + } + defer.resolve(); }); }) return defer.promise; @@ -428,6 +429,9 @@ function getReleaseId(apiServer: string, apiVersion: string, appSlug: string, re }; request.get({ url: getReleaseUrl, headers: headers }, (err, res, body) => { responseHandler(defer, err, res, body, () => { + if ((res["status"] < 200 || res["status"] >= 300)) { + defer.reject(new Error(`HTTP status ${res["status"]}`)); + } defer.resolve(JSON.parse(body)); }); }) @@ -437,7 +441,13 @@ function getReleaseId(apiServer: string, apiVersion: string, appSlug: string, re function loadReleaseIdUntilSuccess(apiServer: string, apiVersion: string, appSlug: string, uploadId: string, token: string, userAgent: string): Q.Promise { let defer = Q.defer(); const timerId = setInterval(async () => { - const response = await getReleaseId(apiServer, apiVersion, appSlug, uploadId, token, userAgent); + let response; + try { + response = await getReleaseId(apiServer, apiVersion, appSlug, uploadId, token, userAgent); + } catch (error) { + clearInterval(timerId); + defer.reject(new Error(`Loading release id failed with: ${error}`)); + } const releaseId = response.release_distinct_id; tl.debug(`---- Received release id is ${releaseId}`); if (response.upload_status === "readyToBePublished" && releaseId) { diff --git a/Tasks/AppCenterDistributeV3/Tests/L0.ts b/Tasks/AppCenterDistributeV3/Tests/L0.ts index 2c03e8dc2c4a..b6ff13d486cd 100644 --- a/Tasks/AppCenterDistributeV3/Tests/L0.ts +++ b/Tasks/AppCenterDistributeV3/Tests/L0.ts @@ -252,6 +252,16 @@ describe('AppCenterDistribute L0 Suite', function () { assert(tr.succeeded, 'task should have succeeded'); }); + it('Negative path: failed when HTTP status is not 2xx', function () { + this.timeout(defaultTimeout); + + let tp = path.join(__dirname, 'L0FailsHttpStatusNot2xx.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.run(); + assert(tr.succeeded, 'task should have succeeded'); + }); + it('Positive path: publish silent update', function () { this.timeout(defaultTimeout); diff --git a/Tasks/AppCenterDistributeV3/Tests/L0FailsHttpStatusNot2xx.ts b/Tasks/AppCenterDistributeV3/Tests/L0FailsHttpStatusNot2xx.ts new file mode 100644 index 000000000000..232977366ca3 --- /dev/null +++ b/Tasks/AppCenterDistributeV3/Tests/L0FailsHttpStatusNot2xx.ts @@ -0,0 +1,199 @@ + +import ma = require('vsts-task-lib/mock-answer'); +import tmrm = require('vsts-task-lib/mock-run'); +import path = require('path'); +import fs = require('fs'); +import * as assert from "assert"; +import azureBlobUploadHelper = require('../azure-blob-upload-helper'); + +var Readable = require('stream').Readable +var Stats = require('fs').Stats + +var nock = require('nock'); + +let taskPath = path.join(__dirname, '..', 'appcenterdistribute.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('serverEndpoint', 'MyTestEndpoint'); +tmr.setInput('appSlug', 'testuser/testapp'); +tmr.setInput('app', './test.ipa'); +tmr.setInput('releaseNotesSelection', 'releaseNotesInput'); +tmr.setInput('releaseNotesInput', 'my release notes'); +tmr.setInput('isMandatory', 'True'); +tmr.setInput('symbolsType', 'AndroidJava'); +tmr.setInput('mappingTxtPath', '/test/path/to/mappings.txt'); + +process.env['BUILD_BUILDID'] = '2'; +process.env['BUILD_SOURCEBRANCH'] = 'refs/heads/master'; +process.env['BUILD_SOURCEVERSION'] = 'commitsha'; + + +const uploadDomain = 'https://example.upload.test/release_upload'; +const assetId = "00000000-0000-0000-0000-000000000123"; +const uploadId = 7; + +nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/uploads/releases') + .reply(201, { + id: uploadId, + package_asset_id: assetId, + upload_domain: uploadDomain, + url_encoded_token: "token" + }).log(console.log); + +nock(uploadDomain) + .post(`/upload/set_metadata/${assetId}`) + .query(true) + .reply(200, { + resume_restart: false, + chunk_list: [1], + chunk_size: 100, + blob_partitions: 1 + }); + +nock(uploadDomain) + .post(`/upload/upload_chunk/${assetId}`) + .query(true) + .reply(200, { + + }); + +nock(uploadDomain) + .post(`/upload/finished/${assetId}`) + .query(true) + .reply(200, { + error: false, + state: "Done", + }); + +nock('https://example.test') + .get(`/v0.1/apps/testuser/testapp/uploads/releases/${uploadId}`) + .query(true) + .reply(500, { + release_distinct_id: 1, + upload_status: "readyToBePublished", + }); + +nock('https://example.test') + .patch(`/v0.1/apps/testuser/testapp/uploads/releases/${uploadId}`, { + upload_status: "uploadFinished", + }) + .query(true) + .reply(200, { + upload_status: "uploadFinished" + }); + + nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1', JSON.stringify({ + release_notes: 'my release notes' + })) + .reply(200); + + //make it available + nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/releases/1/groups', { + id: "00000000-0000-0000-0000-000000000000" + }) + .reply(200); + + //finishing symbol upload, commit the symbol + nock('https://example.test') + .patch('/v0.1/apps/testuser/testapp/symbol_uploads/100', { + status: 'committed' + }) + .reply(200); + + //finishing symbol upload, commit the symbol + nock('https://example.test') + .patch('v0.1/apps/testuser/testapp/release_uploads/7', { + status: 'aborted' + }) + .reply(200); + +nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1') + .query(true) + .reply(200, { + version: '1', + short_version: '1.0', + }); + +//make it available +//JSON.stringify to verify exact match of request body: https://github.com/node-nock/nock/issues/571 +nock('https://example.test') + .post("/v0.1/apps/testuser/testapp/releases/1/groups", JSON.stringify({ + id: "00000000-0000-0000-0000-000000000000", + mandatory_update: true + })) + .reply(200); + +nock('https://example.test') + .put('/v0.1/apps/testuser/testapp/releases/1', JSON.stringify({ + release_notes: 'my release notes', + build: { + id: '2', + branch: 'master', + commit_hash: 'commitsha' + } + })) + .reply(200); + +//begin symbol upload +nock('https://example.test') + .post('/v0.1/apps/testuser/testapp/symbol_uploads', { + symbol_type: "AndroidJava" + }) + .reply(201, { + symbol_upload_id: 100, + upload_url: 'https://example.upload.test/symbol_upload', + expiration_date: 1234567 + }); + +// provide answers for task mock +let a: ma.TaskLibAnswers = { + "checkPath": { + "./test.ipa": true, + "/test/path/to/mappings.txt": true + }, + "findMatch": { + "/test/path/to/mappings.txt": [ + "/test/path/to/mappings.txt" + ], + "./test.ipa": [ + "./test.ipa" + ] + } +}; +tmr.setAnswers(a); + +fs.createReadStream = (s: string) => { + let stream = new Readable; + stream.push(s); + stream.push(null); + + return stream; +}; + +fs.statSync = (s: string) => { + let stat = new Stats; + + stat.isFile = () => { + return !s.toLowerCase().endsWith(".dsym"); + } + stat.isDirectory = () => { + return s.toLowerCase().endsWith(".dsym"); + } + stat.size = 100; + + return stat; +} + +azureBlobUploadHelper.AzureBlobUploadHelper.prototype.upload = async () => { + return Promise.resolve(); +} + +tmr.registerMock('azure-blob-upload-helper', azureBlobUploadHelper); +tmr.registerMock('fs', fs); + +tmr.run(); + diff --git a/Tasks/AppCenterDistributeV3/appcenterdistribute.ts b/Tasks/AppCenterDistributeV3/appcenterdistribute.ts index a6fc219fb7b5..b722a22d78bf 100644 --- a/Tasks/AppCenterDistributeV3/appcenterdistribute.ts +++ b/Tasks/AppCenterDistributeV3/appcenterdistribute.ts @@ -141,7 +141,13 @@ function beginReleaseUpload(apiServer: string, apiVersion: string, appSlug: stri function loadReleaseIdUntilSuccess(apiServer: string, apiVersion: string, appSlug: string, uploadId: string, token: string, userAgent: string): Q.Promise { let defer = Q.defer(); const timerId = setInterval(async () => { - const response = await getReleaseId(apiServer, apiVersion, appSlug, uploadId, token, userAgent); + let response; + try { + response = await getReleaseId(apiServer, apiVersion, appSlug, uploadId, token, userAgent); + } catch (error) { + clearInterval(timerId); + defer.reject(new Error(`Loading release id failed with: ${error}`)); + } const releaseId = response.release_distinct_id; tl.debug(`---- Received release id is ${releaseId}`); if (response.upload_status === "readyToBePublished" && releaseId) { @@ -348,6 +354,9 @@ function getReleaseId(apiServer: string, apiVersion: string, appSlug: string, re request.get({ url: getReleaseUrl, headers: headers }, (err, res, body) => { responseHandler(defer, err, res, body, () => { + if ((res["status"] < 200 || res["status"] >= 300)) { + defer.reject(new Error(`HTTP status ${res["status"]}`)); + } defer.resolve(JSON.parse(body)); }); }) From 9d967febc442477c5f0bd9e3b1a8f25ec8340200 Mon Sep 17 00:00:00 2001 From: Anastasia Kubova Date: Mon, 29 Jun 2020 18:30:53 +0300 Subject: [PATCH 2/2] add missing changes in tests --- Tasks/AppCenterDistributeV1/Tests/L0.ts | 2 +- Tasks/AppCenterDistributeV2/Tests/L0.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Tasks/AppCenterDistributeV1/Tests/L0.ts b/Tasks/AppCenterDistributeV1/Tests/L0.ts index 02dd80d1ba95..91a62bbd0bab 100644 --- a/Tasks/AppCenterDistributeV1/Tests/L0.ts +++ b/Tasks/AppCenterDistributeV1/Tests/L0.ts @@ -46,7 +46,7 @@ describe('AppCenterDistribute L0 Suite', function () { }); it('Negative path: failed when HTTP status is not 2xx', function () { - this.timeout(4000); + this.timeout(6000); let tp = path.join(__dirname, 'L0FailsHttpStatusNot2xx.js'); let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); diff --git a/Tasks/AppCenterDistributeV2/Tests/L0.ts b/Tasks/AppCenterDistributeV2/Tests/L0.ts index c13ac24adc5f..0c8316b84bdf 100644 --- a/Tasks/AppCenterDistributeV2/Tests/L0.ts +++ b/Tasks/AppCenterDistributeV2/Tests/L0.ts @@ -48,10 +48,9 @@ describe('AppCenterDistribute L0 Suite', function () { assert(tr.failed, 'task should have failed'); }); - it.only('Negative path: failed when HTTP status is not 2xx', function () { + it('Negative path: failed when HTTP status is not 2xx', function () { let tp = path.join(__dirname, 'L0FailsHttpStatusNot2xx.js'); let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - tr.run(); assert(tr.succeeded, 'task should have succeeded'); });