Repository: serverless/serverless-azure-functions (ts)

Issue:
Add suport for Node.js 16 
On March 30, 2022, Microsoft announced that Node.js 16 was available for Azure Functions [https://azure.microsoft.com/en-us/updates/generally-available-nodejs-16-in-azure-functions-2/](https://azure.microsoft.com/en-us/updates/generally-available-nodejs-16-in-azure-functions-2/), but this plugin does not support this version. I found an open pull request about this issue, but with a small mistake in the code [https://github.com/serverless/serverless-azure-functions/pull/610](https://github.com/serverless/serverless-azure-functions/pull/610). Could you please add support for Node.js 16?

Pull request description:
feat:  add suport for node16 runtime
<!--
1. Please check out and follow our Contributing Guidelines: https://github.com/serverless/serverless-azure-functions/blob/master/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:

Closes #631 

It will become available to use the nodejs16 runtime.

## How did you implement it:

<!--
If this is a nontrivial change please briefly describe your implementation so it's easy for us to understand and review your code.
-->
I'm updated the available runtimes at [src/config/runtime.ts](https://github.com/serverless/serverless-azure-functions/pull/640/commits/a8a7d68ff874ed53716c6901c4e734a471a4aa18).

## How can we verify it:

<!--
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
-->
Can you generate an azure function with serverless-framework and deploy it with nodejs16 runtime.
Ex: `runtime: nodejs16`

## 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


Patch:
diff --git a/src/config/runtime.ts b/src/config/runtime.ts
index 553427a..7fc37fb 100644
--- a/src/config/runtime.ts
+++ b/src/config/runtime.ts
@@ -3,6 +3,7 @@ export enum Runtime {
   NODE10 = "nodejs10",
   NODE12 = "nodejs12",
   NODE14 = "nodejs14",
+  NODE16 = "nodejs16",
   PYTHON36 = "python3.6",
   PYTHON37 = "python3.7",
   PYTHON38 = "python3.8",
@@ -14,6 +15,7 @@ export const supportedRuntimes = [
   Runtime.NODE10,
   Runtime.NODE12,
   Runtime.NODE14,
+  Runtime.NODE16,
   Runtime.PYTHON36,
   Runtime.PYTHON37,
   Runtime.PYTHON38,
@@ -92,6 +94,7 @@ export const dockerImages = {
   nodejs10: "NODE|10",
   nodejs12: "NODE|12",
   nodejs14: "NODE|14",
+  nodejs16: "NODE|16",
   "python3.6": "PYTHON|3.6",
   "python3.7": "PYTHON|3.7",
   "python3.8": "PYTHON|3.8",


Test patch:
diff --git a/src/armTemplates/resources/functionApp.test.ts b/src/armTemplates/resources/functionApp.test.ts
index 3927442..9945439 100644
--- a/src/armTemplates/resources/functionApp.test.ts
+++ b/src/armTemplates/resources/functionApp.test.ts
@@ -214,6 +214,23 @@ describe("Function App Resource", () => {
         expect(functionAppNodeVersion.value).toEqual("~12");
       });
 
+      it("gets correct parameters - node 16", () => {
+        const config = getConfig(FunctionAppOS.LINUX, Runtime.NODE16);
+
+        const resource = new FunctionAppResource();
+
+        const params = resource.getParameters(config);
+        const {
+          linuxFxVersion,
+          functionAppNodeVersion,
+        } = params;
+
+        assertLinuxParams(params);
+
+        expect(linuxFxVersion.value).toEqual("NODE|16");
+        expect(functionAppNodeVersion.value).toEqual("~16");
+      });
+
       it("gets correct parameters - python 3.6", () => {
         const config = getConfig(FunctionAppOS.LINUX, Runtime.PYTHON36);
     
diff --git a/src/config/runtime.test.ts b/src/config/runtime.test.ts
index ef108f0..a1e0e53 100644
--- a/src/config/runtime.test.ts
+++ b/src/config/runtime.test.ts
@@ -6,6 +6,7 @@ describe("Runtime", () => {
     expect(isNodeRuntime(Runtime.NODE10)).toBe(true);
     expect(isNodeRuntime(Runtime.NODE12)).toBe(true);
     expect(isNodeRuntime(Runtime.NODE14)).toBe(true);
+    expect(isNodeRuntime(Runtime.NODE16)).toBe(true);
     expect(isNodeRuntime(Runtime.PYTHON36)).toBe(false);
     expect(isNodeRuntime(Runtime.PYTHON37)).toBe(false);
     expect(isNodeRuntime(Runtime.PYTHON38)).toBe(false);
@@ -15,6 +16,7 @@ describe("Runtime", () => {
     expect(isPythonRuntime(Runtime.NODE10)).toBe(false);
     expect(isPythonRuntime(Runtime.NODE12)).toBe(false);
     expect(isPythonRuntime(Runtime.NODE14)).toBe(false);
+    expect(isPythonRuntime(Runtime.NODE16)).toBe(false);
     expect(isPythonRuntime(Runtime.PYTHON36)).toBe(true);
     expect(isPythonRuntime(Runtime.PYTHON37)).toBe(true);
     expect(isPythonRuntime(Runtime.PYTHON38)).toBe(true);
@@ -24,24 +26,39 @@ describe("Runtime", () => {
     expect(getRuntimeVersion(Runtime.NODE10)).toBe("10");
     expect(getRuntimeVersion(Runtime.NODE12)).toBe("12");
     expect(getRuntimeVersion(Runtime.NODE14)).toBe("14");
+    expect(getRuntimeVersion(Runtime.NODE16)).toBe("16");
     expect(getRuntimeVersion(Runtime.PYTHON36)).toBe("3.6");
     expect(getRuntimeVersion(Runtime.PYTHON37)).toBe("3.7");
     expect(getRuntimeVersion(Runtime.PYTHON38)).toBe("3.8");
   });
 
+  it("throw exception on get runtime version", () => {
+    Runtime["invalid"] = "invalid"
+    expect(() => { getRuntimeVersion(Runtime["invalid"]) }).toThrowError("Runtime invalid not included in supportedRuntimes")
+    delete Runtime["invalid"]
+  })
+
   it("gets runtime language", () => {
     expect(getRuntimeLanguage(Runtime.NODE10)).toBe("nodejs");
     expect(getRuntimeLanguage(Runtime.NODE12)).toBe("nodejs");
     expect(getRuntimeLanguage(Runtime.NODE14)).toBe("nodejs");
+    expect(getRuntimeLanguage(Runtime.NODE16)).toBe("nodejs");
     expect(getRuntimeLanguage(Runtime.PYTHON36)).toBe("python");
     expect(getRuntimeLanguage(Runtime.PYTHON37)).toBe("python");
     expect(getRuntimeLanguage(Runtime.PYTHON38)).toBe("python");
   });
 
+  it("throw exception on get runtime language", () => {
+    Runtime["invalid"] = "invalid"
+    expect(() => { getRuntimeLanguage(Runtime["invalid"]) }).toThrowError("Runtime invalid not included in supportedRuntimes")
+    delete Runtime["invalid"]
+  })
+
   it("gets function worker runtime", () => {
     expect(getFunctionWorkerRuntime(Runtime.NODE10)).toBe("node");
     expect(getFunctionWorkerRuntime(Runtime.NODE12)).toBe("node");
     expect(getFunctionWorkerRuntime(Runtime.NODE14)).toBe("node");
+    expect(getFunctionWorkerRuntime(Runtime.NODE16)).toBe("node");
     expect(getFunctionWorkerRuntime(Runtime.PYTHON36)).toBe("python");
     expect(getFunctionWorkerRuntime(Runtime.PYTHON37)).toBe("python");
     expect(getFunctionWorkerRuntime(Runtime.PYTHON38)).toBe("python");
diff --git a/src/services/configService.test.ts b/src/services/configService.test.ts
index d696907..4a61b38 100644
--- a/src/services/configService.test.ts
+++ b/src/services/configService.test.ts
@@ -225,7 +225,7 @@ describe("Config Service", () => {
         sls.service.provider.runtime = "python2.7" as any;
         expect(() => new ConfigService(sls, {} as any))
           .toThrowError("Runtime python2.7 is not supported. " +
-            "Runtimes supported: nodejs10,nodejs12,nodejs14,python3.6,python3.7,python3.8");
+            "Runtimes supported: nodejs10,nodejs12,nodejs14,nodejs16,python3.6,python3.7,python3.8");
       });
 
       it("throws error when incomplete nodejs version in defined", () => {
@@ -233,7 +233,7 @@ describe("Config Service", () => {
         sls.service.provider.runtime = "nodejs" as any;
         expect(() => new ConfigService(sls, {} as any))
           .toThrowError("Runtime nodejs is not supported. " +
-            "Runtimes supported: nodejs10,nodejs12,nodejs14,python3.6,python3.7,python3.8");
+            "Runtimes supported: nodejs10,nodejs12,nodejs14,nodejs16,python3.6,python3.7,python3.8");
       });
 
       it("throws error when unsupported nodejs version in defined", () => {
@@ -241,7 +241,7 @@ describe("Config Service", () => {
         sls.service.provider.runtime = "nodejs5.x" as any;
         expect(() => new ConfigService(sls, {} as any))
           .toThrowError("Runtime nodejs5.x is not supported. " +
-            "Runtimes supported: nodejs10,nodejs12,nodejs14,python3.6,python3.7,python3.8");
+            "Runtimes supported: nodejs10,nodejs12,nodejs14,nodejs16,python3.6,python3.7,python3.8");
       });
 
       it("Does not throw an error when valid nodejs version is defined", () => {
@@ -258,7 +258,7 @@ describe("Config Service", () => {
         sls.service.provider.runtime = undefined;
         expect(() => new ConfigService(sls, {} as any))
           .toThrowError("Runtime undefined. " +
-            "Runtimes supported: nodejs10,nodejs12,nodejs14,python3.6,python3.7,python3.8");
+            "Runtimes supported: nodejs10,nodejs12,nodejs14,nodejs16,python3.6,python3.7,python3.8");
       });
 
       it("does not throw an error with python3.6", () => {