diff --git a/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js b/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js index 76acb5a59f..d31808d6b4 100644 --- a/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js +++ b/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js @@ -339,6 +339,9 @@ describe('ActionTests', function () { { text: 'text', age: 11 }, ]) .reply(200, 'array'); + nock('http://foo.com') + .post('/', 'Joe is 52') + .replyWithError({ message: 'Error making the request', code: 'FetchError' }); nock('http://foo.com').get('/image').reply(200, 'TestImage'); nock('http://foo.com').get('/json').reply(200, { test: 'test' }); nock('http://foo.com').get('/activity').reply(200, MessageFactory.text('testtest')); diff --git a/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_HttpRequest.test.dialog b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_HttpRequest.test.dialog index ef4ac808e1..d0534e66dd 100644 --- a/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_HttpRequest.test.dialog +++ b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_HttpRequest.test.dialog @@ -70,6 +70,17 @@ "$kind": "Microsoft.SendActivity", "activity": "${turn.lastresult.content}" }, + { + "$kind": "Microsoft.HttpRequest", + "method": "POST", + "url": "http://foo.com/", + "body": "${dialog.name} is ${dialog.age}", + "contentType": "plain/text" + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "${turn.lastresult.content}" + }, { "$kind": "Microsoft.HttpRequest", "method": "GET", @@ -130,6 +141,10 @@ "$kind": "Microsoft.Test.AssertReply", "text": "array" }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "request to http://foo.com/ failed, reason: Error making the request" + }, { "$kind": "Microsoft.Test.AssertReply", "text": "test" diff --git a/libraries/botbuilder-dialogs-adaptive/package.json b/libraries/botbuilder-dialogs-adaptive/package.json index 8e0a83bb66..83c6885666 100644 --- a/libraries/botbuilder-dialogs-adaptive/package.json +++ b/libraries/botbuilder-dialogs-adaptive/package.json @@ -28,15 +28,16 @@ } }, "dependencies": { - "@microsoft/recognizers-text-sequence": "~1.1.4", "@microsoft/recognizers-text": "~1.1.4", - "@microsoft/recognizers-text-number-with-unit": "~1.1.4", - "@microsoft/recognizers-text-number": "~1.1.4", "@microsoft/recognizers-text-choice": "~1.1.4", "@microsoft/recognizers-text-date-time": "~1.1.4", + "@microsoft/recognizers-text-number": "~1.1.4", + "@microsoft/recognizers-text-number-with-unit": "~1.1.4", + "@microsoft/recognizers-text-sequence": "~1.1.4", "@microsoft/recognizers-text-suite": "1.1.4", "adaptive-expressions": "4.1.6", "botbuilder": "4.1.6", + "botbuilder-core": "4.1.6", "botbuilder-dialogs": "4.1.6", "botbuilder-dialogs-adaptive-runtime-core": "4.1.6", "botbuilder-dialogs-declarative": "4.1.6", diff --git a/libraries/botbuilder-dialogs-adaptive/src/actions/httpRequest.ts b/libraries/botbuilder-dialogs-adaptive/src/actions/httpRequest.ts index e0be178954..d570e8b5f6 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/actions/httpRequest.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/actions/httpRequest.ts @@ -5,7 +5,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import fetch from 'node-fetch'; +import { StatusCodes } from 'botbuilder-core'; +import fetch, { FetchError } from 'node-fetch'; import { Activity } from 'botbuilder'; import { BoolProperty, EnumProperty, StringProperty, UnknownProperty } from '../properties'; import { Response, Headers } from 'node-fetch'; @@ -286,78 +287,103 @@ export class HttpRequest extends Dialog implements Htt instanceHeaders['Content-Type'] = contentType; let instanceBody: string; - const body = evaluateExpression(dc.state, this.body); - if (body) { - if (typeof body === 'string') { - instanceBody = body; - } else { - instanceBody = JSON.stringify(Object.assign({}, body)); + let traceInfo; + try { + const body = evaluateExpression(dc.state, this.body); + if (body) { + if (typeof body === 'string') { + instanceBody = body; + } else { + instanceBody = JSON.stringify(Object.assign({}, body)); + } } - } - const traceInfo = { - request: { - method: instanceMethod, - url: instanceUrl, - headers: instanceHeaders, - content: instanceBody, - }, - response: undefined, - }; - - let response: Response; - - switch (this.method) { - case HttpMethod.DELETE: - case HttpMethod.GET: - response = await fetch(instanceUrl, { - method: instanceMethod, - headers: instanceHeaders, - }); - break; - case HttpMethod.PUT: - case HttpMethod.PATCH: - case HttpMethod.POST: - response = await fetch(instanceUrl, { + traceInfo = { + request: { method: instanceMethod, + url: instanceUrl, headers: instanceHeaders, - body: instanceBody, - }); - break; - } + content: instanceBody, + }, + response: undefined, + }; - const result = new Result(response.headers); - result.statusCode = response.status; - result.reasonPhrase = response.statusText; - - switch (this.responseType.getValue(dc.state)) { - case ResponsesTypes.Activity: - result.content = await response.json(); - dc.context.sendActivity(result.content as Activity); - break; - case ResponsesTypes.Activities: - result.content = await response.json(); - dc.context.sendActivities(result.content as Activity[]); - break; - case ResponsesTypes.Json: { - const content = await response.text(); - try { - result.content = JSON.parse(content); - } catch { - result.content = content; + let response: Response; + + switch (this.method) { + case HttpMethod.DELETE: + case HttpMethod.GET: + response = await fetch(instanceUrl, { + method: instanceMethod, + headers: instanceHeaders, + }); + break; + case HttpMethod.PUT: + case HttpMethod.PATCH: + case HttpMethod.POST: + response = await fetch(instanceUrl, { + method: instanceMethod, + headers: instanceHeaders, + body: instanceBody, + }); + break; + } + + const result = new Result(response.headers); + result.statusCode = response.status; + result.reasonPhrase = response.statusText; + + switch (this.responseType.getValue(dc.state)) { + case ResponsesTypes.Activity: + result.content = await response.json(); + dc.context.sendActivity(result.content as Activity); + break; + case ResponsesTypes.Activities: + result.content = await response.json(); + dc.context.sendActivities(result.content as Activity[]); + break; + case ResponsesTypes.Json: { + const content = await response.text(); + try { + result.content = JSON.parse(content); + } catch { + result.content = content; + } + break; + } + case ResponsesTypes.Binary: { + const buffer = await response.arrayBuffer(); + result.content = new Uint8Array(buffer); + break; } - break; + case ResponsesTypes.None: + default: + break; } - case ResponsesTypes.Binary: { - const buffer = await response.arrayBuffer(); - result.content = new Uint8Array(buffer); - break; + + return await this.endDialogWithResult(dc, result, traceInfo); + } catch (err) { + if (err instanceof FetchError) { + const result = new Result(); + result.content = err.message; + result.statusCode = StatusCodes.NOT_FOUND; + return await this.endDialogWithResult(dc, result, traceInfo); + } else { + throw err; } - case ResponsesTypes.None: - default: - break; } + } + /** + * Writes Trace Activity for the http request and response values and returns the actionResult as the result of this operation. + * + * @param dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation. + * @param result Value returned from the dialog that was called. The type + * of the value returned is dependent on the child dialog. + * @param traceInfo Trace information to be written. + * @returns A `Promise` representing the asynchronous operation. + */ + private async endDialogWithResult(dc: DialogContext, result: Result, traceInfo: any): Promise { traceInfo.response = result; // Write trace activity for http request and response values.