Repository: serverless/serverless-azure-functions
Pull request #457 (2020-05-18 22:01:38)

ci: .NET Core Integration tests & Workflow fixes
<!--
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:

Adds .NET core integration tests that will be triggered 2x daily as well as any pushes to branch with pattern `*/trigger-int-tests`

<!--
Briefly describe the feature if no issue exists for this PR
-->

## How did you implement it:

- Added templates for .NET Core versions 3.1 & 2.2 for Linux and Windows
- Updated the workflow to create jobs for all multi-os runtimes, keeping Python in its own job

<!--
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:

Check out and push to a branch with pattern `*/trigger-int-tests` and go to `Actions` page on repo

<!--
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
* 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] 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] Write tests and confirm existing functionality is not broken.  
       **Validate via `npm test`**
- [x] Write documentation
- [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


tbarlow12: Just a heads up to reviewers: The only reason integration tests show in this PR is because the workflow is set to trigger on branches that follow the pattern `*/trigger-int-tests`. This was used to test the workflow, and will be used in the future to manually trigger the workflow if needed. They will not be part of PRs going forward

Patch:
diff --git a/README.md b/README.md
index 2cdb8fb..28affc8 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,7 @@
 
 This plugin enables Azure Functions support within the Serverless Framework.
 
-[![Code Coverage](https://codecov.io/gh/serverless/serverless-azure-functions/branch/dev/graph/badge.svg)](https://codecov.io/gh/serverless/serverless-azure-functions)
-
-![Integration Tests](https://github.com/serverless/serverless-azure-functions/workflows/Integration%20Tests/badge.svg)
+[![Code Coverage](https://codecov.io/gh/serverless/serverless-azure-functions/branch/dev/graph/badge.svg)](https://codecov.io/gh/serverless/serverless-azure-functions) [![Node Integration Tests](https://github.com/serverless/serverless-azure-functions/workflows/Node%20Integration%20Tests/badge.svg)](https://github.com/serverless/serverless-azure-functions/actions?query=workflow%3A%22Node+Integration+Tests%22) [![Python Integration Tests](https://github.com/serverless/serverless-azure-functions/workflows/Python%20Integration%20Tests/badge.svg)](https://github.com/serverless/serverless-azure-functions/actions?query=workflow%3A%22Python+Integration+Tests%22) [![.NET Integration Tests](https://github.com/serverless/serverless-azure-functions/workflows/.NET%20Integration%20Tests/badge.svg)](https://github.com/serverless/serverless-azure-functions/actions?query=workflow%3A%22.NET+Integration+Tests%22)
 
 ## Quickstart
 
diff --git a/package-lock.json b/package-lock.json
index fbc2e21..7800a5b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.2-36",
+  "version": "1.0.2-37",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -3497,9 +3497,9 @@
       }
     },
     "clvr": {
-      "version": "0.6.8",
-      "resolved": "https://registry.npmjs.org/clvr/-/clvr-0.6.8.tgz",
-      "integrity": "sha512-0xKmSBdafU3xZFo8LewEJ7uf7Pt+SK3d5ZJ3gjfcV/PNLyR7jOGbU7AkDegvLldKGjCChzxBfA/YhBCgDDw9zQ==",
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/clvr/-/clvr-0.7.1.tgz",
+      "integrity": "sha512-S4yGNZAvx1k+D5erdKUN5v32tUpGfx6plImr0GP2GeoFTnQ7yvCJKCo7+rARW3rciWVnBTSMUFwpMAjOqTCq/Q==",
       "dev": true,
       "requires": {
         "chalk": "^4.0.0",
diff --git a/package.json b/package.json
index 4cb0513..74b443f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.2-36",
+  "version": "1.0.2-37",
   "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.",
   "license": "MIT",
   "main": "./lib/index.js",
@@ -86,7 +86,7 @@
     "axios-mock-adapter": "^1.16.0",
     "babel-jest": "^24.8.0",
     "babel-preset-react-app": "^9.0.0",
-    "clvr": "^0.6.8",
+    "clvr": "^0.7.1",
     "conventional-changelog-cli": "^2.0.23",
     "eslint": "^5.16.0",
     "jest": "^24.8.0",
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 87ef4d1..eb3427f 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -13,6 +13,7 @@ import { AzureBlobStorageService } from "./azureBlobStorageService";
 import { BaseService } from "./baseService";
 import { constants } from "../shared/constants";
 import { FunctionAppOS } from "../config/runtime";
+const packageJson = require("../../package.json"); // eslint-disable-line @typescript-eslint/no-var-requires
 
 export class FunctionAppService extends BaseService {
   private static readonly retryCount: number = 30;
@@ -220,10 +221,12 @@ export class FunctionAppService extends BaseService {
         Authorization: `Bearer ${await this.getAccessToken()}`,
         Accept: "*/*",
         ContentType: "application/octet-stream",
-        "User-Agent": "serverless_framework", //TODO Underscore version name of plugin/cli
+        "User-Agent": `serverless_framework/${this.serverless.version} serverless_azure_functions/${packageJson.version}`, //TODO Underscore version name of plugin/cli
       }
     };
 
+    this.prettyPrint(requestOptions);
+
     await this.sendFile(requestOptions, functionZipFile);
     this.log("-> Function package uploaded successfully");
   }
diff --git a/src/services/invokeService.ts b/src/services/invokeService.ts
index d8077ec..6ff8b0b 100644
--- a/src/services/invokeService.ts
+++ b/src/services/invokeService.ts
@@ -1,4 +1,4 @@
-import axios from "axios";
+import axios, { AxiosRequestConfig } from "axios";
 import { stringify } from "querystring";
 import Serverless from "serverless";
 import { ApimResource } from "../armTemplates/resources/apim";
@@ -46,16 +46,22 @@ export class InvokeService extends BaseService {
       this.log("Needs to be an http function");
       return;
     }
+    const options: AxiosRequestConfig = await this.getRequestOptions(method, data);
 
     let url = await this.getUrl(functionName);
     if (method === "GET" && data) {
       url += `?${this.getQueryString(data)}`;
+      options.params = data;
     }
 
-    const options = await this.getRequestOptions(method, data);
     this.log(`Invocation url: ${url}`);
     this.log(`Invoking function ${functionName} with ${method} request`);
-    return await axios(url, options,);
+    try {
+      return await axios(url, options,);
+    } catch (err) {
+      const response = err.response;
+      throw new Error(`HTTP Error: ${response.status} - ${response.data}`);
+    }
   }
 
   private async getUrl(functionName: string) {
diff --git a/src/shared/bindings.ts b/src/shared/bindings.ts
index 33248bd..b0e85c9 100644
--- a/src/shared/bindings.ts
+++ b/src/shared/bindings.ts
@@ -1,6 +1,5 @@
 import Serverless from "serverless";
 import { constants } from "./constants";
-import axios from "axios";
 const defaultBindingsJson = require("./bindings.json"); // eslint-disable-line @typescript-eslint/no-var-requires
 
 export class BindingUtils {


Test patch:
diff --git a/.github/workflows/integration-tests-dotnet.yml b/.github/workflows/integration-tests-dotnet.yml
new file mode 100644
index 0000000..2f1abda
--- /dev/null
+++ b/.github/workflows/integration-tests-dotnet.yml
@@ -0,0 +1,57 @@
+on:
+  schedule:
+    # Run twice per day (1:00 AM & 1:00 PM)
+    - cron: '0 1,13 * * *'
+  push:
+    branches:
+      # Push to a branch that ends with "/trigger-int-tests" to manually trigger tests
+      - '**/dotnet-int-tests'
+name: .NET Integration Tests
+jobs:
+  integration_tests:
+    name: Test
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        runtime: [ dotnet22, dotnet31 ]
+        os: [ linux, windows ]
+      fail-fast: false
+    steps:
+    - uses: actions/checkout@master
+    
+    # Install dependencies and cache them
+    - name: Cache node_modules
+      id: cache-modules
+      uses: actions/cache@v1
+      with:
+        path: node_modules
+        key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
+    - name: npm install
+      if: steps.cache-modules.outputs.cache-hit != 'true'
+      run: npm install
+    
+    # Build plugin
+    - name: build
+      run: tsc
+    
+    # Setup dotnet when needed
+    - name: setup dotnet 2.2
+      if: matrix.runtime == 'dotnet22'
+      uses: actions/setup-dotnet@v1
+      with:
+        dotnet-version: '2.2.100'
+    - name: setup dotnet 3.1
+      if: matrix.runtime == 'dotnet31'
+      uses: actions/setup-dotnet@v1
+      with:
+        dotnet-version: '3.1.103'
+    
+    # Run the integration test
+    - name: package, deploy, invoke, remove
+      run: npm run test:int -- -t deploy.* -d '${{ matrix.runtime }}-${{ matrix.os }}'
+      env:
+        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID}}
+        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
+        AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
+        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID}}
+  
\ No newline at end of file
diff --git a/.github/workflows/integration-tests-node.yml b/.github/workflows/integration-tests-node.yml
new file mode 100644
index 0000000..4f5a8ab
--- /dev/null
+++ b/.github/workflows/integration-tests-node.yml
@@ -0,0 +1,45 @@
+on:
+  schedule:
+    # Run twice per day (2:00 AM & 2:00 PM)
+    - cron: '0 2/14 * * *'
+  push:
+    branches:
+      # Push to a branch that ends with "/trigger-int-tests" to manually trigger tests
+      - '**/node-int-tests'
+name: Node Integration Tests
+jobs:
+  multi_os_integration_tests:
+    name: Test
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        runtime: [ node10, node12 ]
+        os: [ linux, windows ]
+      fail-fast: false
+    steps:
+    - uses: actions/checkout@master
+    
+    # Install dependencies and cache them
+    - name: Cache node_modules
+      id: cache-modules
+      uses: actions/cache@v1
+      with:
+        path: node_modules
+        key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
+    - name: npm install
+      if: steps.cache-modules.outputs.cache-hit != 'true'
+      run: npm install
+    
+    # Build plugin
+    - name: build
+      run: tsc
+    
+    # Run the integration test
+    - name: package, deploy, invoke, remove
+      run: npm run test:int -- -t deploy.* -d '${{ matrix.runtime }}-${{ matrix.os }}(-external|-webpack)?'
+      env:
+        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID}}
+        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
+        AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
+        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID}}
+  
\ No newline at end of file
diff --git a/.github/workflows/integration-tests-python.yml b/.github/workflows/integration-tests-python.yml
new file mode 100644
index 0000000..c645464
--- /dev/null
+++ b/.github/workflows/integration-tests-python.yml
@@ -0,0 +1,37 @@
+on:
+  schedule:
+    # Run twice per day (3:00 AM & 3:00 PM)
+    - cron: '0 3/15 * * *'
+  push:
+    branches:
+      # Push to a branch that ends with "/trigger-int-tests" to manually trigger tests
+      - '**/python-int-tests'
+name: Python Integration Tests
+jobs:
+  int_tests_python:
+    name: Test
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        runtime: [ python36, python37, python38 ]
+    steps:
+    - uses: actions/checkout@master
+    - name: Cache node_modules
+      id: cache-modules
+      uses: actions/cache@v1
+      with:
+        path: node_modules
+        key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
+    - name: npm install
+      if: steps.cache-modules.outputs.cache-hit != 'true'
+      run: npm install
+    - name: build
+      run: tsc
+    - name: package, deploy, invoke, remove
+      run: npm run test:int -- -t deploy.* -d ${{ matrix.runtime }}
+      env:
+        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID}}
+        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
+        AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
+        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID}}
+  
\ No newline at end of file
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
deleted file mode 100644
index d750899..0000000
--- a/.github/workflows/integration-tests.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-on:
-  schedule:
-    # Run twice per day (12:45 AM & PM)
-    - cron: '45 */12 * * *'
-  push:
-    branches:
-      # Push to a branch that ends with "/trigger-int-tests" to manually trigger tests
-      - '**/trigger-int-tests'
-name: Integration Tests
-jobs:
-  int_tests_node_10:
-    name: Node 10
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@master
-    - name: Cache node_modules
-      id: cache-modules
-      uses: actions/cache@v1
-      with:
-        path: node_modules
-        key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
-    - name: npm install
-      if: steps.cache-modules.outputs.cache-hit != 'true'
-      run: npm install
-    - name: build
-      run: tsc
-    - name: node 10 integration tests
-      run: npm run test:int -- -t deploy -d node10
-      env:
-        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID}}
-        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
-        AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
-        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID}}
-  int_tests_node_12:
-    name: Node 12
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@master
-    - name: Cache node_modules
-      id: cache-modules
-      uses: actions/cache@v1
-      with:
-        path: node_modules
-        key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
-    - name: npm install
-      if: steps.cache-modules.outputs.cache-hit != 'true'
-      run: npm install
-    - name: build
-      run: tsc
-    - name: node 12 integration tests
-      run: npm run test:int -- -t deploy -d node12
-      env:
-        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID}}
-        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
-        AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
-        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID}}
-  int_tests_python:
-    name: Python
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@master
-    - name: Cache node_modules
-      id: cache-modules
-      uses: actions/cache@v1
-      with:
-        path: node_modules
-        key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
-    - name: npm install
-      if: steps.cache-modules.outputs.cache-hit != 'true'
-      run: npm install
-    - name: build
-      run: tsc
-    - name: python integration tests
-      run: npm run test:int -- -t deploy -d python
-      env:
-        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID}}
-        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
-        AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
-        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID}}
diff --git a/integrationTests/configurations/dotnet22-linux/.gitignore b/integrationTests/configurations/dotnet22-linux/.gitignore
new file mode 100644
index 0000000..ff5b00c
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-linux/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# Azure Functions localsettings file
+local.settings.json
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-linux/hello.cs b/integrationTests/configurations/dotnet22-linux/hello.cs
new file mode 100644
index 0000000..5779733
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-linux/hello.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+namespace Company.Function
+{
+    public static class hello
+    {
+        [FunctionName("hello")]
+        public static async Task<IActionResult> Run(
+            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
+            ILogger log)
+        {
+            log.LogInformation("C# HTTP trigger function processed a request.");
+
+            string name = req.Query["name"];
+
+            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+            dynamic data = JsonConvert.DeserializeObject(requestBody);
+            name = name ?? data?.name;
+
+            string responseMessage = string.IsNullOrEmpty(name)
+                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
+                : $"Hello {name} {Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME")} " +
+                    $"Environment variable: {Environment.GetEnvironmentVariable("VARIABLE_FOO")}";
+
+            return new OkObjectResult(responseMessage);
+        }
+    }
+}
diff --git a/integrationTests/configurations/dotnet22-linux/host.json b/integrationTests/configurations/dotnet22-linux/host.json
new file mode 100644
index 0000000..bb3b8da
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-linux/host.json
@@ -0,0 +1,11 @@
+{
+    "version": "2.0",
+    "logging": {
+        "applicationInsights": {
+            "samplingExcludedTypes": "Request",
+            "samplingSettings": {
+                "isEnabled": true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-linux/package.json b/integrationTests/configurations/dotnet22-linux/package.json
new file mode 100644
index 0000000..fa78fe7
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-linux/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "sls-csharp",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
diff --git a/integrationTests/configurations/dotnet22-linux/serverless.yml b/integrationTests/configurations/dotnet22-linux/serverless.yml
new file mode 100644
index 0000000..c936511
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-linux/serverless.yml
@@ -0,0 +1,24 @@
+service: dt22-lin
+
+provider:
+  name: azure
+  runtime: dotnet2.2
+  region: West Europe
+  prefix: tmp
+  stage: qa
+  os: linux
+  apim: true
+  environment:
+    VARIABLE_FOO: 'foo'
+
+plugins:
+  - serverless-azure-functions
+
+functions:
+  hello:
+    handler: hello
+    events:
+      - http: true
+        methods:
+          - GET
+        authLevel: anonymous
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-linux/sls-csharp.csproj b/integrationTests/configurations/dotnet22-linux/sls-csharp.csproj
new file mode 100644
index 0000000..7908924
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-linux/sls-csharp.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
+    <RootNamespace>sls_csharp</RootNamespace>
+    <EnableDefaultCompileItems>False</EnableDefaultCompileItems>
+    <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.31" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Update="host.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="local.settings.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="*.cs" Exclude="node_modules\**\*.cs" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-windows/.gitignore b/integrationTests/configurations/dotnet22-windows/.gitignore
new file mode 100644
index 0000000..ff5b00c
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-windows/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# Azure Functions localsettings file
+local.settings.json
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-windows/hello.cs b/integrationTests/configurations/dotnet22-windows/hello.cs
new file mode 100644
index 0000000..5779733
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-windows/hello.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+namespace Company.Function
+{
+    public static class hello
+    {
+        [FunctionName("hello")]
+        public static async Task<IActionResult> Run(
+            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
+            ILogger log)
+        {
+            log.LogInformation("C# HTTP trigger function processed a request.");
+
+            string name = req.Query["name"];
+
+            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+            dynamic data = JsonConvert.DeserializeObject(requestBody);
+            name = name ?? data?.name;
+
+            string responseMessage = string.IsNullOrEmpty(name)
+                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
+                : $"Hello {name} {Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME")} " +
+                    $"Environment variable: {Environment.GetEnvironmentVariable("VARIABLE_FOO")}";
+
+            return new OkObjectResult(responseMessage);
+        }
+    }
+}
diff --git a/integrationTests/configurations/dotnet22-windows/host.json b/integrationTests/configurations/dotnet22-windows/host.json
new file mode 100644
index 0000000..bb3b8da
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-windows/host.json
@@ -0,0 +1,11 @@
+{
+    "version": "2.0",
+    "logging": {
+        "applicationInsights": {
+            "samplingExcludedTypes": "Request",
+            "samplingSettings": {
+                "isEnabled": true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-windows/package.json b/integrationTests/configurations/dotnet22-windows/package.json
new file mode 100644
index 0000000..fa78fe7
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-windows/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "sls-csharp",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
diff --git a/integrationTests/configurations/dotnet22-windows/serverless.yml b/integrationTests/configurations/dotnet22-windows/serverless.yml
new file mode 100644
index 0000000..5d7c5e0
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-windows/serverless.yml
@@ -0,0 +1,23 @@
+service: dt22-win
+
+provider:
+  name: azure
+  runtime: dotnet2.2
+  region: West Europe
+  prefix: tmp
+  stage: qa
+  apim: true
+  environment:
+    VARIABLE_FOO: 'foo'
+
+plugins:
+  - serverless-azure-functions
+
+functions:
+  hello:
+    handler: hello
+    events:
+      - http: true
+        methods:
+          - GET
+        authLevel: anonymous
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet22-windows/sls-csharp.csproj b/integrationTests/configurations/dotnet22-windows/sls-csharp.csproj
new file mode 100644
index 0000000..7908924
--- /dev/null
+++ b/integrationTests/configurations/dotnet22-windows/sls-csharp.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
+    <RootNamespace>sls_csharp</RootNamespace>
+    <EnableDefaultCompileItems>False</EnableDefaultCompileItems>
+    <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.31" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Update="host.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="local.settings.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="*.cs" Exclude="node_modules\**\*.cs" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-linux/.gitignore b/integrationTests/configurations/dotnet31-linux/.gitignore
new file mode 100644
index 0000000..ff5b00c
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-linux/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# Azure Functions localsettings file
+local.settings.json
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-linux/hello.cs b/integrationTests/configurations/dotnet31-linux/hello.cs
new file mode 100644
index 0000000..5779733
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-linux/hello.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+namespace Company.Function
+{
+    public static class hello
+    {
+        [FunctionName("hello")]
+        public static async Task<IActionResult> Run(
+            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
+            ILogger log)
+        {
+            log.LogInformation("C# HTTP trigger function processed a request.");
+
+            string name = req.Query["name"];
+
+            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+            dynamic data = JsonConvert.DeserializeObject(requestBody);
+            name = name ?? data?.name;
+
+            string responseMessage = string.IsNullOrEmpty(name)
+                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
+                : $"Hello {name} {Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME")} " +
+                    $"Environment variable: {Environment.GetEnvironmentVariable("VARIABLE_FOO")}";
+
+            return new OkObjectResult(responseMessage);
+        }
+    }
+}
diff --git a/integrationTests/configurations/dotnet31-linux/host.json b/integrationTests/configurations/dotnet31-linux/host.json
new file mode 100644
index 0000000..bb3b8da
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-linux/host.json
@@ -0,0 +1,11 @@
+{
+    "version": "2.0",
+    "logging": {
+        "applicationInsights": {
+            "samplingExcludedTypes": "Request",
+            "samplingSettings": {
+                "isEnabled": true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-linux/package.json b/integrationTests/configurations/dotnet31-linux/package.json
new file mode 100644
index 0000000..fa78fe7
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-linux/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "sls-csharp",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
diff --git a/integrationTests/configurations/dotnet31-linux/serverless.yml b/integrationTests/configurations/dotnet31-linux/serverless.yml
new file mode 100644
index 0000000..ab3f7ff
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-linux/serverless.yml
@@ -0,0 +1,24 @@
+service: dt31-lin
+
+provider:
+  name: azure
+  runtime: dotnet3.1
+  region: West Europe
+  prefix: tmp
+  stage: qa
+  os: linux
+  apim: true
+  environment:
+    VARIABLE_FOO: 'foo'
+
+plugins:
+  - serverless-azure-functions
+
+functions:
+  hello:
+    handler: hello
+    events:
+      - http: true
+        methods:
+          - GET
+        authLevel: anonymous
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-linux/sls-csharp.csproj b/integrationTests/configurations/dotnet31-linux/sls-csharp.csproj
new file mode 100644
index 0000000..204b361
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-linux/sls-csharp.csproj
@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
+    <RootNamespace>sls_csharp</RootNamespace>
+    <EnableDefaultCompileItems>False</EnableDefaultCompileItems>
+    <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7" />
+    <PackageReference Include="Microsoft.NETCore.App" Version="2.2.8" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Update="host.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="local.settings.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="*.cs" Exclude="node_modules\**\*.cs" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-windows/.gitignore b/integrationTests/configurations/dotnet31-windows/.gitignore
new file mode 100644
index 0000000..ff5b00c
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-windows/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# Azure Functions localsettings file
+local.settings.json
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-windows/hello.cs b/integrationTests/configurations/dotnet31-windows/hello.cs
new file mode 100644
index 0000000..5779733
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-windows/hello.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+namespace Company.Function
+{
+    public static class hello
+    {
+        [FunctionName("hello")]
+        public static async Task<IActionResult> Run(
+            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
+            ILogger log)
+        {
+            log.LogInformation("C# HTTP trigger function processed a request.");
+
+            string name = req.Query["name"];
+
+            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+            dynamic data = JsonConvert.DeserializeObject(requestBody);
+            name = name ?? data?.name;
+
+            string responseMessage = string.IsNullOrEmpty(name)
+                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
+                : $"Hello {name} {Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME")} " +
+                    $"Environment variable: {Environment.GetEnvironmentVariable("VARIABLE_FOO")}";
+
+            return new OkObjectResult(responseMessage);
+        }
+    }
+}
diff --git a/integrationTests/configurations/dotnet31-windows/host.json b/integrationTests/configurations/dotnet31-windows/host.json
new file mode 100644
index 0000000..bb3b8da
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-windows/host.json
@@ -0,0 +1,11 @@
+{
+    "version": "2.0",
+    "logging": {
+        "applicationInsights": {
+            "samplingExcludedTypes": "Request",
+            "samplingSettings": {
+                "isEnabled": true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-windows/package.json b/integrationTests/configurations/dotnet31-windows/package.json
new file mode 100644
index 0000000..fa78fe7
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-windows/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "sls-csharp",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
diff --git a/integrationTests/configurations/dotnet31-windows/serverless.yml b/integrationTests/configurations/dotnet31-windows/serverless.yml
new file mode 100644
index 0000000..0999789
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-windows/serverless.yml
@@ -0,0 +1,23 @@
+service: dt31-win
+
+provider:
+  name: azure
+  runtime: dotnet3.1
+  region: West Europe
+  prefix: tmp
+  stage: qa
+  apim: true
+  environment:
+    VARIABLE_FOO: 'foo'
+
+plugins:
+  - serverless-azure-functions
+
+functions:
+  hello:
+    handler: hello
+    events:
+      - http: true
+        methods:
+          - GET
+        authLevel: anonymous
\ No newline at end of file
diff --git a/integrationTests/configurations/dotnet31-windows/sls-csharp.csproj b/integrationTests/configurations/dotnet31-windows/sls-csharp.csproj
new file mode 100644
index 0000000..204b361
--- /dev/null
+++ b/integrationTests/configurations/dotnet31-windows/sls-csharp.csproj
@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
+    <RootNamespace>sls_csharp</RootNamespace>
+    <EnableDefaultCompileItems>False</EnableDefaultCompileItems>
+    <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7" />
+    <PackageReference Include="Microsoft.NETCore.App" Version="2.2.8" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Update="host.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="local.settings.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="*.cs" Exclude="node_modules\**\*.cs" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/integrationTests/configurations/node12-windows-premium/serverless.yml b/integrationTests/configurations/node12-windows-premium/serverless.yml
index 704e629..9941aca 100644
--- a/integrationTests/configurations/node12-windows-premium/serverless.yml
+++ b/integrationTests/configurations/node12-windows-premium/serverless.yml
@@ -30,4 +30,4 @@ functions:
           - GET
         authLevel: anonymous
 
- 
\ No newline at end of file
+ 
diff --git a/integrationTests/configurations/python36-premium/serverless.yml b/integrationTests/configurations/python36-premium/serverless.yml
index 2b44567..e75bc65 100644
--- a/integrationTests/configurations/python36-premium/serverless.yml
+++ b/integrationTests/configurations/python36-premium/serverless.yml
@@ -7,6 +7,7 @@ provider:
   stage: qa
   region: West Europe
   apim: true
+  type: premium
   environment:
     VARIABLE_FOO: 'foo'
 
diff --git a/integrationTests/parameters.ts b/integrationTests/parameters.ts
index 99f468f..7234e79 100644
--- a/integrationTests/parameters.ts
+++ b/integrationTests/parameters.ts
@@ -12,6 +12,30 @@ export interface ConfigurationParameters {
 }
 
 export const defaultParameters: ConfigurationParameters = {
+  "dotnet22-linux": {
+    serviceName: "dt22-lin",
+    configName: "dotnet22-linux",
+    runtime: "dotnet",
+    region: "weur",
+  },
+  "dotnet22-windows": {
+    serviceName: "dt22-win",
+    configName: "dotnet22-windows",
+    runtime: "dotnet",
+    region: "weur",
+  },
+  "dotnet31-linux": {
+    serviceName: "dt31-lin",
+    configName: "dotnet31-linux",
+    runtime: "dotnet",
+    region: "weur",
+  },
+  "dotnet31-windows": {
+    serviceName: "dt31-win",
+    configName: "dotnet31-windows",
+    runtime: "dotnet",
+    region: "weur",
+  },
   "node10-linux": {
     serviceName: "nd10-lin",
     configName: "node10-linux",
diff --git a/integrationTests/tests/deploy.clvr.ts b/integrationTests/tests/deploy.clvr.ts
index df946fe..6611089 100644
--- a/integrationTests/tests/deploy.clvr.ts
+++ b/integrationTests/tests/deploy.clvr.ts
@@ -10,20 +10,23 @@ run({
       silent: true,
     },
     {
-      command: "sls deploy",
+      command: "sls package",
+    },
+    {
+      command: "sls deploy -p .serverless/${serviceName}.zip",
+      retries: 3,
       stdout: {
         shouldContain: [
           "Logging into Azure",
           "Deployed serverless functions:",
           "tmp-${region}-qa-${serviceName}-rg",
-          "Resource Group: tmp-${region}-qa-${serviceName}-rg",
-          "Deploying zip file to function app: tmp-${region}-qa-${serviceName}",
           "-> hello: [GET] tmp-${region}-qa-${serviceName}.azurewebsites.net/api/hello"
         ],
       }
     },
     {
       command: `sls invoke -f hello -d ${JSON.stringify({name: "Azure"}).replace(" ", "")}`,
+      retries: 3,
       stdout: {
         shouldContain: [
           "Hello Azure ${runtime}",
@@ -36,6 +39,7 @@ run({
     },
     {
       command: `sls invoke apim -f hello -d ${JSON.stringify({name: "Azure"}).replace(" ", "")}`,
+      retries: 3,
       stdout: {
         shouldContain: [
           "Hello Azure ${runtime}",
@@ -48,6 +52,7 @@ run({
     },
     {
       command: "sls remove --force",
+      retries: 3,
       stdout: {
         shouldContain: [
           "Service successfully removed"
diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts
index 4ca7781..7c71b19 100644
--- a/src/services/functionAppService.test.ts
+++ b/src/services/functionAppService.test.ts
@@ -296,7 +296,7 @@ describe("Function App Service", () => {
         Authorization: `Bearer ${(await variables["azureCredentials"].getToken()).accessToken}`,
         Accept: "*/*",
         ContentType: "application/octet-stream",
-        "User-Agent": "serverless_framework",
+        "User-Agent": expect.stringContaining("serverless_framework"),
       }
     }, defaultArtifact);
     const expectedArtifactName = service.getDeploymentName().replace("rg-deployment", "artifact");
@@ -331,7 +331,7 @@ describe("Function App Service", () => {
         Authorization: `Bearer ${(await variables["azureCredentials"].getToken()).accessToken}`,
         Accept: "*/*",
         ContentType: "application/octet-stream",
-        "User-Agent": "serverless_framework",
+        "User-Agent": expect.stringContaining("serverless_framework"),
       }
     }, "app.zip");
     expectLogFunctions(sls);
@@ -359,7 +359,7 @@ describe("Function App Service", () => {
         Authorization: `Bearer ${(await variables["azureCredentials"].getToken()).accessToken}`,
         Accept: "*/*",
         ContentType: "application/octet-stream",
-        "User-Agent": "serverless_framework",
+        "User-Agent": expect.stringContaining("serverless_framework"),
       }
     }, slsService["artifact"]);
   });
diff --git a/src/services/invokeService.test.ts b/src/services/invokeService.test.ts
index 3af1031..cc6b6b8 100644
--- a/src/services/invokeService.test.ts
+++ b/src/services/invokeService.test.ts
@@ -18,6 +18,8 @@ describe("Invoke Service ", () => {
   const testData = "test-data";
   const testResult = "test result";
   const authKey = "authKey";
+  const errorCode = 400;
+  const errorData = "This was a bad request";
   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`;
@@ -29,6 +31,7 @@ describe("Invoke Service ", () => {
 
   let masterKey: string;
   let sls = MockFactory.createTestServerless();
+  const axiosMock = new MockAdapter(axios);
 
   let options = {
     function: functionName,
@@ -36,8 +39,7 @@ describe("Invoke Service ", () => {
     method: "GET"
   } as any;
 
-  beforeAll(() => {
-    const axiosMock = new MockAdapter(axios);
+  beforeEach(() => {
     // Master Key
     axiosMock.onGet(masterKeyUrl).reply(200, { value: masterKey });
     // Auth Key
@@ -110,4 +112,11 @@ describe("Invoke Service ", () => {
     await service.invoke("GET", fakeName);
     expect(sls.cli.log).lastCalledWith(`Function ${fakeName} does not exist`)
   });
+
+  it("throws an error with status code and data", async () => {
+    // Mock url for POST
+    axiosMock.onPost(urlPOST).reply(errorCode, errorData);
+    const service = new InvokeService(sls, options);
+    await expect(service.invoke("POST", options.function, options.data)).rejects.toThrow(`HTTP Error: ${errorCode} - ${errorData}`);
+  });
 });