Repository: serverless/serverless-azure-functions
Pull request #338 (2019-09-16 21:47:03)

feat: ConfigService for centralizing configuration and simplifying BaseService
<!--
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:

`ConfigService` and its usage. This removes the configuration logic from `BaseService` and keeps it all in one place. Also adds some constants for default configuration.

In a future PR, the `ConfigService` will cache its configuration so that the service is only configured one time in the initialization of the first `BaseService` subclass. It will store the data in the `serverless.variables` object and checks to see if that value is populated on future runs.

Also added some constants for default configuration values.

<!--
Briefly describe the feature if no issue exists for this PR
-->

## How did you implement it:

Removed the configuration logic from the `BaseService` and placed inside the `ConfigService`, which is initialized in the constructor of the `BaseService`

<!--
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:

- Unit tests
- Run an end-to-end deployment of a service

<!--
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/338?src=pr&el=h1) Report
> Merging [#338](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338?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.06%`.
> The diff coverage is `98.97%`.

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

```diff
@@            Coverage Diff             @@
##              dev     #338      +/-   ##
==========================================
+ Coverage   94.67%   94.73%   +0.06%     
==========================================
  Files          47       48       +1     
  Lines        1727     1747      +20     
  Branches      242      248       +6     
==========================================
+ Hits         1635     1655      +20     
  Misses         92       92
```


| [Impacted Files](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/config.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL2NvbmZpZy50cw==) | `100% <ø> (ø)` | :arrow_up: |
| [src/shared/constants.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NoYXJlZC9jb25zdGFudHMudHM=) | `100% <ø> (ø)` | :arrow_up: |
| [src/services/resourceService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL3Jlc291cmNlU2VydmljZS50cw==) | `95.55% <0%> (ø)` | :arrow_up: |
| [src/services/configService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2NvbmZpZ1NlcnZpY2UudHM=) | `100% <100%> (ø)` | |
| [src/services/funcService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2Z1bmNTZXJ2aWNlLnRz) | `100% <100%> (ø)` | :arrow_up: |
| [src/services/packageService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL3BhY2thZ2VTZXJ2aWNlLnRz) | `100% <100%> (ø)` | :arrow_up: |
| [src/services/functionAppService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2Z1bmN0aW9uQXBwU2VydmljZS50cw==) | `87.59% <100%> (+0.18%)` | :arrow_up: |
| [src/services/baseService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2Jhc2VTZXJ2aWNlLnRz) | `89.74% <100%> (-6.01%)` | :arrow_down: |
| [src/services/loginService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL2xvZ2luU2VydmljZS50cw==) | `100% <100%> (ø)` | :arrow_up: |
| [src/services/rollbackService.ts](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree#diff-c3JjL3NlcnZpY2VzL3JvbGxiYWNrU2VydmljZS50cw==) | `100% <100%> (ø)` | :arrow_up: |
| ... and [2 more](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338?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/338?src=pr&el=footer). Last update [084b4c1...56d00e9](https://codecov.io/gh/serverless/serverless-azure-functions/pull/338?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..5418960
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,35 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "type": "node",
+        "request": "launch",
+        "name": "Jest All",
+        "program": "${workspaceFolder}/node_modules/.bin/jest",
+        "args": ["--runInBand"],
+        "console": "integratedTerminal",
+        "internalConsoleOptions": "neverOpen",
+        "disableOptimisticBPs": true,
+        "windows": {
+          "program": "${workspaceFolder}/node_modules/jest/bin/jest",
+        }
+      },
+      {
+        "type": "node",
+        "request": "launch",
+        "name": "Jest Current File",
+        "program": "${workspaceFolder}/node_modules/.bin/jest",
+        "args": [
+          "${fileBasenameNoExtension}",
+          "--config",
+          "jest.config.js"
+        ],
+        "console": "integratedTerminal",
+        "internalConsoleOptions": "neverOpen",
+        "disableOptimisticBPs": true,
+        "windows": {
+          "program": "${workspaceFolder}/node_modules/jest/bin/jest",
+        }
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/package.json b/package.json
index 562a64d..2562533 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0",
+  "version": "1.0.1",
   "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.",
   "license": "MIT",
   "main": "./lib/index.js",
diff --git a/src/config.ts b/src/config.ts
index b1988e8..88a1e45 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -34,7 +34,13 @@ export const configConstants = {
   scmVfsPath: "/api/vfs/site/wwwroot/",
   scmZipDeployApiPath: "/api/zipdeploy",
   resourceGroupHashLength: 6,
-  defaultLocalPort: 7071,
+  defaults: {
+    awsRegion: "us-east-1",
+    region: "westus",
+    stage: "dev",
+    prefix: "sls",
+    localPort: 7071,
+  },
 };
 
 export default configConstants;
diff --git a/src/models/serverless.ts b/src/models/serverless.ts
index 91b77d4..55a59cf 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -35,6 +35,8 @@ export interface ServerlessAzureProvider {
   stage: string;
   name: string;
   subscriptionId?: string;
+  tenantId?: string;
+  appId?: string;
   environment?: {
     [key: string]: any;
   };
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index 1e433b0..730a22d 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -4,17 +4,15 @@ import fs from "fs";
 import request from "request";
 import Serverless from "serverless";
 import { StorageAccountResource } from "../armTemplates/resources/storageAccount";
-import { configConstants } from "../config";
-import {
-  DeploymentConfig,
+import { 
   ServerlessAzureConfig,
-  ServerlessAzureFunctionConfig,
   ServerlessAzureOptions,
-  ServerlessLogOptions
+  ServerlessLogOptions 
 } from "../models/serverless";
+import { constants } from "../shared/constants";
 import { Guard } from "../shared/guard";
 import { Utils } from "../shared/utils";
-import { AzureNamingService, AzureNamingServiceOptions } from "./namingService";
+import { ConfigService } from "./configService";
 
 export abstract class BaseService {
   protected baseUrl: string;
@@ -24,9 +22,9 @@ export abstract class BaseService {
   protected resourceGroup: string;
   protected deploymentName: string;
   protected artifactName: string;
-  protected deploymentConfig: DeploymentConfig;
   protected storageAccountName: string;
   protected config: ServerlessAzureConfig;
+  protected configService: ConfigService;
 
   protected constructor(
     protected serverless: Serverless,
@@ -34,18 +32,16 @@ export abstract class BaseService {
     authenticate: boolean = true
   ) {
     Guard.null(serverless);
-    this.setDefaultValues();
-    this.config = serverless.service as any;
-    this.setupConfig();
+    this.configService = new ConfigService(serverless, options);
+    this.config = this.configService.getConfig();
 
     this.baseUrl = "https://management.azure.com";
-    this.serviceName = this.getServiceName();
-    this.credentials = serverless.variables["azureCredentials"];
+    this.serviceName = this.configService.getServiceName();
+    this.credentials = serverless.variables[constants.variableKeys.azureCredentials];
     this.subscriptionId = this.config.provider.subscriptionId;
-    this.resourceGroup = this.getResourceGroupName();
-    this.deploymentConfig = this.getDeploymentConfig();
-    this.deploymentName = this.getDeploymentName();
-    this.artifactName = this.getArtifactName(this.deploymentName);
+    this.resourceGroup = this.configService.getResourceGroupName();
+    this.deploymentName = this.configService.getDeploymentName();
+    this.artifactName = this.configService.getArtifactName(this.deploymentName);
     this.storageAccountName = StorageAccountResource.getResourceName(this.config);
 
     if (!this.credentials && authenticate) {
@@ -53,87 +49,6 @@ export abstract class BaseService {
     }
   }
 
-  /**
-   * Name of Azure Region for deployment
-   */
-  public getRegion(): string {
-    return this.config.provider.region;
-  }
-
-  /**
-   * Name of current deployment stage
-   */
-  public getStage(): string {
-    return this.config.provider.stage;
-  }
-
-  /**
-   * Prefix for service
-   */
-  public getPrefix(): string {
-    return this.config.provider.prefix;
-  }
-
-  /**
-   * Name of current resource group
-   */
-  public getResourceGroupName(): string {
-    return this.config.provider.resourceGroup;
-  }
-
-  /**
-   * Azure Subscription ID
-   */
-  public getSubscriptionId(): string {
-    return this.config.provider.subscriptionId;
-  }
-
-  /**
-   * Deployment config from `serverless.yml` or default.
-   * Defaults can be found in the `config.ts` file
-   */
-  public getDeploymentConfig(): DeploymentConfig {
-    return {
-      ...configConstants.deploymentConfig,
-      ...this.config.provider.deployment,
-    }
-  }
-
-  /**
-   * Name of current ARM deployment.
-   *
-   * Naming convention:
-   *
-   * {safeName (see naming service)}--{serviceName}(if rollback enabled: -t{timestamp})
-   *
-   * The string is guaranteed to be less than 64 characters, since that is the limit
-   * imposed by Azure deployment names. If a trim is needed, the service name will be trimmed
-   */
-  public getDeploymentName(): string {
-    return AzureNamingService.getDeploymentName(
-      this.config,
-      (this.deploymentConfig.rollback) ? `t${this.getTimestamp()}` : null
-    )
-  }
-
-  /**
-   * Name of Function App Service
-   */
-  public getServiceName(): string {
-    return this.serverless.service["service"];
-  }
-
-  /**
-   * Get rollback-configured artifact name. Contains `-t{timestamp}`
-   * Takes name of deployment and replaces `rg-deployment` or `deployment` with `artifact`
-   */
-  protected getArtifactName(deploymentName: string): string {
-    const { deployment, artifact } = configConstants.naming.suffix;
-    return `${deploymentName
-      .replace(`rg-${deployment}`, artifact)
-      .replace(deployment, artifact)}.zip`
-  }
-
   /**
    * Get the access token from credentials token cache
    */
@@ -196,85 +111,13 @@ export abstract class BaseService {
    */
   protected log(message: string, options?: ServerlessLogOptions, entity?: string) {
     (this.serverless.cli.log as any)(message, entity, options);
-  }
-
-  /**
-   * Get function objects
-   */
-  protected slsFunctions(): { [functionName: string]: ServerlessAzureFunctionConfig } {
-    return this.serverless.service["functions"];
-  }
-
-  protected slsConfigFile(): string {
-    return "config" in this.options ? this.options["config"] : "serverless.yml";
-  }
-
-  protected getOption(key: string, defaultValue?: any) {
-    return Utils.get(this.options, key, defaultValue);
-  }
+  }  
 
   protected prettyPrint(object: any) {
     this.log(JSON.stringify(object, null, 2));
   }
 
-  private setDefaultValues(): void {
-    // TODO: Right now the serverless core will always default to AWS default region if the
-    // region has not been set in the serverless.yml or CLI options
-    const awsDefault = "us-east-1";
-    const providerRegion = this.serverless.service.provider.region;
-
-    if (!providerRegion || providerRegion === awsDefault) {
-      // no region specified in serverless.yml
-      this.serverless.service.provider.region = this.serverless.service.provider["location"] || "westus";
-    }
-
-    if (!this.serverless.service.provider.stage) {
-      this.serverless.service.provider.stage = "dev";
-    }
-
-    if (!this.serverless.service.provider["prefix"]) {
-      this.serverless.service.provider["prefix"] = "sls";
-    }
-  }
-
-  /**
-   * Get timestamp from `packageTimestamp` serverless variable
-   * If not set, create timestamp, set variable and return timestamp
-   */
-  private getTimestamp(): number {
-    let timestamp = +this.serverless.variables["packageTimestamp"];
-    if (!timestamp) {
-      timestamp = Date.now();
-      this.serverless.variables["packageTimestamp"] = timestamp;
-    }
-    return timestamp;
-  }
-
-  /**
-   * Overwrite values for resourceGroup, prefix, region and stage
-   * in config if passed through CLI
-   */
-  private setupConfig() {
-    const { prefix, region, stage, subscriptionId } = this.config.provider;
-
-    const options: AzureNamingServiceOptions = {
-      config: this.config,
-      suffix: `${this.getServiceName()}-rg`,
-      includeHash: false,
-    }
-
-    this.config.provider = {
-      ...this.config.provider,
-      prefix: this.getOption("prefix") || prefix,
-      stage: this.getOption("stage") || stage,
-      region: this.getOption("region") || region,
-      subscriptionId: this.getOption("subscriptionId")
-        || process.env.AZURE_SUBSCRIPTION_ID
-        || subscriptionId
-        || this.serverless.variables["subscriptionId"]
-    }
-    this.config.provider.resourceGroup = (
-      this.getOption("resourceGroup", this.config.provider.resourceGroup)
-    ) || AzureNamingService.getResourceName(options);
+  protected getOption(key: string, defaultValue?: any) {
+    return Utils.get(this.options, key, defaultValue);
   }
 }
diff --git a/src/services/configService.ts b/src/services/configService.ts
new file mode 100644
index 0000000..4a2fe25
--- /dev/null
+++ b/src/services/configService.ts
@@ -0,0 +1,210 @@
+import Serverless from "serverless";
+import Service from "serverless/classes/Service";
+import configConstants from "../config";
+import { ServerlessAzureConfig, ServerlessAzureFunctionConfig } from "../models/serverless";
+import { constants } from "../shared/constants";
+import { Utils } from "../shared/utils";
+import { AzureNamingService, AzureNamingServiceOptions } from "./namingService";
+
+/**
+ * Handles all Service Configuration
+ */
+export class ConfigService {
+
+  /** Configuration for service */
+  private config: ServerlessAzureConfig;
+
+  public constructor(private serverless: Serverless, private options: Serverless.Options) {
+    this.config = this.initializeConfig(serverless.service);
+  }
+
+  /**
+   * Get Azure Provider Configuration
+   */
+  public getConfig(): ServerlessAzureConfig {
+    return this.config;
+  }
+
+  /**
+   * Name of Azure Region for deployment
+   */
+  public getRegion(): string {
+    return this.config.provider.region;
+  }
+
+  /**
+   * Name of current deployment stage
+   */
+  public getStage(): string {
+    return this.config.provider.stage;
+  }
+
+  /**
+   * Prefix for service
+   */
+  public getPrefix(): string {
+    return this.config.provider.prefix;
+  }
+
+  /**
+   * Name of current resource group
+   */
+  public getResourceGroupName(): string {
+    return this.config.provider.resourceGroup;
+  }
+
+  /**
+   * Azure Subscription ID
+   */
+  public getSubscriptionId(): string {
+    return this.config.provider.subscriptionId;
+  }
+
+  /**
+   * Name of current deployment
+   */
+  public getDeploymentName(): string {
+    return AzureNamingService.getDeploymentName(
+      this.config,
+      (this.config.provider.deployment.rollback) ? `t${this.getTimestamp()}` : null
+    )
+  }
+
+  /**
+   * Get rollback-configured artifact name. Contains `-t{timestamp}`
+   * Takes name of deployment and replaces `rg-deployment` or `deployment` with `artifact`
+   */
+  public getArtifactName(deploymentName?: string): string {
+    deploymentName = deploymentName || this.getDeploymentName();
+    const { deployment, artifact } = configConstants.naming.suffix;
+    return `${deploymentName
+      .replace(`rg-${deployment}`, artifact)
+      .replace(deployment, artifact)}.zip`
+  }
+
+  /**
+   * Function configuration from serverless.yml
+   */
+  public getFunctionConfig(): { [functionName: string]: ServerlessAzureFunctionConfig } {
+    return this.config.functions;
+  }
+
+  /**
+   * Name of file containing serverless config
+   */
+  public getConfigFile(): string {
+    return this.getOption("config", "serverless.yml");
+  }
+
+  /**
+   * Name of Function App Service
+   */
+  public getServiceName(): string {
+    return this.config.service;
+  }
+
+  /**
+   * Set any default values required for service
+   * @param config Current Serverless configuration
+   */
+  private setDefaultValues(config: ServerlessAzureConfig) {
+    const { awsRegion, region, stage, prefix } = configConstants.defaults;
+    const providerRegion = config.provider.region;
+
+    if (!providerRegion || providerRegion === awsRegion) {
+      config.provider.region = this.serverless.service.provider["location"] || region;
+    }
+ 
+    if (!config.provider.stage) {
+      config.provider.stage = stage;
+    }
+
+    if (!config.provider.prefix) {
+      config.provider.prefix = prefix;
+    }
+  }
+
+  /**
+   * Overwrite values for resourceGroup, prefix, region and stage
+   * in config if passed through CLI
+   */
+  private initializeConfig(service: Service): ServerlessAzureConfig {
+    const config: ServerlessAzureConfig = service as any;
+    this.setDefaultValues(config);
+
+    const {
+      prefix,
+      region,
+      stage,
+      subscriptionId,
+      tenantId,
+      appId,
+      deployment
+    } = config.provider;
+
+    const options: AzureNamingServiceOptions = {
+      config: config,
+      suffix: `${config.service}-rg`,
+      includeHash: false,
+    }
+
+    config.provider = {
+      ...config.provider,
+      prefix: this.getOption("prefix") || prefix,
+      stage: this.getOption("stage") || stage,
+      region: this.getOption("region") || region,
+      subscriptionId: this.getOption(constants.variableKeys.subscriptionId)
+        || process.env.AZURE_SUBSCRIPTION_ID
+        || subscriptionId
+        || this.serverless.variables[constants.variableKeys.subscriptionId],
+      tenantId: this.getOption(constants.variableKeys.tenantId)
+        || process.env.AZURE_TENANT_ID
+        || tenantId,
+      appId: this.getOption(constants.variableKeys.appId)
+        || process.env.AZURE_CLIENT_ID
+        || appId
+    }
+    config.provider.resourceGroup = (
+      this.getOption("resourceGroup", config.provider.resourceGroup)
+    ) || AzureNamingService.getResourceName(options);
+
+    config.provider.deployment = {
+      ...configConstants.deploymentConfig,
+      ...deployment
+    }
+
+    return config;
+  }
+
+  /**
+   * Get timestamp from `packageTimestamp` serverless variable
+   * If not set, create timestamp, set variable and return timestamp
+   */
+  private getTimestamp(): number {
+    const key = constants.variableKeys.packageTimestamp
+    let timestamp = +this.getVariable(key);
+    if (!timestamp) {
+      timestamp = Date.now();
+      this.serverless.variables[key] = timestamp;
+    }
+    return timestamp;
+  }
+
+  /**
+   * Get value of option from Serverless CLI
+   * @param key Key of option
+   * @param defaultValue Default value if key not found in options object
+   */
+  protected getOption(key: string, defaultValue?: any) {
+    return Utils.get(this.options, key, defaultValue);
+  }
+
+  /**
+   * Get variable value from Serverless variables
+   * @param key Key for variable
+   * @param defaultValue Default value if key not found in variable object
+   */
+  protected getVariable(key: string, defaultValue?: any) {
+    return Utils.get(this.serverless.variables, key, defaultValue);
+  }
+}
diff --git a/src/services/funcService.ts b/src/services/funcService.ts
index b84cda7..580f155 100644
--- a/src/services/funcService.ts
+++ b/src/services/funcService.ts
@@ -44,7 +44,7 @@ export class FuncService extends BaseService {
   }
 
   private exists(functionName: string) {
-    return (functionName in this.slsFunctions());
+    return (functionName in this.configService.getFunctionConfig());
   }
 
   private createHandler(functionName: string) {
@@ -52,26 +52,26 @@ export class FuncService extends BaseService {
   }
 
   private addToServerlessYml(functionName: string) {
-    const functions = this.slsFunctions();
+    const functions = this.configService.getFunctionConfig();
     functions[functionName] = this.getFunctionSlsObject(functionName)
     this.updateFunctionsYml(functions)
   }
 
   private removeFromServerlessYml(functionName: string) {
-    const functions = this.slsFunctions();
+    const functions = this.configService.getFunctionConfig();
     delete functions[functionName];
     this.updateFunctionsYml(functions)
   }
 
   private getServerlessYml() {
-    return this.serverless.utils.readFileSync(this.slsConfigFile());
+    return this.serverless.utils.readFileSync(this.configService.getConfigFile());
   }
 
   private updateFunctionsYml(functionYml: any) {
     const serverlessYml = this.getServerlessYml();
     serverlessYml["functions"] = functionYml;
     this.serverless.utils.writeFileSync(
-      this.slsConfigFile(),
+      this.configService.getConfigFile(),
       yaml.dump(serverlessYml)
     );
   }
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 6942f8b..3eb5bd4 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -155,12 +155,12 @@ export class FunctionAppService extends BaseService {
 
     const functionZipFile = this.getFunctionZipFile();
 
-    if (this.deploymentConfig.runFromBlobUrl) {
+    if (this.config.provider.deployment.runFromBlobUrl) {
       this.log("Updating function app setting to run from external package...");
       await this.uploadZippedArtifactToBlobStorage(functionZipFile);
 
       const sasUrl = await this.blobService.generateBlobSasTokenUrl(
-        this.deploymentConfig.container,
+        this.config.provider.deployment.container,
         this.artifactName
       );
 
@@ -250,6 +250,10 @@ export class FunctionAppService extends BaseService {
     return functionZipFile;
   }
 
+  public getDeploymentName(): string {
+    return this.configService.getDeploymentName();
+  }
+
   public async updateFunctionAppSetting(functionApp: Site, setting: string, value: string) {
     const { properties } = await this.webClient.webApps.listApplicationSettings(this.resourceGroup, functionApp.name);
     properties[setting] = value;
@@ -261,10 +265,10 @@ export class FunctionAppService extends BaseService {
    */
   private async uploadZippedArtifactToBlobStorage(functionZipFile: string): Promise<void> {
     await this.blobService.initialize();
-    await this.blobService.createContainerIfNotExists(this.deploymentConfig.container);
+    await this.blobService.createContainerIfNotExists(this.config.provider.deployment.container);
     await this.blobService.uploadFile(
       functionZipFile,
-      this.deploymentConfig.container,
+      this.config.provider.deployment.container,
       this.artifactName,
     );
   }
diff --git a/src/services/invokeService.ts b/src/services/invokeService.ts
index 00366e4..dbb4b52 100644
--- a/src/services/invokeService.ts
+++ b/src/services/invokeService.ts
@@ -24,7 +24,7 @@ export class InvokeService extends BaseService {
    */
   public async invoke(method: string, functionName: string, data?: any){
 
-    const functionObject = this.slsFunctions()[functionName];
+    const functionObject = this.configService.getFunctionConfig()[functionName];
     /* accesses the admin key */
     if (!functionObject) {
       this.serverless.cli.log(`Function ${functionName} does not exist`);
@@ -63,7 +63,7 @@ export class InvokeService extends BaseService {
   }
 
   private getLocalHost() {
-    return `http://localhost:${this.getOption("port", configConstants.defaultLocalPort)}`
+    return `http://localhost:${this.getOption("port", configConstants.defaults.localPort)}`
   }
 
   private getConfiguredFunctionRoute(functionName: string) {
diff --git a/src/services/loginService.ts b/src/services/loginService.ts
index 49bf602..3a2953e 100644
--- a/src/services/loginService.ts
+++ b/src/services/loginService.ts
@@ -27,7 +27,7 @@ export class AzureLoginService extends BaseService {
    * @param options Options for different authentication methods
    */
   public async login(options?: AzureTokenCredentialsOptions | InteractiveLoginOptions): Promise<AuthResponse> {
-    const subscriptionId = this.getSubscriptionId();
+    const subscriptionId = this.configService.getSubscriptionId();
     const clientId = process.env.AZURE_CLIENT_ID;
     const secret = process.env.AZURE_CLIENT_SECRET;
     const tenantId = process.env.AZURE_TENANT_ID;
@@ -57,4 +57,8 @@ export class AzureLoginService extends BaseService {
   public async servicePrincipalLogin(clientId: string, secret: string, tenantId: string, options: AzureTokenCredentialsOptions): Promise<AuthResponse> {
     return await loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId, options);
   }
+
+  public getSubscriptionId() {
+    return this.configService.getSubscriptionId();
+  }
 }
diff --git a/src/services/packageService.ts b/src/services/packageService.ts
index 8ab9477..cec641c 100644
--- a/src/services/packageService.ts
+++ b/src/services/packageService.ts
@@ -93,7 +93,7 @@ export class PackageService extends BaseService {
     const functionJSON = functionMetadata.params.functionsJson;
     functionJSON.entryPoint = functionMetadata.entryPoint;
     functionJSON.scriptFile = functionMetadata.handlerPath;
-    const functionObject = this.slsFunctions()[functionName];
+    const functionObject = this.configService.getFunctionConfig()[functionName];
     const bindingAzureSettings = Utils.getIncomingBindingConfig(functionObject)["x-azure-settings"];
 
     if (bindingAzureSettings.route) {
diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts
index 6a03265..b76a153 100644
--- a/src/services/resourceService.ts
+++ b/src/services/resourceService.ts
@@ -58,7 +58,7 @@ export class ResourceService extends BaseService {
   public async listDeployments(): Promise<string> {
     const deployments = await this.getDeployments()
     if (!deployments || deployments.length === 0) {
-      this.log(`No deployments found for resource group '${this.getResourceGroupName()}'`);
+      this.log(`No deployments found for resource group '${this.configService.getResourceGroupName()}'`);
       return;
     }
     let stringDeployments = "\n\nDeployments";
@@ -89,7 +89,7 @@ export class ResourceService extends BaseService {
     this.log(`Creating resource group: ${this.resourceGroup}`);
 
     return await this.resourceClient.resourceGroups.createOrUpdate(this.resourceGroup, {
-      location: AzureNamingService.getNormalizedRegionName(this.getRegion()),
+      location: AzureNamingService.getNormalizedRegionName(this.configService.getRegion()),
     });
   }
 
diff --git a/src/services/rollbackService.ts b/src/services/rollbackService.ts
index 9747196..b1a1ec9 100644
--- a/src/services/rollbackService.ts
+++ b/src/services/rollbackService.ts
@@ -41,7 +41,7 @@ export class RollbackService extends BaseService {
       return;
     }
     // Name of artifact in blob storage
-    const artifactName = this.getArtifactName(deployment.name);
+    const artifactName = this.configService.getArtifactName(deployment.name);
     // Redeploy resource group (includes SAS token URL if running from blob URL)
     await this.redeployDeployment(deployment, artifactName);
   }
@@ -56,12 +56,12 @@ 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.deploymentConfig.runFromBlobUrl) {
+    if (this.config.provider.deployment.runFromBlobUrl) {
       // Set functionRunFromPackage param to SAS URL of blob
       armDeployment.parameters.functionAppRunFromPackage = {
         type: ArmParamType.String,
         value: await this.blobService.generateBlobSasTokenUrl(
-          this.deploymentConfig.container,
+          this.config.provider.deployment.container,
           artifactName
         )
       }
@@ -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.deploymentConfig.runFromBlobUrl) {
+    if (!this.config.provider.deployment.runFromBlobUrl) {
       const artifactPath = await this.downloadArtifact(artifactName);
       await this.redeployArtifact(artifactPath);
     }
@@ -132,7 +132,7 @@ export class RollbackService extends BaseService {
   private async downloadArtifact(artifactName: string): Promise<string> {
     const artifactPath = path.join(this.serverless.config.servicePath, ".serverless", artifactName)
     await this.blobService.downloadBinary(
-      this.deploymentConfig.container,
+      this.config.provider.deployment.container,
       artifactName,
       artifactPath
     );
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 4ed98f7..08b7d4e 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -21,4 +21,12 @@ export const constants = {
   queueName: "queueName",
   xAzureSettings: "x-azure-settings",
   entryPoint: "entryPoint",
-}
\ No newline at end of file
+  variableKeys: {
+    config: "serverlessAzureConfig",
+    subscriptionId: "subscriptionId",
+    tenantId: "tenantId",
+    appId: "appId",
+    packageTimestamp: "packageTimestamp",
+    azureCredentials: "azureCredentials",
+  }
+}


Test patch:
diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts
index 23a4d8f..0dbb742 100644
--- a/src/services/apimService.test.ts
+++ b/src/services/apimService.test.ts
@@ -70,8 +70,8 @@ describe("APIM Service", () => {
     const apimConfigName = MockFactory.createTestApimConfig(true);
     (serverless.service.provider as any).apim = apimConfigName;
 
-    const service = new ApimService(serverless);
-    const expectedRegionName = AzureNamingService.createShortAzureRegionName(service.getRegion());
+    new ApimService(serverless);
+    const expectedRegionName = AzureNamingService.createShortAzureRegionName(serverless.service.provider.region);
     expect(apimConfigName.name.includes(expectedRegionName)).toBeTruthy();
   });
 
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index f37557a..a7430e0 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -1,10 +1,9 @@
 import fs from "fs";
 import mockFs from "mock-fs";
 import Serverless from "serverless";
-import { ServerlessAzureOptions, ServerlessAzureConfig } from "../models/serverless";
+import { ServerlessAzureOptions } from "../models/serverless";
 import { MockFactory } from "../test/mockFactory";
 import { BaseService } from "./baseService";
-import { AzureNamingService } from "./namingService";
 
 jest.mock("axios", () => jest.fn());
 import axios from "axios";
@@ -40,11 +39,9 @@ class MockService extends BaseService {
 describe("Base Service", () => {
   let service: MockService;
   let serverless: Serverless;
-  const serviceName = "my-custom-service"
-  const loginResultSubscriptionId = "ABC123";
-  const envVarSubscriptionId = "env var sub id";
-
 
+  const serviceName = "my-custom-service";
+  
   const slsConfig = {
     service: serviceName,
     provider: {
@@ -59,7 +56,6 @@ describe("Base Service", () => {
 
   function createMockService(sls: Serverless, options?: Serverless.Options) {
     sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials();
-    sls.variables["subscriptionId"] = loginResultSubscriptionId;
     Object.assign(sls.service, slsConfig);
 
     return new MockService(sls, options);
@@ -87,65 +83,7 @@ describe("Base Service", () => {
     expect(mockService).not.toBeNull();
     expect(serverless.service.provider.region).toEqual("westus");
     expect(serverless.service.provider.stage).toEqual("dev");
-  });
-
-  it("returns region and stage based on CLI options", () => {
-    const cliOptions = {
-      stage: "prod",
-      region: "eastus2",
-    };
-    const mockService = new MockService(serverless, cliOptions);
-
-    expect(mockService.getRegion()).toEqual(cliOptions.region);
-    expect(mockService.getStage()).toEqual(cliOptions.stage);
-  });
-
-  it("use the resource group name specified in CLI", () => {
-    const resourceGroupName = "cliResourceGroupName"
-    const cliOptions = {
-      stage: "prod",
-      region: "eastus2",
-      resourceGroup: resourceGroupName
-    };
-
-    const mockService = new MockService(serverless, cliOptions);
-    const actualResourceGroupName = mockService.getResourceGroupName();
-
-    expect(actualResourceGroupName).toEqual(resourceGroupName);
-  });
-
-  it("use the resource group name from sls yaml config", () => {
-    const mockService = new MockService(serverless);
-    const actualResourceGroupName = mockService.getResourceGroupName();
-
-    expect(actualResourceGroupName).toEqual(serverless.service.provider["resourceGroup"]);
-  });
-
-  it("Generates resource group from convention when NOT defined in sls yaml", () => {
-    serverless.service.provider["resourceGroup"] = null;
-    const mockService = new MockService(serverless);
-    const actualResourceGroupName = mockService.getResourceGroupName();
-    const expectedRegion = AzureNamingService.createShortAzureRegionName(mockService.getRegion());
-    const expectedStage = AzureNamingService.createShortStageName(mockService.getStage());
-    const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${serverless.service["service"]}-rg`;
-
-    expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
-  });
-
-  it("set default prefix when one is not defined in yaml config", () => {
-    const mockService = new MockService(serverless);
-    const actualPrefix = mockService.getPrefix();
-    expect(actualPrefix).toEqual("sls");
-  });
-
-  it("use the prefix defined in sls yaml config", () => {
-    const expectedPrefix = "testPrefix"
-    serverless.service.provider["prefix"] = expectedPrefix;
-    const mockService = new MockService(serverless);
-    const actualPrefix = mockService.getPrefix();
-
-    expect(actualPrefix).toEqual(expectedPrefix);
-  });
+  });  
 
   it("Fails if credentials have not been set in serverless config", () => {
     serverless.variables["azureCredentials"] = null;
@@ -202,91 +140,4 @@ describe("Base Service", () => {
 
     readStreamSpy.mockRestore();
   });
-
-  it("sets stage name from CLI", async () => {
-    const stage = "test";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider.stage).not.toEqual(stage);
-    service = new MockService(serverless, { stage } as any);
-    expect(service.getStage()).toEqual(stage);
-    expect(service.getResourceGroupName()).toEqual(`sls-wus-${stage}-${serviceName}-rg`);
-  });
-
-  it("sets region name from CLI", async () => {
-    const region = "East US";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider.region).not.toEqual(region);
-    service = new MockService(serverless, { region } as any);
-    expect(service.getRegion()).toEqual(region);
-    expect(service.getResourceGroupName()).toEqual(`sls-eus-dev-${serviceName}-rg`);
-  });
-
-  it("sets prefix from CLI", async () => {
-    const prefix = "prefix";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider["prefix"]).not.toEqual(prefix);
-    service = new MockService(serverless, { prefix } as any);
-    expect(service.getPrefix()).toEqual(prefix);
-    expect(service.getResourceGroupName()).toEqual(`${prefix}-wus-dev-${serviceName}-rg`);
-  });
-
-  it("sets resource group from CLI", async () => {
-    const resourceGroup = "resourceGroup";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
-    service = new MockService(serverless, { resourceGroup } as any);
-    expect(service.getResourceGroupName()).toEqual(resourceGroup);
-  });
-
-  const cliSubscriptionId = "cli sub id";
-  const configSubscriptionId = "config sub id";
-
-  it("sets subscription ID from CLI", async () => {
-    process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
-    serverless.service.provider["subscriptionId"] = configSubscriptionId;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { subscriptionId: cliSubscriptionId } as any);
-    expect(service.getSubscriptionId()).toEqual(cliSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(cliSubscriptionId);
-  });
-
-  it("sets subscription ID from environment variable", async () => {
-    process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
-    serverless.service.provider["subscriptionId"] = configSubscriptionId;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { } as any);
-    expect(service.getSubscriptionId()).toEqual(envVarSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(envVarSubscriptionId);
-  });
-
-  it("sets subscription ID from config", async () => {
-    delete process.env.AZURE_SUBSCRIPTION_ID;
-    serverless.service.provider["subscriptionId"] = configSubscriptionId;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { } as any);
-    expect(service.getSubscriptionId()).toEqual(configSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(configSubscriptionId);
-  });
-
-  it("sets subscription ID from login result", async () => {
-    delete process.env.AZURE_SUBSCRIPTION_ID;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { } as any);
-    expect(service.getSubscriptionId()).toEqual(loginResultSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(loginResultSubscriptionId);
-  });
-
-  it("sets region to be value from location property if region not set", () => {
-    const slsService = MockFactory.createTestService();
-    delete slsService.provider.region;
-    const location = "East US";
-    slsService.provider["location"] = location;
-
-    const sls = MockFactory.createTestServerless({
-      service: slsService
-    });
-
-    service = new MockService(sls, {} as any);
-    expect(service.getRegion()).toEqual(location);
-  });
 });
diff --git a/src/services/configService.test.ts b/src/services/configService.test.ts
new file mode 100644
index 0000000..8c24874
--- /dev/null
+++ b/src/services/configService.test.ts
@@ -0,0 +1,164 @@
+import { ConfigService } from "./configService";
+import Serverless from "serverless";
+import { MockFactory } from "../test/mockFactory";
+import { ServerlessAzureConfig } from "../models/serverless";
+import configConstants from "../config";
+import { AzureNamingService } from "./namingService";
+
+describe("Config Service", () => {
+  const serviceName = "my-custom-service"
+
+  let serverless: Serverless;
+
+  beforeEach(() => {
+    serverless = MockFactory.createTestServerless();
+    const config = (serverless.service as any as ServerlessAzureConfig);
+    config.service = serviceName;
+
+    delete config.provider.resourceGroup;
+    delete config.provider.region;
+    delete config.provider.stage;
+    delete config.provider.prefix;
+  });
+
+  describe("Configurable Variables", () => {
+    it("returns default values if not specified", () => {
+      const service = new ConfigService(serverless, {} as any);
+      const { prefix, region, stage } = configConstants.defaults;
+      expect(service.getPrefix()).toEqual(prefix);
+      expect(service.getStage()).toEqual(stage);
+      expect(service.getRegion()).toEqual(region);
+    });
+
+    it("use prefix from the CLI over the SLS yml config", () => {
+      const prefix = "prefix";
+      const config = (serverless.service as any as ServerlessAzureConfig);
+      delete config.provider.resourceGroup;
+      config.provider.prefix = "not the prefix";    
+      expect(serverless.service.provider["prefix"]).not.toEqual(prefix);
+      const service = new ConfigService(serverless, { prefix } as any);
+      expect(service.getPrefix()).toEqual(prefix);
+      expect(service.getResourceGroupName()).toEqual(`${prefix}-wus-dev-${serviceName}-rg`);
+    });
+
+    it("use region name from the CLI over the SLS yml config", () => {
+      const region = "East US";
+      const config = (serverless.service as any as ServerlessAzureConfig);
+      delete config.provider.resourceGroup;
+
+      expect(serverless.service.provider.region).not.toEqual(region);
+      const service = new ConfigService(serverless, { region } as any);
+      expect(service.getRegion()).toEqual(region);
+      expect(service.getResourceGroupName()).toEqual(`sls-eus-dev-${serviceName}-rg`);
+    });
+
+    it("use stage name from the CLI over the SLS yml config", () => {
+      const stage = "test";
+      expect(serverless.service.provider.stage).not.toEqual(stage);
+      const service = new ConfigService(serverless, { stage } as any);
+      expect(service.getStage()).toEqual(stage);
+      expect(service.getResourceGroupName()).toEqual(`sls-wus-${stage}-${serviceName}-rg`);
+    });
+
+    it("use the resource group name from the CLI over the SLS yml config", () => {
+      const resourceGroup = "resourceGroup";
+      const config = (serverless.service as any as ServerlessAzureConfig);
+      config.provider.resourceGroup = "not the resource group";
+      expect(serverless.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
+      const service = new ConfigService(serverless, { resourceGroup } as any);
+      expect(service.getResourceGroupName()).toEqual(resourceGroup);
+    });
+
+    it("use the prefix defined in SLS yml config", () => {
+      const expectedPrefix = "testPrefix"
+      serverless.service.provider["prefix"] = expectedPrefix;
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getPrefix()).toEqual(expectedPrefix);
+    });
+
+    it("use region from SLS yml config", () => {
+      const expectedRegion = "eastus2"
+      serverless.service.provider["region"] = expectedRegion;
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getRegion()).toEqual(expectedRegion);
+    });
+
+    it("use stage name from SLS yml config", () => {
+      const expectedStage = "testStage"
+      serverless.service.provider["stage"] = expectedStage;
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getStage()).toEqual(expectedStage);
+    });
+
+    it("use the resource group name from SLS yml config", () => {
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getResourceGroupName()).toEqual(serverless.service.provider["resourceGroup"]);
+    });
+
+    it("use location property as region if region not set", () => {
+      const slsService = MockFactory.createTestService();
+      delete slsService.provider.region;
+      const location = "East US";
+      slsService.provider["location"] = location;
+  
+      const sls = MockFactory.createTestServerless({
+        service: slsService
+      });
+  
+      const service = new ConfigService(sls, {} as any);
+      expect(service.getRegion()).toEqual(location);
+    });
+  
+    it("Generates resource group from convention when NOT defined in sls yaml", () => {
+      serverless.service.provider["resourceGroup"] = null;
+      const service = new ConfigService(serverless, { } as any);
+      const actualResourceGroupName = service.getResourceGroupName();
+      const expectedRegion = AzureNamingService.createShortAzureRegionName(service.getRegion());
+      const expectedStage = AzureNamingService.createShortStageName(service.getStage());
+      const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${serverless.service["service"]}-rg`;
+      expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
+    });
+  });
+  
+  describe("Service Principal Configuration", () => {
+    const cliSubscriptionId = "cli sub id";
+    const envVarSubscriptionId = "env var sub id";
+    const configSubscriptionId = "config sub id";
+    const loginResultSubscriptionId = "ABC123";
+  
+    it("use subscription ID from the CLI", () => {
+      process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
+      serverless.service.provider["subscriptionId"] = configSubscriptionId;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { subscriptionId: cliSubscriptionId } as any);
+      expect(service.getSubscriptionId()).toEqual(cliSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(cliSubscriptionId);
+    });
+  
+    it("use subscription ID from environment variable", () => {
+      process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
+      serverless.service.provider["subscriptionId"] = configSubscriptionId;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getSubscriptionId()).toEqual(envVarSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(envVarSubscriptionId);
+    });
+  
+    it("use subscription ID from config", () => {
+      delete process.env.AZURE_SUBSCRIPTION_ID;
+      serverless.service.provider["subscriptionId"] = configSubscriptionId;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getSubscriptionId()).toEqual(configSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(configSubscriptionId);
+    });
+  
+    it("use subscription ID from login result", () => {
+      delete process.env.AZURE_SUBSCRIPTION_ID;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getSubscriptionId()).toEqual(loginResultSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(loginResultSubscriptionId);
+    });
+  });
+});
diff --git a/src/services/invokeService.test.ts b/src/services/invokeService.test.ts
index ab97587..f4b6b8a 100644
--- a/src/services/invokeService.test.ts
+++ b/src/services/invokeService.test.ts
@@ -20,7 +20,7 @@ describe("Invoke Service ", () => {
   const functionName = "hello";
   const urlPOST = `http://${app.defaultHostName}/api/${functionName}`;
   const urlGET = `http://${app.defaultHostName}/api/${functionName}?name%3D${testData}`;
-  const localUrl = `http://localhost:${configConstants.defaultLocalPort}/api/${functionName}`
+  const localUrl = `http://localhost:${configConstants.defaults.localPort}/api/${functionName}`
   let masterKey: string;
   let sls = MockFactory.createTestServerless();
   let options = {