UNPKG

code-push-cli

Version:

Management CLI for the CodePush service

1,294 lines (1,286 loc) 64 kB
var assert = require("assert"); var sinon = require("sinon"); var Q = require("q"); var path = require("path"); var cli = require("../definitions/cli"); var cmdexec = require("../script/command-executor"); var os = require("os"); function assertJsonDescribesObject(json, object) { // Make sure JSON is indented correctly assert.equal(json, JSON.stringify(object, null, 2)); } function clone(obj) { return JSON.parse(JSON.stringify(obj)); } function ensureInTestAppDirectory() { if (!~__dirname.indexOf("/resources/TestApp")) { process.chdir(__dirname + "/resources/TestApp"); } } function isDefined(object) { return object !== undefined && object !== null; } var NOW = 1471460856191; var DEFAULT_ACCESS_KEY_MAX_AGE = 1000 * 60 * 60 * 24 * 60; // 60 days var TEST_MACHINE_NAME = "Test machine"; var SdkStub = (function () { function SdkStub() { this.productionDeployment = { name: "Production", key: "6" }; this.stagingDeployment = { name: "Staging", key: "6", package: { appVersion: "1.0.0", description: "fgh", label: "v2", packageHash: "jkl", isMandatory: true, size: 10, blobUrl: "http://mno.pqr", uploadTime: 1000 } }; } SdkStub.prototype.getAccountInfo = function () { return Q({ email: "a@a.com" }); }; SdkStub.prototype.addAccessKey = function (name, ttl) { return Q({ key: "key123", createdTime: new Date().getTime(), name: name, expires: NOW + (isDefined(ttl) ? ttl : DEFAULT_ACCESS_KEY_MAX_AGE) }); }; SdkStub.prototype.patchAccessKey = function (oldName, newName, newTtl) { return Q({ createdTime: new Date().getTime(), name: newName, expires: NOW + (isDefined(newTtl) ? newTtl : DEFAULT_ACCESS_KEY_MAX_AGE) }); }; SdkStub.prototype.addApp = function (name, os, platform, manuallyProvisionDeployments) { if (manuallyProvisionDeployments === void 0) { manuallyProvisionDeployments = false; } return Q({ name: name, os: os, platform: platform, manuallyProvisionDeployments: manuallyProvisionDeployments }); }; SdkStub.prototype.addCollaborator = function (name, email) { return Q(null); }; SdkStub.prototype.addDeployment = function (appName, deploymentName) { return Q({ name: deploymentName, key: "6" }); }; SdkStub.prototype.clearDeploymentHistory = function (appName, deploymentName) { return Q(null); }; SdkStub.prototype.getAccessKeys = function () { return Q([{ createdTime: 0, name: "Test name", expires: NOW + DEFAULT_ACCESS_KEY_MAX_AGE }]); }; SdkStub.prototype.getSessions = function () { return Q([{ loggedInTime: 0, machineName: TEST_MACHINE_NAME }]); }; SdkStub.prototype.getApps = function () { return Q([{ name: "a", collaborators: { "a@a.com": { permission: "Owner", isCurrentAccount: true } }, deployments: ["Production", "Staging"] }, { name: "b", collaborators: { "a@a.com": { permission: "Owner", isCurrentAccount: true } }, deployments: ["Production", "Staging"] }]); }; SdkStub.prototype.getDeployments = function (appName) { if (appName === "a") { return Q([this.productionDeployment, this.stagingDeployment]); } return Q.reject(); }; SdkStub.prototype.getDeployment = function (appName, deploymentName) { if (appName === "a") { if (deploymentName === "Production") { return Q(this.productionDeployment); } else if (deploymentName === "Staging") { return Q(this.stagingDeployment); } } return Q.reject(); }; SdkStub.prototype.getDeploymentHistory = function (appName, deploymentName) { return Q([ { description: null, appVersion: "1.0.0", isMandatory: false, packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", blobUrl: "https://fakeblobstorage.net/storagev2/blobid1", uploadTime: 1447113596270, size: 1, label: "v1" }, { description: "New update - this update does a whole bunch of things, including testing linewrapping", appVersion: "1.0.1", isMandatory: false, packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", blobUrl: "https://fakeblobstorage.net/storagev2/blobid2", uploadTime: 1447118476669, size: 2, label: "v2" } ]); }; SdkStub.prototype.getDeploymentMetrics = function (appName, deploymentName) { return Q({ "1.0.0": { active: 123 }, "v1": { active: 789, downloaded: 456, failed: 654, installed: 987 }, "v2": { active: 123, downloaded: 321, failed: 789, installed: 456 } }); }; SdkStub.prototype.getCollaborators = function (app) { return Q({ "a@a.com": { permission: "Owner", isCurrentAccount: true }, "b@b.com": { permission: "Collaborator", isCurrentAccount: false } }); }; SdkStub.prototype.patchRelease = function (appName, deployment, label, updateMetaData) { return Q(null); }; SdkStub.prototype.promote = function (appName, sourceDeployment, destinationDeployment, updateMetaData) { return Q(null); }; SdkStub.prototype.release = function (appName, deploymentName) { return Q("Successfully released"); }; SdkStub.prototype.removeAccessKey = function (accessKeyId) { return Q(null); }; SdkStub.prototype.removeApp = function (appName) { return Q(null); }; SdkStub.prototype.removeCollaborator = function (name, email) { return Q(null); }; SdkStub.prototype.removeDeployment = function (appName, deploymentName) { return Q(null); }; SdkStub.prototype.removeSession = function (createdBy) { return Q(null); }; SdkStub.prototype.renameApp = function (app) { return Q(null); }; SdkStub.prototype.rollback = function (appName, deployment, targetRelease) { return Q(null); }; SdkStub.prototype.transferApp = function (app) { return Q(null); }; SdkStub.prototype.renameDeployment = function (appName, deploymentName) { return Q(null); }; return SdkStub; })(); exports.SdkStub = SdkStub; describe("CLI", function () { var log; var sandbox; var spawn; var wasConfirmed = true; var INVALID_RELEASE_FILE_ERROR_MESSAGE = "It is unnecessary to package releases in a .zip or binary file. Please specify the direct path to the update content's directory (e.g. /platforms/ios/www) or file (e.g. main.jsbundle)."; beforeEach(function () { wasConfirmed = true; sandbox = sinon.sandbox.create(); sandbox.stub(cmdexec, "confirm", function () { return Q(wasConfirmed); }); sandbox.stub(cmdexec, "createEmptyTempReleaseFolder", function () { return Q(null); }); log = sandbox.stub(cmdexec, "log", function (message) { }); spawn = sandbox.stub(cmdexec, "spawn", function (command, commandArgs) { return { stdout: { on: function () { } }, stderr: { on: function () { } }, on: function (event, callback) { callback(); } }; }); cmdexec.sdk = new SdkStub(); }); afterEach(function () { sandbox.restore(); }); it("accessKeyAdd creates access key with name and default ttl", function (done) { var command = { type: cli.CommandType.accessKeyAdd, name: "Test name" }; cmdexec.execute(command) .done(function () { sinon.assert.calledTwice(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = "Successfully created the \"Test name\" access key: key123"; assert.equal(actual, expected); actual = log.args[1][0]; expected = "Make sure to save this key value somewhere safe, since you won't be able to view it from the CLI again!"; assert.equal(actual, expected); done(); }); }); it("accessKeyAdd creates access key with name and specified ttl", function (done) { var ttl = 10000; var command = { type: cli.CommandType.accessKeyAdd, name: "Test name", ttl: ttl }; cmdexec.execute(command) .done(function () { sinon.assert.calledTwice(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = "Successfully created the \"Test name\" access key: key123"; assert.equal(actual, expected); actual = log.args[1][0]; expected = "Make sure to save this key value somewhere safe, since you won't be able to view it from the CLI again!"; assert.equal(actual, expected); done(); }); }); it("accessKeyPatch updates access key with new name", function (done) { var command = { type: cli.CommandType.accessKeyPatch, oldName: "Test name", newName: "Updated name" }; cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = "Successfully renamed the access key \"Test name\" to \"Updated name\"."; assert.equal(actual, expected); done(); }); }); /* it("accessKeyPatch updates access key with new ttl", (done: MochaDone): void => { var ttl = 10000; var command: cli.IAccessKeyPatchCommand = { type: cli.CommandType.accessKeyPatch, oldName: "Test name", ttl }; cmdexec.execute(command) .done((): void => { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual: string = log.args[0][0]; var expected = `Successfully changed the expiration date of the "Test name" access key to Wednesday, August 17, 2016 12:07 PM.`; assert.equal(actual, expected); done(); }); }); it("accessKeyPatch updates access key with new name and ttl", (done: MochaDone): void => { var ttl = 10000; var command: cli.IAccessKeyPatchCommand = { type: cli.CommandType.accessKeyPatch, oldName: "Test name", newName: "Updated name", ttl }; cmdexec.execute(command) .done((): void => { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual: string = log.args[0][0]; var expected = `Successfully renamed the access key "Test name" to "Updated name" and changed its expiration date to Wednesday, August 17, 2016 12:07 PM.`; assert.equal(actual, expected); done(); }); }); */ it("accessKeyList lists access key name and expires fields", function (done) { var command = { type: cli.CommandType.accessKeyList, format: "json" }; cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = [ { createdTime: 0, name: "Test name", expires: NOW + DEFAULT_ACCESS_KEY_MAX_AGE } ]; assertJsonDescribesObject(actual, expected); done(); }); }); it("accessKeyRemove removes access key", function (done) { var command = { type: cli.CommandType.accessKeyRemove, accessKey: "8" }; var removeAccessKey = sandbox.spy(cmdexec.sdk, "removeAccessKey"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(removeAccessKey); sinon.assert.calledWithExactly(removeAccessKey, "8"); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully removed the \"8\" access key."); done(); }); }); it("accessKeyRemove does not remove access key if cancelled", function (done) { var command = { type: cli.CommandType.accessKeyRemove, accessKey: "8" }; var removeAccessKey = sandbox.spy(cmdexec.sdk, "removeAccessKey"); wasConfirmed = false; cmdexec.execute(command) .done(function () { sinon.assert.notCalled(removeAccessKey); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Access key removal cancelled."); done(); }); }); it("appAdd reports new app name and ID", function (done) { var command = { type: cli.CommandType.appAdd, appName: "a", os: "ios", platform: "react-native" }; var addApp = sandbox.spy(cmdexec.sdk, "addApp"); var deploymentList = sandbox.spy(cmdexec, "deploymentList"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(addApp); sinon.assert.calledTwice(log); sinon.assert.calledWithExactly(log, "Successfully added the \"a\" app, along with the following default deployments:"); sinon.assert.calledOnce(deploymentList); done(); }); }); it("appList lists app names and ID's", function (done) { var command = { type: cli.CommandType.appList, format: "json" }; cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = [ { name: "a", collaborators: { "a@a.com": { permission: "Owner", isCurrentAccount: true } }, deployments: ["Production", "Staging"] }, { name: "b", collaborators: { "a@a.com": { permission: "Owner", isCurrentAccount: true } }, deployments: ["Production", "Staging"] } ]; assertJsonDescribesObject(actual, expected); done(); }); }); it("appRemove removes app", function (done) { var command = { type: cli.CommandType.appRemove, appName: "a" }; var removeApp = sandbox.spy(cmdexec.sdk, "removeApp"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(removeApp); sinon.assert.calledWithExactly(removeApp, "a"); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully removed the \"a\" app."); done(); }); }); it("appRemove does not remove app if cancelled", function (done) { var command = { type: cli.CommandType.appRemove, appName: "a" }; var removeApp = sandbox.spy(cmdexec.sdk, "removeApp"); wasConfirmed = false; cmdexec.execute(command) .done(function () { sinon.assert.notCalled(removeApp); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "App removal cancelled."); done(); }); }); it("appRename renames app", function (done) { var command = { type: cli.CommandType.appRename, currentAppName: "a", newAppName: "c" }; var renameApp = sandbox.spy(cmdexec.sdk, "renameApp"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(renameApp); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully renamed the \"a\" app to \"c\"."); done(); }); }); it("appTransfer transfers app", function (done) { var command = { type: cli.CommandType.appTransfer, appName: "a", email: "b@b.com" }; var transferApp = sandbox.spy(cmdexec.sdk, "transferApp"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(transferApp); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully transferred the ownership of app \"a\" to the account with email \"b@b.com\"."); done(); }); }); it("collaboratorAdd adds collaborator", function (done) { var command = { type: cli.CommandType.collaboratorAdd, appName: "a", email: "b@b.com" }; var addCollaborator = sandbox.spy(cmdexec.sdk, "addCollaborator"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(addCollaborator); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Collaborator invitation email for \"a\" sent to \"b@b.com\"."); done(); }); }); it("collaboratorList lists collaborators email and properties", function (done) { var command = { type: cli.CommandType.collaboratorList, appName: "a", format: "json" }; cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = { "collaborators": { "a@a.com": { permission: "Owner", isCurrentAccount: true }, "b@b.com": { permission: "Collaborator", isCurrentAccount: false } } }; assertJsonDescribesObject(actual, expected); done(); }); }); it("collaboratorRemove removes collaborator", function (done) { var command = { type: cli.CommandType.collaboratorRemove, appName: "a", email: "b@b.com" }; var removeCollaborator = sandbox.spy(cmdexec.sdk, "removeCollaborator"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(removeCollaborator); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully removed \"b@b.com\" as a collaborator from the app \"a\"."); done(); }); }); it("deploymentAdd reports new app name and ID", function (done) { var command = { type: cli.CommandType.deploymentAdd, appName: "a", deploymentName: "b", default: false }; var addDeployment = sandbox.spy(cmdexec.sdk, "addDeployment"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(addDeployment); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully added the \"b\" deployment with key \"6\" to the \"a\" app."); done(); }); }); it("deploymentHistoryClear clears deployment", function (done) { var command = { type: cli.CommandType.deploymentHistoryClear, appName: "a", deploymentName: "Staging" }; var clearDeployment = sandbox.spy(cmdexec.sdk, "clearDeploymentHistory"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(clearDeployment); sinon.assert.calledWithExactly(clearDeployment, "a", "Staging"); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully cleared the release history associated with the \"Staging\" deployment from the \"a\" app."); done(); }); }); it("deploymentHistoryClear does not clear deployment if cancelled", function (done) { var command = { type: cli.CommandType.deploymentHistoryClear, appName: "a", deploymentName: "Staging" }; var clearDeployment = sandbox.spy(cmdexec.sdk, "clearDeploymentHistory"); wasConfirmed = false; cmdexec.execute(command) .done(function () { sinon.assert.notCalled(clearDeployment); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Clear deployment cancelled."); done(); }); }); it("deploymentList lists deployment names, deployment keys, and package information", function (done) { var command = { type: cli.CommandType.deploymentList, appName: "a", format: "json", displayKeys: true }; cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = [ { name: "Production", key: "6" }, { name: "Staging", key: "6", package: { appVersion: "1.0.0", description: "fgh", label: "v2", packageHash: "jkl", isMandatory: true, size: 10, blobUrl: "http://mno.pqr", uploadTime: 1000, metrics: { active: 123, downloaded: 321, failed: 789, installed: 456, totalActive: 1035 } } } ]; assertJsonDescribesObject(actual, expected); done(); }); }); it("deploymentRemove removes deployment", function (done) { var command = { type: cli.CommandType.deploymentRemove, appName: "a", deploymentName: "Staging" }; var removeDeployment = sandbox.spy(cmdexec.sdk, "removeDeployment"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(removeDeployment); sinon.assert.calledWithExactly(removeDeployment, "a", "Staging"); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully removed the \"Staging\" deployment from the \"a\" app."); done(); }); }); it("deploymentRemove does not remove deployment if cancelled", function (done) { var command = { type: cli.CommandType.deploymentRemove, appName: "a", deploymentName: "Staging" }; var removeDeployment = sandbox.spy(cmdexec.sdk, "removeDeployment"); wasConfirmed = false; cmdexec.execute(command) .done(function () { sinon.assert.notCalled(removeDeployment); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Deployment removal cancelled."); done(); }); }); it("deploymentRename renames deployment", function (done) { var command = { type: cli.CommandType.deploymentRename, appName: "a", currentDeploymentName: "Staging", newDeploymentName: "c" }; var renameDeployment = sandbox.spy(cmdexec.sdk, "renameDeployment"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(renameDeployment); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully renamed the \"Staging\" deployment to \"c\" for the \"a\" app."); done(); }); }); it("deploymentHistory lists package history information", function (done) { var command = { type: cli.CommandType.deploymentHistory, appName: "a", deploymentName: "Staging", format: "json", displayAuthor: false }; var getDeploymentHistory = sandbox.spy(cmdexec.sdk, "getDeploymentHistory"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(getDeploymentHistory); sinon.assert.calledOnce(log); assert.equal(log.args[0].length, 1); var actual = log.args[0][0]; var expected = [ { description: null, appVersion: "1.0.0", isMandatory: false, packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", blobUrl: "https://fakeblobstorage.net/storagev2/blobid1", uploadTime: 1447113596270, size: 1, label: "v1", metrics: { active: 789, downloaded: 456, failed: 654, installed: 987, totalActive: 1035 } }, { description: "New update - this update does a whole bunch of things, including testing linewrapping", appVersion: "1.0.1", isMandatory: false, packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", blobUrl: "https://fakeblobstorage.net/storagev2/blobid2", uploadTime: 1447118476669, size: 2, label: "v2", metrics: { active: 123, downloaded: 321, failed: 789, installed: 456, totalActive: 1035 } } ]; assertJsonDescribesObject(actual, expected); done(); }); }); it("patch command successfully updates specific label", function (done) { var command = { type: cli.CommandType.patch, appName: "a", deploymentName: "Staging", label: "v1", disabled: false, description: "Patched", mandatory: true, rollout: 25, appStoreVersion: "1.0.1" }; var patch = sandbox.spy(cmdexec.sdk, "patchRelease"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(patch); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully updated the \"v1\" release of \"a\" app's \"Staging\" deployment."); done(); }); }); it("patch command successfully updates latest release", function (done) { var command = { type: cli.CommandType.patch, appName: "a", deploymentName: "Staging", label: null, disabled: false, description: "Patched", mandatory: true, rollout: 25, appStoreVersion: "1.0.1" }; var patch = sandbox.spy(cmdexec.sdk, "patchRelease"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(patch); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully updated the \"latest\" release of \"a\" app's \"Staging\" deployment."); done(); }); }); it("patch command successfully updates without appStoreVersion", function (done) { var command = { type: cli.CommandType.patch, appName: "a", deploymentName: "Staging", label: null, disabled: false, description: "Patched", mandatory: true, rollout: 25, appStoreVersion: null }; var patch = sandbox.spy(cmdexec.sdk, "patchRelease"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(patch); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully updated the \"latest\" release of \"a\" app's \"Staging\" deployment."); done(); }); }); it("patch command fails if no properties were specified for update", function (done) { var command = { type: cli.CommandType.patch, appName: "a", deploymentName: "Staging", label: null, disabled: null, description: null, mandatory: null, rollout: null, appStoreVersion: null }; var patch = sandbox.spy(cmdexec.sdk, "patchRelease"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "At least one property must be specified to patch a release."); sinon.assert.notCalled(patch); done(); }) .done(); }); it("promote works successfully", function (done) { var command = { type: cli.CommandType.promote, appName: "a", sourceDeploymentName: "Staging", destDeploymentName: "Production", description: "Promoted", mandatory: true, rollout: 25, appStoreVersion: "1.0.1" }; var promote = sandbox.spy(cmdexec.sdk, "promote"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(promote); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully promoted the \"Staging\" deployment of the \"a\" app to the \"Production\" deployment."); done(); }); }); it("promote works successfully without appStoreVersion", function (done) { var command = { type: cli.CommandType.promote, appName: "a", sourceDeploymentName: "Staging", destDeploymentName: "Production", description: "Promoted", mandatory: true, rollout: 25, appStoreVersion: null }; var promote = sandbox.spy(cmdexec.sdk, "promote"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(promote); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully promoted the \"Staging\" deployment of the \"a\" app to the \"Production\" deployment."); done(); }); }); it("rollback works successfully", function (done) { var command = { type: cli.CommandType.rollback, appName: "a", deploymentName: "Staging", targetRelease: "v2" }; var rollback = sandbox.spy(cmdexec.sdk, "rollback"); cmdexec.execute(command) .done(function () { sinon.assert.calledOnce(rollback); sinon.assert.calledOnce(log); sinon.assert.calledWithExactly(log, "Successfully performed a rollback on the \"Staging\" deployment of the \"a\" app."); done(); }); }); it("release doesn't allow non valid semver ranges", function (done) { var command = { type: cli.CommandType.release, appName: "a", deploymentName: "Staging", description: "test releasing zip file", mandatory: false, rollout: null, appStoreVersion: "not semver", package: "./resources" }; releaseHelperFunction(command, done, "Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); }); it("release doesn't allow releasing .zip file", function (done) { var command = { type: cli.CommandType.release, appName: "a", deploymentName: "Staging", description: "test releasing zip file", mandatory: false, rollout: null, appStoreVersion: "1.0.0", package: "/fake/path/test/file.zip" }; releaseHelperFunction(command, done, INVALID_RELEASE_FILE_ERROR_MESSAGE); }); it("release doesn't allow releasing .ipa file", function (done) { var command = { type: cli.CommandType.release, appName: "a", deploymentName: "Staging", description: "test releasing ipa file", mandatory: false, rollout: null, appStoreVersion: "1.0.0", package: "/fake/path/test/file.ipa" }; releaseHelperFunction(command, done, INVALID_RELEASE_FILE_ERROR_MESSAGE); }); it("release doesn't allow releasing .apk file", function (done) { var command = { type: cli.CommandType.release, appName: "a", deploymentName: "Staging", description: "test releasing apk file", mandatory: false, rollout: null, appStoreVersion: "1.0.0", package: "/fake/path/test/file.apk" }; releaseHelperFunction(command, done, INVALID_RELEASE_FILE_ERROR_MESSAGE); }); it("release-cordova fails if Cordova project cannot be prepared", function (done) { testReleaseCordovaFailure(false, done); }); it("release-cordova fails if Cordova project cannot be built", function (done) { testReleaseCordovaFailure(true, done); }); function testReleaseCordovaFailure(build, done) { var command = { type: cli.CommandType.releaseCordova, appName: "a", appStoreVersion: null, build: build, deploymentName: "Staging", description: "Test invalid project", mandatory: false, rollout: null, platform: "ios" }; var cordovaCommand = build ? "build" : "prepare"; var execSync = sandbox.stub(cmdexec, "execSync", function (command, options) { throw "Failed " + cordovaCommand; }); var release = sandbox.spy(cmdexec, "release"); var releaseCordova = sandbox.spy(cmdexec, "releaseCordova"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Unable to " + cordovaCommand + " project. Please ensure that the CWD represents a Cordova project and that the \"" + command.platform + "\" platform was added by running \"cordova platform add " + command.platform + "\"."); sinon.assert.notCalled(release); done(); }) .done(); } it("release-cordova fails if CWD does not contain config.xml", function (done) { var command = { type: cli.CommandType.releaseCordova, appName: "a", appStoreVersion: null, build: false, deploymentName: "Staging", description: "Test missing config.xml", mandatory: false, rollout: null, platform: "ios" }; var execSync = sandbox.stub(cmdexec, "execSync", function (command, options) { }); var release = sandbox.spy(cmdexec, "release"); var releaseCordova = sandbox.spy(cmdexec, "releaseCordova"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Unable to find or read \"config.xml\" in the CWD. The \"release-cordova\" command must be executed in a Cordova project folder."); sinon.assert.notCalled(release); sinon.assert.calledOnce(execSync); done(); }) .done(); }); it("release-cordova fails if platform is invalid", function (done) { var command = { type: cli.CommandType.releaseCordova, appName: "a", appStoreVersion: null, build: false, deploymentName: "Staging", description: "Test invalid platform", mandatory: false, rollout: null, platform: "blackberry", }; var release = sandbox.spy(cmdexec, "release"); var releaseCordova = sandbox.spy(cmdexec, "releaseCordova"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Platform must be either \"ios\" or \"android\"."); sinon.assert.notCalled(release); sinon.assert.notCalled(spawn); done(); }) .done(); }); it("release-cordova defaults appStoreVersion to value pulled from config.xml", function (done) { var command = { type: cli.CommandType.releaseCordova, appName: "a", appStoreVersion: null, build: false, deploymentName: "Staging", description: "Test config.xml app version read", mandatory: false, rollout: null, platform: "ios" }; var oldWd = process.cwd(); ensureInTestAppDirectory(); var expectedReleaseCommand = { type: cli.CommandType.release, appName: "a", appStoreVersion: "0.0.1", build: false, deploymentName: "Staging", description: "Test config.xml app version read", mandatory: false, rollout: null, package: path.join(process.cwd(), "platforms", "ios", "www"), platform: "ios" }; var execSync = sandbox.stub(cmdexec, "execSync", function (command, options) { }); var release = sandbox.stub(cmdexec, "release"); var releaseCordova = sandbox.spy(cmdexec, "releaseCordova"); cmdexec.execute(command) .then(function (compiledReleaseCommand) { sinon.assert.calledOnce(execSync); sinon.assert.calledWith(release, expectedReleaseCommand); done(); }) .catch(function (err) { done(new Error("Threw error. " + err.message)); }) .done(function () { process.chdir(oldWd); }); }); it("release-cordova points 'package' to the built folder for android", function (done) { var command = { type: cli.CommandType.releaseCordova, appName: "a", appStoreVersion: null, build: true, deploymentName: "Staging", description: "Test android package resolution", mandatory: false, rollout: null, platform: "android" }; var oldWd = process.cwd(); ensureInTestAppDirectory(); var expectedReleaseCommand = { type: cli.CommandType.release, appName: "a", appStoreVersion: "0.0.1", build: true, deploymentName: "Staging", description: "Test android package resolution", mandatory: false, rollout: null, package: path.join(process.cwd(), "platforms", "android", "assets", "www"), platform: "android" }; var execSync = sandbox.stub(cmdexec, "execSync", function (command, options) { }); var release = sandbox.stub(cmdexec, "release"); var releaseCordova = sandbox.spy(cmdexec, "releaseCordova"); cmdexec.execute(command) .then(function (compiledReleaseCommand) { sinon.assert.calledOnce(execSync); sinon.assert.calledWith(release, expectedReleaseCommand); done(); }) .catch(function (err) { done(new Error("Threw error. " + err.message)); }) .done(function () { process.chdir(oldWd); }); }); it("release-react fails if CWD does not contain package.json", function (done) { var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: null, deploymentName: "Staging", description: "Test invalid folder", mandatory: false, rollout: null, platform: "ios" }; var release = sandbox.spy(cmdexec, "release"); var releaseReact = sandbox.spy(cmdexec, "releaseReact"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Unable to find or read \"package.json\" in the CWD. The \"release-react\" command must be executed in a React Native project folder."); sinon.assert.notCalled(release); sinon.assert.notCalled(spawn); done(); }) .done(); }); it("release-react fails if entryFile does not exist", function (done) { var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: null, deploymentName: "Staging", description: "Test invalid entryFile", entryFile: "doesntexist.js", mandatory: false, rollout: null, platform: "ios" }; ensureInTestAppDirectory(); var release = sandbox.spy(cmdexec, "release"); var releaseReact = sandbox.spy(cmdexec, "releaseReact"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Entry file \"doesntexist.js\" does not exist."); sinon.assert.notCalled(release); sinon.assert.notCalled(spawn); done(); }) .done(); }); it("release-react fails if platform is invalid", function (done) { var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: null, deploymentName: "Staging", description: "Test invalid platform", mandatory: false, rollout: null, platform: "blackberry", }; ensureInTestAppDirectory(); var release = sandbox.spy(cmdexec, "release"); var releaseReact = sandbox.spy(cmdexec, "releaseReact"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Platform must be \"android\", \"ios\", or \"windows\"."); sinon.assert.notCalled(release); sinon.assert.notCalled(spawn); done(); }) .done(); }); it("release-react fails if targetBinaryRange is not a valid semver range expression", function (done) { var bundleName = "bundle.js"; var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: "notsemver", bundleName: bundleName, deploymentName: "Staging", description: "Test uses targetBinaryRange", mandatory: false, rollout: null, platform: "android", sourcemapOutput: "index.android.js.map" }; ensureInTestAppDirectory(); var release = sandbox.stub(cmdexec, "release", function () { return Q(null); }); var releaseReact = sandbox.spy(cmdexec, "releaseReact"); cmdexec.execute(command) .then(function () { done(new Error("Did not throw error.")); }) .catch(function (err) { assert.equal(err.message, "Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); sinon.assert.notCalled(release); sinon.assert.notCalled(spawn); done(); }) .done(); }); it("release-react defaults entry file to index.{platform}.js if not provided", function (done) { var bundleName = "bundle.js"; var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: null, bundleName: bundleName, deploymentName: "Staging", description: "Test default entry file", mandatory: false, rollout: null, platform: "ios" }; ensureInTestAppDirectory(); var release = sandbox.stub(cmdexec, "release", function () { return Q(null); }); cmdexec.execute(command) .then(function () { var releaseCommand = command; releaseCommand.package = path.join(os.tmpdir(), "CodePush/CodePush"); releaseCommand.appStoreVersion = "1.2.3"; sinon.assert.calledOnce(spawn); var spawnCommand = spawn.args[0][0]; var spawnCommandArgs = spawn.args[0][1].join(" "); assert.equal(spawnCommand, "node"); assert.equal(spawnCommandArgs, path.join("node_modules", "react-native", "local-cli", "cli.js") + " bundle --assets-dest " + path.join(os.tmpdir(), "CodePush/CodePush") + " --bundle-output " + path.join(os.tmpdir(), "CodePush/CodePush", bundleName) + " --dev false --entry-file index.ios.js --platform ios"); assertJsonDescribesObject(JSON.stringify(release.args[0][0], null, 2), releaseCommand); done(); }) .done(); }); it("release-react defaults bundle name to \"main.jsbundle\" if not provided and platform is \"ios\"", function (done) { var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: null, deploymentName: "Staging", description: "Test default entry file", mandatory: false, rollout: null, platform: "ios" }; ensureInTestAppDirectory(); var release = sandbox.stub(cmdexec, "release", function () { return Q(null); }); cmdexec.execute(command) .then(function () { var releaseCommand = clone(command); var packagePath = path.join(os.tmpdir(), "CodePush/CodePush"); releaseCommand.package = packagePath; releaseCommand.appStoreVersion = "1.2.3"; sinon.assert.calledOnce(spawn); var spawnCommand = spawn.args[0][0]; var spawnCommandArgs = spawn.args[0][1].join(" "); assert.equal(spawnCommand, "node"); assert.equal(spawnCommandArgs, path.join("node_modules", "react-native", "local-cli", "cli.js") + " bundle --assets-dest " + packagePath + " --bundle-output " + path.join(packagePath, "main.jsbundle") + " --dev false --entry-file index.ios.js --platform ios"); assertJsonDescribesObject(JSON.stringify(release.args[0][0], null, 2), releaseCommand); done(); }) .done(); }); it("release-react defaults bundle name to \"index.android.bundle\" if not provided and platform is \"android\"", function (done) { var command = { type: cli.CommandType.releaseReact, appName: "a", appStoreVersion: null, deploymentName: "Staging",