Repository: serverless/serverless-azure-functions
Pull request #324 (2019-09-09 20:04:52)

fix: Throw original error if no previous deployments
If a first-time deployment fails, it will crash with a weird error because the last deployment is undefined. This handles that scenario.


Patch:
diff --git a/src/services/armService.ts b/src/services/armService.ts
index cde93a4..a574c01 100644
--- a/src/services/armService.ts
+++ b/src/services/armService.ts
@@ -95,7 +95,7 @@ export class ArmService extends BaseService {
     this.applyEnvironmentVariables(deployment);
 
     const resourceService = new ResourceService(this.serverless, this.options);
-    const previous = await resourceService.getLastDeploymentTemplate();
+    const previous = await resourceService.getPreviousDeploymentTemplate();
 
     if (this.areDeploymentsEqual(deployment, previous)) {
       this.log("Generated template same as previous. Skipping ARM deployment");
@@ -130,11 +130,14 @@ export class ArmService extends BaseService {
       this.log("-> ARM deployment complete");
       return result;
     } catch (err) {
-      const lastDeployment = await resourceService.getLastDeployment();
-      const errorDetails: DeploymentExtendedError = lastDeployment.properties["error"];
-      if (errorDetails) {
-        throw new Error(this.deploymentErrorToString(errorDetails));
+      const previousDeployment = await resourceService.getPreviousDeployment();
+      if (previousDeployment) {
+        const errorDetails: DeploymentExtendedError = previousDeployment.properties["error"];
+        if (errorDetails) {
+          throw new Error(this.deploymentErrorToString(errorDetails));
+        }
       }
+      throw err;
     }
   }
 
diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts
index dac7acf..6a03265 100644
--- a/src/services/resourceService.ts
+++ b/src/services/resourceService.ts
@@ -29,7 +29,7 @@ export class ResourceService extends BaseService {
   /**
    * Get the most recent resource group deployment
    */
-  public async getLastDeployment() {
+  public async getPreviousDeployment() {
     const deployments = await this.getDeployments();
     if (deployments && deployments.length) {
       return deployments[0];
@@ -39,8 +39,8 @@ export class ResourceService extends BaseService {
   /**
    * Get template from last resource group deployment
    */
-  public async getLastDeploymentTemplate(): Promise<ArmDeployment> {
-    const deployment = await this.getLastDeployment();
+  public async getPreviousDeploymentTemplate(): Promise<ArmDeployment> {
+    const deployment = await this.getPreviousDeployment();
     if (!deployment || deployment.properties.provisioningState !== ArmTemplateProvisioningState.SUCCEEDED) {
       return;
     }


Test patch:
diff --git a/src/services/armService.test.ts b/src/services/armService.test.ts
index a926ac1..e458276 100644
--- a/src/services/armService.test.ts
+++ b/src/services/armService.test.ts
@@ -327,7 +327,7 @@ describe("Arm Service", () => {
 
     it("Throws more detailed error message upon failed ARM deployment", async () => {
       Deployments.prototype.createOrUpdate = jest.fn(() => Promise.reject(null));
-      const lastDeploymentError: DeploymentExtendedError = {
+      const previousDeploymentError: DeploymentExtendedError = {
         code: "DeploymentFailed",
         message: "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.",
         details: [
@@ -341,9 +341,9 @@ describe("Arm Service", () => {
           }
         ]
       }
-      ResourceService.prototype.getLastDeployment = jest.fn(() => Promise.resolve({
+      ResourceService.prototype.getPreviousDeployment = jest.fn(() => Promise.resolve({
         properties: {
-          error: lastDeploymentError
+          error: previousDeploymentError
         }
       })) as any;
       const deployment: ArmDeployment = {
@@ -351,7 +351,7 @@ describe("Arm Service", () => {
         template: MockFactory.createTestArmTemplate()
       };
       deployment.parameters.param1.value = "3"
-      const { code, message, details } = lastDeploymentError;
+      const { code, message, details } = previousDeploymentError;
       let errorPattern = [
         code,
         message,
@@ -401,5 +401,42 @@ describe("Arm Service", () => {
       expect(call[1]).toMatch(expectedDeploymentNameRegex);
       expect(call[2]).toEqual(expectedDeployment);
     });
+    
+    it("Throws original error when there has not been a previous deployment", async () => {
+      const originalError = new Error("original error message");
+      Deployments.prototype.createOrUpdate = jest.fn(() => Promise.reject(originalError));
+      const previousDeploymentError: DeploymentExtendedError = {
+        code: "DeploymentFailed",
+        message: "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.",
+        details: [
+          {
+            code: "ServiceAlreadyExists",
+            message: "Api service already exists: abc-123-apim"
+          },
+          {
+            code: "StorageAccountAlreadyTaken",
+            message: "The storage account named ABC123 is already taken."
+          }
+        ]
+      }
+      ResourceService.prototype.getPreviousDeployment = jest.fn(() => Promise.resolve(undefined)) as any;
+      const deployment: ArmDeployment = {
+        parameters: MockFactory.createTestParameters(),
+        template: MockFactory.createTestArmTemplate()
+      };
+      deployment.parameters.param1.value = "3"
+      const { code, message, details } = previousDeploymentError;
+      let errorPattern = [
+        code,
+        message,
+        details[0].code,
+        details[0].message,
+        details[1].code,
+        details[1].message
+      ].join(".*")
+      await expect(service.deployTemplate(deployment))
+        .rejects
+        .toThrowError(originalError);
+    });
   });
 });