Repository: serverless/serverless-azure-functions
Pull request #339 (2019-09-17 21:48:02)

fix: Sync triggers on external package deployment
<!--
1. Please check out and follow our Contributing Guidelines: https://github.com/serverless/serverless-azure-functions/CONTRIBUTING.md
2. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
3. Please follow the template, otherwise we'll have to ask you to update it and it will take longer until your PR is merged
-->

## What did you implement:

Fix updating of function app settings (SDK call wasn't working) and syncing triggers for function apps running from external package.

<!--
Briefly describe the feature if no issue exists for this PR
-->

## How did you implement it:

- Updated the `syncTriggers` function to use [the correct URL](https://docs.microsoft.com/en-us/azure/azure-functions/functions-deployment-technologies#trigger-syncing)
- Updated the `updateFunctionAppSetting` function to just make a rest API call instead of using the SDK. The SDK call now gives the error message: `Error: Failed to update function app settings: Error: {"Code":"BadRequest","Message":"SiteAppSettings object is not present in the request body...`. I checked with the documentation and found matching GitHub issues. Rather than the SDK call, just making a REST request according to [the docs](https://docs.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings)

<!--
If this is a nontrivial change please briefly describe your implementation so its easy for us to understand and review your code.
-->

## How can we verify it:

Deploy a function app with the following configuration:

```yaml
# serverless.yml
provider:
    deployment:
        external: true
```
<!--
Add any applicable config, commands, screenshots or other resources
to make it easy for us to verify this works. The easier you make it for us
to review a PR, the faster we can review and merge it.

Examples:
* serverless.yml - Fully functioning to easily deploy changes
* Screenshots - Showing the difference between your output and the master
* Cloud Configuration - List cloud resources and show that the correct configuration is in place (e.g. AWS CLI commands)
* Other - Anything else that comes to mind to help us evaluate
-->

## Todos:

_**Note: Run `npm run test:ci` to run all validation checks on proposed changes**_

- [x] Write tests and confirm existing functionality is not broken.  
       **Validate via `npm test`**
- [x] Write documentation
- [x] Ensure there are no lint errors.  
       **Validate via `npm run lint`**  
       _Note: Some reported issues can be automatically fixed by running `npm run lint:fix`_
- [x] Make sure code coverage hasn't dropped
- [x] Provide verification config / commands / resources
- [x] Enable "Allow edits from maintainers" for this PR
- [x] Update the messages below

**_Is this ready for review?:_** YES
**_Is it a breaking change?:_** NO


codecov[bot]: # [Codecov](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=h1) Report
> Merging [#339](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=desc) into [dev](https://codecov.io/gh/serverless/serverless-azure-functions/commit/084b4c1d9ef958ffda4a0f4a9e100a51b10c0906?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `73.91%`.

[![Impacted file tree graph](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339/graphs/tree.svg?width=650&token=ro919H5eps&height=150&src=pr)](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              dev     #339      +/-   ##
==========================================
+ Coverage   94.67%   94.69%   +0.02%     
==========================================
  Files          47       47              
  Lines        1727     1734       +7     
  Branches      242      271      +29     
==========================================
+ Hits         1635     1642       +7     
  Misses         92       92
```


| [Impacted Files](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/shared/utils.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339/diff?src=pr&el=tree#diff-c3JjL3NoYXJlZC91dGlscy50cw==) | `94.56% <ø> (+2.01%)` | :arrow_up: |
| [src/services/packageService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL3BhY2thZ2VTZXJ2aWNlLnRz) | `100% <100%> (ø)` | :arrow_up: |
| [src/services/baseService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2Jhc2VTZXJ2aWNlLnRz) | `95.83% <50%> (+0.08%)` | :arrow_up: |
| [src/services/apimService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2FwaW1TZXJ2aWNlLnRz) | `94.07% <75%> (ø)` | :arrow_up: |
| [src/services/functionAppService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2Z1bmN0aW9uQXBwU2VydmljZS50cw==) | `86.61% <78.57%> (-0.79%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=footer). Last update [084b4c1...f88dd75](https://codecov.io/gh/serverless/serverless-azure-functions/pull/339?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

tbarlow12: > Since you are modifying a yaml config property this should be treated as a breaking change unless we support both `runFromBlobUrl` as well as `external`.

@wbreza I can add a backfill similar to the `location` and `region`. Good catch

Patch:
diff --git a/src/config.ts b/src/config.ts
index 88a1e45..d7dbff7 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -3,7 +3,7 @@ export const configConstants = {
   deploymentConfig: {
     container: "deployment-artifacts",
     rollback: true,
-    runFromBlobUrl: false,
+    external: false,
   },
   naming: {
     maxLength: {
diff --git a/src/models/serverless.ts b/src/models/serverless.ts
index 55a59cf..78be3ea 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -25,7 +25,7 @@ export interface FunctionAppConfig extends ResourceConfig {
 export interface DeploymentConfig {
   rollback?: boolean;
   container?: string;
-  runFromBlobUrl?: boolean;
+  external?: boolean;
 }
 
 export interface ServerlessAzureProvider {
diff --git a/src/services/apimService.ts b/src/services/apimService.ts
index 932f734..110a489 100644
--- a/src/services/apimService.ts
+++ b/src/services/apimService.ts
@@ -231,7 +231,7 @@ export class ApimService extends BaseService {
       return api;
     } catch (e) {
       this.log("Error creating APIM API");
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
@@ -264,7 +264,7 @@ export class ApimService extends BaseService {
       });
     } catch (e) {
       this.log("Error creating APIM Backend");
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
@@ -309,7 +309,7 @@ export class ApimService extends BaseService {
       return result;
     } catch (e) {
       this.log(`Error deploying API operation ${functionName}`);
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
@@ -331,7 +331,7 @@ export class ApimService extends BaseService {
       });
     } catch (e) {
       this.log("Error creating APIM Property");
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index 730a22d..13caf56 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -96,7 +96,7 @@ export abstract class BaseService {
       fs.createReadStream(filePath).pipe(
         request(requestOptions, (err, response) => {
           if (err) {
-            this.log(JSON.stringify(err, null, 4));
+            this.log(this.stringify(err));
             return reject(err);
           }
           resolve(response);
@@ -114,7 +114,11 @@ export abstract class BaseService {
   }  
 
   protected prettyPrint(object: any) {
-    this.log(JSON.stringify(object, null, 2));
+    this.log(this.stringify(object));
+  }
+
+  protected stringify(object: any): string {
+    return JSON.stringify(object, null, 2);
   }
 
   protected getOption(key: string, defaultValue?: any) {
diff --git a/src/services/configService.ts b/src/services/configService.ts
index 4a2fe25..3af065f 100644
--- a/src/services/configService.ts
+++ b/src/services/configService.ts
@@ -1,7 +1,7 @@
 import Serverless from "serverless";
 import Service from "serverless/classes/Service";
 import configConstants from "../config";
-import { ServerlessAzureConfig, ServerlessAzureFunctionConfig } from "../models/serverless";
+import { ServerlessAzureConfig, ServerlessAzureFunctionConfig, DeploymentConfig } from "../models/serverless";
 import { constants } from "../shared/constants";
 import { Utils } from "../shared/utils";
 import { AzureNamingService, AzureNamingServiceOptions } from "./namingService";
@@ -70,6 +70,10 @@ export class ConfigService {
     )
   }
 
+  public getDeploymentConfig(): DeploymentConfig {
+    return this.config.provider.deployment;
+  }
+
   /**
    * Get rollback-configured artifact name. Contains `-t{timestamp}`
    * Takes name of deployment and replaces `rg-deployment` or `deployment` with `artifact`
@@ -168,6 +172,11 @@ export class ConfigService {
       this.getOption("resourceGroup", config.provider.resourceGroup)
     ) || AzureNamingService.getResourceName(options);
 
+    // Get property from `runFromBlobUrl` for backwards compatability
+    if (deployment && deployment.external === undefined && deployment["runFromBlobUrl"] !== undefined) {
+      deployment.external = deployment["runFromBlobUrl"];
+    }
+
     config.provider.deployment = {
       ...configConstants.deploymentConfig,
       ...deployment
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 3eb5bd4..9853302 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -63,13 +63,20 @@ export class FunctionAppService extends BaseService {
     return await this.sendApiRequest("DELETE", deleteFunctionUrl);
   }
 
-  public async syncTriggers(functionApp: Site) {
+  public async syncTriggers(functionApp: Site, properties: { [propertyName: string]: string }) {
     Guard.null(functionApp);
 
     this.log("Syncing function triggers");
 
-    const syncTriggersUrl = `${this.baseUrl}${functionApp.id}/syncfunctiontriggers?api-version=2016-08-01`;
-    return await this.sendApiRequest("POST", syncTriggersUrl);
+    const syncTriggersUrl = `${this.baseUrl}/subscriptions/${this.subscriptionId}` +
+      `/resourceGroups/${this.resourceGroup}/providers/Microsoft.Web/sites/${functionApp.name}` +
+      "/syncfunctiontriggers?api-version=2016-08-01";
+
+    try {
+      return await this.sendApiRequest("POST", syncTriggersUrl, { data: { properties } });
+    } catch (err) {
+      throw new Error(`Error syncing function app triggers: ${err}`)
+    }
   }
 
   public async cleanUp(functionApp: Site) {
@@ -101,7 +108,7 @@ export class FunctionAppService extends BaseService {
 
         if (listFunctionsResponse.status !== 200 || listFunctionsResponse.data.value.length === 0) {
           this.log(`-> Function App not ready. Retry ${retries++} of ${FunctionAppService.retryCount}...`);
-          const response = JSON.stringify(listFunctionsResponse.data, null, 2);
+          const response = this.stringify(listFunctionsResponse.data);
           throw new Error(
             `The function app is taking longer than usual to be provisioned. Please try again soon.
             Response error data: \n${response}`
@@ -136,7 +143,7 @@ export class FunctionAppService extends BaseService {
 
         if (getFunctionResponse.status !== 200) {
           this.log("-> Function app not ready. Retrying...")
-          throw new Error(JSON.stringify(response.data, null, 2));
+          throw new Error(this.stringify(response.data));
         }
 
         return getFunctionResponse;
@@ -155,7 +162,7 @@ export class FunctionAppService extends BaseService {
 
     const functionZipFile = this.getFunctionZipFile();
 
-    if (this.config.provider.deployment.runFromBlobUrl) {
+    if (this.config.provider.deployment.external) {
       this.log("Updating function app setting to run from external package...");
       await this.uploadZippedArtifactToBlobStorage(functionZipFile);
 
@@ -164,13 +171,17 @@ export class FunctionAppService extends BaseService {
         this.artifactName
       );
 
-      await this.updateFunctionAppSetting(
+      const response = await this.updateFunctionAppSetting(
         functionApp,
         configConstants.runFromPackageSetting,
         sasUrl
       );
+
+      await this.syncTriggers(functionApp, response.properties);
     } else {
       await Promise.all([
+        // Can run in parallel if also uploading to function app
+        // Needs to happen first if `external` is true
         this.uploadZippedArtifactToBlobStorage(functionZipFile),
         this.uploadZippedArfifactToFunctionApp(functionApp, functionZipFile)
       ]);
@@ -257,7 +268,16 @@ export class FunctionAppService extends BaseService {
   public async updateFunctionAppSetting(functionApp: Site, setting: string, value: string) {
     const { properties } = await this.webClient.webApps.listApplicationSettings(this.resourceGroup, functionApp.name);
     properties[setting] = value;
-    await this.webClient.webApps.updateApplicationSettings(this.resourceGroup, functionApp.name, properties);
+    try {
+      const url = `${this.baseUrl}/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}` +
+        `/providers/Microsoft.Web/sites/${functionApp.name}/config/appsettings?api-version=2016-08-01`;
+
+      const response = await this.sendApiRequest("PUT", url, { data: { properties } });
+      return response.data;
+      // return await this.webClient.webApps.updateApplicationSettings(this.resourceGroup, functionApp.name, properties);
+    } catch (err) {
+      throw new Error(`Failed to update function app settings: ${err}`)
+    }
   }
 
   /**
diff --git a/src/services/packageService.ts b/src/services/packageService.ts
index cec641c..ef31e71 100644
--- a/src/services/packageService.ts
+++ b/src/services/packageService.ts
@@ -109,7 +109,7 @@ export class PackageService extends BaseService {
       fs.mkdirSync(functionDirPath);
     }
 
-    const functionJsonString = JSON.stringify(functionJSON, null, 2);
+    const functionJsonString = this.stringify(functionJSON);
     fs.writeFileSync(path.join(functionDirPath, "function.json"), functionJsonString);
 
     return Promise.resolve();
diff --git a/src/services/rollbackService.ts b/src/services/rollbackService.ts
index b1a1ec9..6186d41 100644
--- a/src/services/rollbackService.ts
+++ b/src/services/rollbackService.ts
@@ -56,7 +56,7 @@ export class RollbackService extends BaseService {
     const armDeployment = await this.convertToArmDeployment(deployment);
     // Initialize blob service for either creating SAS token or downloading artifact to uplod to function app
     await this.blobService.initialize();
-    if (this.config.provider.deployment.runFromBlobUrl) {
+    if (this.config.provider.deployment.external) {
       // Set functionRunFromPackage param to SAS URL of blob
       armDeployment.parameters.functionAppRunFromPackage = {
         type: ArmParamType.String,
@@ -71,7 +71,7 @@ export class RollbackService extends BaseService {
      * Cannot use an `else` statement just because deploying the artifact
      * depends on `deployTemplate` already being called
      */
-    if (!this.config.provider.deployment.runFromBlobUrl) {
+    if (!this.config.provider.deployment.external) {
       const artifactPath = await this.downloadArtifact(artifactName);
       await this.redeployArtifact(artifactPath);
     }
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
index 0a26b5a..02cee7f 100644
--- a/src/shared/utils.ts
+++ b/src/shared/utils.ts
@@ -114,10 +114,6 @@ export class Utils {
     return metaData;
   }
 
-  public static prettyPrint(json) {
-    return JSON.stringify(json, null, 2);
-  }
-
   /**
    * Take the first `substringSize` characters from each string and return as one string
    * @param substringSize Size of substring to take from beginning of each string


Test patch:
diff --git a/src/services/configService.test.ts b/src/services/configService.test.ts
index 8c24874..6888220 100644
--- a/src/services/configService.test.ts
+++ b/src/services/configService.test.ts
@@ -1,7 +1,7 @@
 import { ConfigService } from "./configService";
 import Serverless from "serverless";
 import { MockFactory } from "../test/mockFactory";
-import { ServerlessAzureConfig } from "../models/serverless";
+import { ServerlessAzureConfig, DeploymentConfig } from "../models/serverless";
 import configConstants from "../config";
 import { AzureNamingService } from "./namingService";
 
@@ -118,6 +118,38 @@ describe("Config Service", () => {
       const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${serverless.service["service"]}-rg`;
       expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
     });
+
+    it("Gets deployment config from SLS yaml using external property", () => {
+      const deploymentConfig1: DeploymentConfig = {
+        external: true
+      }
+      serverless.service.provider["deployment"] = deploymentConfig1
+      const service1 = new ConfigService(serverless, { } as any);
+      expect(service1.getDeploymentConfig().external).toBe(true);
+
+      const deploymentConfig2: DeploymentConfig = {
+        external: false
+      }
+      serverless.service.provider["deployment"] = deploymentConfig2
+      const service2 = new ConfigService(serverless, { } as any);
+      expect(service2.getDeploymentConfig().external).toBe(false);
+    });
+
+    it("Gets deployment config from SLS yaml using runFromBlobUrl property", () => {
+      const deploymentConfig1 = {
+        runFromBlobUrl: true
+      }
+      serverless.service.provider["deployment"] = deploymentConfig1
+      const service1 = new ConfigService(serverless, { } as any);
+      expect(service1.getDeploymentConfig().external).toBe(true);
+
+      const deploymentConfig2 = {
+        runFromBlobUrl: false
+      }
+      serverless.service.provider["deployment"] = deploymentConfig2
+      const service2 = new ConfigService(serverless, { } as any);
+      expect(service2.getDeploymentConfig().external).toBe(false);
+    });
   });
   
   describe("Service Principal Configuration", () => {
diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts
index aa70c80..9fff364 100644
--- a/src/services/functionAppService.test.ts
+++ b/src/services/functionAppService.test.ts
@@ -17,15 +17,18 @@ jest.mock("./azureBlobStorageService");
 import { AzureBlobStorageService } from "./azureBlobStorageService"
 import configConstants from "../config";
 import { Utils } from "../shared/utils";
+import { ServerlessAzureConfig } from "../models/serverless";
 
 describe("Function App Service", () => {
   const app = MockFactory.createTestSite();
   const slsService = MockFactory.createTestService();
+  const config: ServerlessAzureConfig = slsService as any;
   const variables = MockFactory.createTestVariables();
   const provider = MockFactory.createTestAzureServiceProvider();
 
   const masterKey = "masterKey";
   const authKey = "authKey";
+  const subscriptionId = "subId";
   const syncTriggersMessage = "sync triggers success";
   const deleteFunctionMessage = "delete function success";
   const functions = MockFactory.createTestSlsFunctionConfig();
@@ -34,8 +37,13 @@ describe("Function App Service", () => {
   const baseUrl = "https://management.azure.com"
   const masterKeyUrl = `https://${app.defaultHostName}/admin/host/systemkeys/_master`;
   const authKeyUrl = `${baseUrl}${app.id}/functions/admin/token?api-version=2016-08-01`;
-  const syncTriggersUrl = `${baseUrl}${app.id}/syncfunctiontriggers?api-version=2016-08-01`;
+  const syncTriggersUrl = `${baseUrl}/subscriptions/${subscriptionId}` +
+    `/resourceGroups/${config.provider.resourceGroup}/providers/Microsoft.Web/sites/${app.name}` +
+    "/syncfunctiontriggers?api-version=2016-08-01";
   const listFunctionsUrl = `${baseUrl}${app.id}/functions?api-version=2016-08-01`;
+  const appSettingsUrl = `${baseUrl}/subscriptions/${subscriptionId}/resourceGroups/${config.provider.resourceGroup}` +
+    `/providers/Microsoft.Web/sites/${app.name}/config/appsettings?api-version=2016-08-01`;
+
 
   const appSettings = {
     setting1: "value1",
@@ -53,6 +61,8 @@ describe("Function App Service", () => {
     axiosMock.onGet(authKeyUrl).reply(200, authKey);
     // Sync Triggers
     axiosMock.onPost(syncTriggersUrl).reply(200, syncTriggersMessage);
+    // Update app settings
+    axiosMock.onPut(appSettingsUrl).reply(200, appSettings)
     // List Functions
     axiosMock.onGet(listFunctionsUrl).reply(200, { value: functionsResponse });
     // Delete Function
@@ -86,12 +96,16 @@ describe("Function App Service", () => {
   });
 
   function createService(sls?: Serverless, options?: Serverless.Options) {
+    options = options || MockFactory.createTestServerlessOptions();
     return new FunctionAppService(
       sls || MockFactory.createTestServerless({
         service: slsService,
         variables: variables,
       }),
-      options || MockFactory.createTestServerlessOptions()
+      {
+        ...options,
+        subscriptionId: subscriptionId
+      } as any
     )
   }
 
@@ -119,7 +133,6 @@ describe("Function App Service", () => {
     const service = createService();
     const result = await service.getMasterKey();
     expect(result).toEqual(masterKey);
-
   });
 
   it("deletes function", async () => {
@@ -131,7 +144,7 @@ describe("Function App Service", () => {
 
   it("syncs triggers", async () => {
     const service = createService();
-    const result = await service.syncTriggers(app);
+    const result = await service.syncTriggers(app, {});
     expect(result.data).toEqual(syncTriggersMessage)
   });
 
@@ -344,29 +357,45 @@ describe("Function App Service", () => {
   it("adds a new function app setting", async () => {
     const service = createService();
     const settingName = "TEST_SETTING";
-    const settingValue = "TEST_VALUE"
+    const settingValue = "TEST_VALUE";
+    const sendApiSpy = jest.spyOn(FunctionAppService.prototype as any, "sendApiRequest")
     await service.updateFunctionAppSetting(app, settingName, settingValue);
-    expect(WebSiteManagementClient.prototype.webApps.updateApplicationSettings).toBeCalledWith(
-      "myResourceGroup",
-      "Test",
+
+    const expectedAppSettings = {
+      ...appSettings,
+      TEST_SETTING: settingValue
+    }
+
+    expect(sendApiSpy).toBeCalledWith(
+      "PUT",
+      appSettingsUrl,
       {
-        ...appSettings,
-        TEST_SETTING: settingValue
+        data: {
+          properties: expectedAppSettings
+        }
       }
-    )
+    );
   });
 
   it("updates an existing function app setting", async () => {
     const service = createService();
     const settingName = "setting1";
     const settingValue = "TEST_VALUE"
+    const sendApiSpy = jest.spyOn(FunctionAppService.prototype as any, "sendApiRequest")
+
     await service.updateFunctionAppSetting(app, settingName, settingValue);
-    expect(WebSiteManagementClient.prototype.webApps.updateApplicationSettings).toBeCalledWith(
-      "myResourceGroup",
-      "Test",
+
+    const expectedAppSettings = {
+      setting1: settingValue,
+      setting2: appSettings.setting2
+    }
+    expect(sendApiSpy).toBeCalledWith(
+      "PUT",
+      appSettingsUrl,
       {
-        setting1: settingValue,
-        setting2: appSettings.setting2
+        data: {
+          properties: expectedAppSettings
+        }
       }
     );
   });
@@ -376,7 +405,11 @@ describe("Function App Service", () => {
     const sasUrl = "sasUrl"
 
     beforeEach(() => {
-      FunctionAppService.prototype.updateFunctionAppSetting = jest.fn();
+      FunctionAppService.prototype.updateFunctionAppSetting = jest.fn(() => {
+        return {
+          properties: appSettings
+        }
+      });
       AzureBlobStorageService.prototype.generateBlobSasTokenUrl = jest.fn(() => Promise.resolve(sasUrl));
     });
 
@@ -387,7 +420,7 @@ describe("Function App Service", () => {
     it("updates WEBSITE_RUN_FROM_PACKAGE with SAS URL if configured to run from blob", async () => {
       const newSlsService = MockFactory.createTestService();
       newSlsService.provider["deployment"] = {
-        runFromBlobUrl: true,
+        external: true,
       }
       const service = createService(MockFactory.createTestServerless({
         service: newSlsService,
@@ -404,7 +437,7 @@ describe("Function App Service", () => {
     it("does not upload directly to function app if configured to run from blob", async () => {
       const newSlsService = MockFactory.createTestService();
       newSlsService.provider["deployment"] = {
-        runFromBlobUrl: true,
+        external: true,
       }
       const service = createService(MockFactory.createTestServerless({
         service: newSlsService,
@@ -424,7 +457,7 @@ describe("Function App Service", () => {
     it("uploads directly to function app if not configured to run from blob", async () => {
       const newSlsService = MockFactory.createTestService();
       newSlsService.provider["deployment"] = {
-        runFromBlobUrl: false,
+        external: false,
       }
       const sls = MockFactory.createTestServerless({
         service: newSlsService,
diff --git a/src/services/rollbackService.test.ts b/src/services/rollbackService.test.ts
index 6a77eda..d8b2482 100644
--- a/src/services/rollbackService.test.ts
+++ b/src/services/rollbackService.test.ts
@@ -117,7 +117,7 @@ describe("Rollback Service", () => {
   it("should deploy function app with SAS URL", async () => {
     const sls = MockFactory.createTestServerless();
     const deploymentConfig: DeploymentConfig = {
-      runFromBlobUrl: true
+      external: true
     }
     sls.service.provider["deployment"] = deploymentConfig;
     const service = createService(sls);