UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

373 lines (310 loc) 14.6 kB
import { expect, use } from "chai"; import * as Fs from "fs"; import * as Nock from "nock"; import * as Path from "path"; import * as Sinon from "sinon"; import * as Temp from "temp"; import * as ChaiAsPromised from "chai-as-promised"; use(ChaiAsPromised); import ReleaseBinaryCommand from "../../../../src/commands/distribute/release"; import { CommandArgs, CommandResult, CommandFailedResult } from "../../../../src/util/commandline"; Temp.track(); describe("release command", () => { const fakeAppOwner = "fakeAppOwner"; const fakeAppName = "fakeAppName"; const fakeAppIdentifier = `${fakeAppOwner}/${fakeAppName}`; const fakeToken = "c1o3d3e7"; const fakeReleaseUploadingId = "fakeReleaseUploadingId"; const fakeUploadUrl = "/upload/here"; const fakeReleaseId = "1"; const fakeReleaseUrl = "/fake/release/url/" + fakeReleaseId; const fakeDistributionGroupName = "fakeDistributionGroupName"; const fakeGroupId = "00000000-0000-0000-0000-000000000000"; /* tslint:disable-next-line:no-http-string */ const fakeHost = "http://localhost:1700"; const version = "1.0"; const shortVersion = "1"; const releaseFileName = "releaseBinaryFile"; const releaseNotesFileName = "releaseNotesFile"; const releaseFileContent = "Hello World!"; const releaseNotes = "Release Notes for v1"; let tmpFolderPath: string; let uploadSpy: Sinon.SinonSpy; let postSymbolSpy: Sinon.SinonSpy; let patchSymbolSpy: Sinon.SinonSpy; let abortSymbolSpy: Sinon.SinonSpy; let distributeSpy: Sinon.SinonSpy; let expectedRequestsScope: Nock.Scope; let skippedRequestsScope: Nock.Scope; before(() => { Nock.disableNetConnect(); }); beforeEach(() => { tmpFolderPath = Temp.mkdirSync("releaseTest"); uploadSpy = Sinon.spy(); postSymbolSpy = Sinon.spy(); patchSymbolSpy = Sinon.spy(); abortSymbolSpy = Sinon.spy(); distributeSpy = Sinon.spy(); }); describe("when all network requests are successful", () => { beforeEach(() => { expectedRequestsScope = setupSuccessfulGetDistributionGroupUsersResponse( setupSuccessfulPostUploadResponse( setupSuccessfulUploadResponse( setupSuccessfulPatchUploadResponse( setupSuccessfulCreateReleaseResponse( setupSuccessfulAddGroupResponse( setupSuccsessFulGetDistributionGroupResponse( Nock(fakeHost)))))))); skippedRequestsScope = setupSuccessfulAbortUploadResponse(Nock(fakeHost)); }); it("uploads release with release notes text", async () => { // Arrange const releaseFilePath = createFile(tmpFolderPath, releaseFileName, releaseFileContent); // Act const command = new ReleaseBinaryCommand(getCommandArgs(["-f", releaseFilePath, "-r", releaseNotes, "-g", fakeDistributionGroupName])); const result = await command.execute(); // Assert testCommandSuccess(result, expectedRequestsScope, skippedRequestsScope); testUploadedFormData(); }); it("uploads release with release notes file", async () => { // Arrange const releaseFilePath = createFile(tmpFolderPath, releaseFileName, releaseFileContent); const releaseNotesFilePath = createFile(tmpFolderPath, releaseNotesFileName, releaseNotes); // Act const command = new ReleaseBinaryCommand(getCommandArgs(["-f", releaseFilePath, "-R", releaseNotesFilePath, "-g", fakeDistributionGroupName])); const result = await command.execute(); // Assert testCommandSuccess(result, expectedRequestsScope, skippedRequestsScope); testUploadedFormData(); }); }); describe("when release upload fails", () => { beforeEach(() => { expectedRequestsScope = setupSuccessfulGetDistributionGroupUsersResponse( setupSuccessfulPostUploadResponse( setupFailedUploadResponse( setupSuccessfulAbortUploadResponse( Nock(fakeHost))))); skippedRequestsScope = setupSuccessfulCreateReleaseResponse( setupSuccessfulPatchUploadResponse(Nock(fakeHost))); }); it("attempts to abort the upload", async () => { // Arrange const releaseFilePath = createFile(tmpFolderPath, releaseFileName, releaseFileContent); const releaseNotesFilePath = createFile(tmpFolderPath, releaseNotesFileName, releaseNotes); // Act const command = new ReleaseBinaryCommand(getCommandArgs(["-f", releaseFilePath, "-R", releaseNotesFilePath, "-g", fakeDistributionGroupName])); const result = await expect(command.execute()).to.eventually.be.rejected as CommandFailedResult; // Assert testFailure(result, "release binary file uploading failed: HTTP 500 null", expectedRequestsScope, skippedRequestsScope); }); }); describe("when creating the release fails", () => { beforeEach(() => { expectedRequestsScope = setupSuccessfulGetDistributionGroupUsersResponse( setupSuccessfulPostUploadResponse( setupSuccessfulUploadResponse( setupSuccessfulPatchUploadResponse( setupFailedCreateReleaseResponse( Nock(fakeHost)))))); skippedRequestsScope = setupSuccessfulGetDistributionGroupUsersResponse( setupSuccessfulAddGroupResponse(Nock(fakeHost))); }); it("does not try to add the group to the release", async () => { // Arrange const releaseFilePath = createFile(tmpFolderPath, releaseFileName, releaseFileContent); const releaseNotesFilePath = createFile(tmpFolderPath, releaseNotesFileName, releaseNotes); // Act const command = new ReleaseBinaryCommand(getCommandArgs(["-f", releaseFilePath, "-R", releaseNotesFilePath, "-g", fakeDistributionGroupName])); const result = await expect(command.execute()).to.eventually.be.rejected as CommandFailedResult; // Assert testFailure(result, `failed to set distribution group and release notes for release ${fakeReleaseId}`, expectedRequestsScope, skippedRequestsScope); }); }); describe("when getting the distribution group fails", () => { beforeEach(() => { expectedRequestsScope = setupSuccessfulGetDistributionGroupUsersResponse( setupSuccessfulPostUploadResponse( setupSuccessfulUploadResponse( setupSuccessfulPatchUploadResponse( setupSuccessfulCreateReleaseResponse( setupFailedGetDistributionGroupResponse( Nock(fakeHost))))))); skippedRequestsScope = setupSuccessfulAddGroupResponse(Nock(fakeHost)); }); it("does not try to add the group to the release", async () => { // Arrange const releaseFilePath = createFile(tmpFolderPath, releaseFileName, releaseFileContent); const releaseNotesFilePath = createFile(tmpFolderPath, releaseNotesFileName, releaseNotes); // Act const command = new ReleaseBinaryCommand(getCommandArgs(["-f", releaseFilePath, "-R", releaseNotesFilePath, "-g", fakeDistributionGroupName])); const result = await expect(command.execute()).to.eventually.be.rejected as CommandFailedResult; // Assert testFailure(result, `Could not find group ${fakeDistributionGroupName}`, expectedRequestsScope, skippedRequestsScope); }); }); describe("when adding the group to the distribution group fails", () => { beforeEach(() => { expectedRequestsScope = setupSuccessfulGetDistributionGroupUsersResponse( setupSuccessfulPostUploadResponse( setupSuccessfulUploadResponse( setupSuccessfulPatchUploadResponse( setupSuccessfulCreateReleaseResponse( setupSuccsessFulGetDistributionGroupResponse( setupFailedAddGroupResponse( Nock(fakeHost)))))))); }); it("responds with a failed result", async () => { // Arrange const releaseFilePath = createFile(tmpFolderPath, releaseFileName, releaseFileContent); const releaseNotesFilePath = createFile(tmpFolderPath, releaseNotesFileName, releaseNotes); // Act const command = new ReleaseBinaryCommand(getCommandArgs(["-f", releaseFilePath, "-R", releaseNotesFilePath, "-g", fakeDistributionGroupName])); const result = await expect(command.execute()).to.eventually.be.rejected as CommandFailedResult; // Assert testFailure(result, `Could not find release ${fakeReleaseId}`, expectedRequestsScope); }); }); afterEach(() => { Nock.cleanAll(); }); after(() => { Nock.enableNetConnect(); }); function createFile(folderPath: string, fileName: string, fileContent: string): string { const finalPath = Path.join(folderPath, fileName); Fs.writeFileSync(finalPath, fileContent); return finalPath; } function testCommandSuccess(result: CommandResult, executionScope: Nock.Scope, abortScope: Nock.Scope) { expect(result.succeeded).to.eql(true, "Command should be successfully completed"); expect(abortScope.isDone()).to.eql(false, "Upload should not be aborted"); executionScope.done(); // All normal API calls are executed } function testFailure(result: CommandFailedResult, errorMessage: string, executionScope: Nock.Scope, skippedScope?: Nock.Scope) { expect(result.succeeded).to.eql(false, "Command should fail"); expect(result.errorMessage).to.eql(errorMessage); if (skippedScope) { expect(skippedScope.isDone()).to.eql(false, "Skipped scope should not be completed"); } executionScope.done(); // All normal API calls are executed } function testUploadedFormData() { const formData = uploadSpy.lastCall.args[0] as string; expect(typeof(formData)).to.eql("string", "Form Data should be string"); expect(formData).to.have.string(releaseFileContent, "Release file content should be sent"); expect(formData).to.have.string('name="ipa"', "There should be 'ipa' field in the form data"); expect(formData).to.have.string(`filename="${releaseFileName}"`, "Release file name is expected"); } function getCommandArgs(additionalArgs: string[]): CommandArgs { const args: string[] = ["-a", fakeAppIdentifier, "--token", fakeToken, "--env", "local"].concat(additionalArgs); return { args, command: ["distribute", "release"], commandPath: "FAKE" }; } function setupSuccessfulGetDistributionGroupUsersResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.get(`/v0.1/apps/${fakeAppOwner}/${fakeAppName}/distribution_groups/${fakeDistributionGroupName}/members`) .reply(200, ((uri: any, requestBody: any) => { return [{ /* Single user, fields are not used */}]; })); } function setupSuccessfulPostUploadResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.post(`/v0.1/apps/${fakeAppOwner}/${fakeAppName}/release_uploads`) .reply(201, ((uri: any, requestBody: any) => { postSymbolSpy(requestBody); return { upload_id: fakeReleaseUploadingId, upload_url: fakeHost + fakeUploadUrl }; })); } function setupSuccessfulUploadResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.post(fakeUploadUrl).reply(200, (uri: any, requestBody: any) => { uploadSpy(requestBody); }); } function setupFailedUploadResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.post(fakeUploadUrl).reply(500, (uri: any, requestBody: any) => { uploadSpy(requestBody); }); } function setupSuccessfulPatchUploadResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.patch(`/v0.1/apps/${fakeAppOwner}/${fakeAppName}/release_uploads/${fakeReleaseUploadingId}`, { status: "committed" }).reply(200, ((uri: any, requestBody: any) => { patchSymbolSpy(requestBody); return { release_url: fakeReleaseUrl }; })); } function setupSuccessfulAbortUploadResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.patch(`/v0.1/apps/${fakeAppOwner}/${fakeAppName}/release_uploads/${fakeReleaseUploadingId}`, { status: "aborted" }).reply(200, ((uri: any, requestBody: any) => { abortSymbolSpy(requestBody); return { }; })); } function setupSuccessfulCreateReleaseResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.put(`/v0.1/apps/${fakeAppOwner}/${fakeAppName}/releases/${fakeReleaseId}`, { release_notes: releaseNotes }).reply(200, ((uri: any, requestBody: any) => { distributeSpy(requestBody); return { version, short_version: shortVersion }; })); } function setupFailedCreateReleaseResponse(nockScope: Nock.Scope): Nock.Scope { return nockScope.put(`/v0.1/apps/${fakeAppOwner}/${fakeAppName}/releases/${fakeReleaseId}`, { release_notes: releaseNotes }).reply(404); } function setupSuccessfulAddGroupResponse(nockScope: Nock.Scope): Nock.Scope { const postAddReleaseGroupDestinationUrl = `/v0.1/apps/${fakeAppOwner}/${fakeAppName}/releases/${fakeReleaseId}/groups`; const expectedBody = { id: fakeGroupId, mandatory_update: false, notify_testers: true }; return nockScope.post(postAddReleaseGroupDestinationUrl, expectedBody) .reply(201, { id: fakeGroupId, mandatory_update: false, notify_testers: true }); } function setupFailedAddGroupResponse(nockScope: Nock.Scope): Nock.Scope { const postAddReleaseGroupDestinationUrl = `/v0.1/apps/${fakeAppOwner}/${fakeAppName}/releases/${fakeReleaseId}/groups`; const expectedBody = { id: fakeGroupId, mandatory_update: false, notify_testers: true }; return nockScope.post(postAddReleaseGroupDestinationUrl, expectedBody) .reply(404); } function setupSuccsessFulGetDistributionGroupResponse(nockScope: Nock.Scope): Nock.Scope { const getDistributionGroupUrl = `/v0.1/apps/${fakeAppOwner}/${fakeAppName}/distribution_groups/${fakeDistributionGroupName}`; return nockScope.get(getDistributionGroupUrl) .reply(200, { id: fakeGroupId, name: fakeDistributionGroupName, dismay_name: "my group", origin: "appcenter", is_public: false }); } function setupFailedGetDistributionGroupResponse(nockScope: Nock.Scope): Nock.Scope { const getDistributionGroupUrl = `/v0.1/apps/${fakeAppOwner}/${fakeAppName}/distribution_groups/${fakeDistributionGroupName}`; return nockScope.get(getDistributionGroupUrl) .reply(404); } });