Repository: serverless/serverless-azure-functions
Pull request #458 (2020-05-19 03:31:21)

fix: Print error message for invoke command
<!--
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:

Print response message from failed axios request in invoke plugin

<!--
Briefly describe the feature if no issue exists for this PR
-->

## How did you implement it:

<!--
If this is a nontrivial change please briefly describe your implementation so its easy for us to understand and review your code.
-->

Catch the error message from failed axios request and log message

## How can we verify it:

Invoke API incorrectly (try a service that hasn't been deployed, should get 404). Error message will be printed.

<!--
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



Patch:
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/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}`);
+  });
 });