diff --git a/src/_test/assistant.test.ts b/src/_test/assistant.test.ts index e45df2db..777e8561 100644 --- a/src/_test/assistant.test.ts +++ b/src/_test/assistant.test.ts @@ -218,3 +218,39 @@ test('app is callable as an Express request', async t => { }, }) }) + +test('app.handler can process requests and response with response headers', async t => { + const expectedHeaders = { + headers3: 'headers4', + } + const app = attach({ + handler: async (body: JsonObject, headers: Headers): Promise => { + return { + body: { + }, + status: 123, + headers: expectedHeaders, + } + }, + }) + t.is(typeof app.handler, 'function') + const res = await app.handler({}, {}) + t.is(res.status, 123) + t.is(res.headers!.headers3, expectedHeaders.headers3) +}) + +test('app.handler adds content-type headers', async t => { + const app = attach({ + handler: async (body: JsonObject, headers: Headers): Promise => { + return { + body: { + }, + status: 123, + } + }, + }) + t.is(typeof app.handler, 'function') + const res = await app.handler({}, {}) + t.is(res.status, 123) + t.is(res.headers!['content-type'], 'application/json; charset=utf-8') +}) diff --git a/src/assistant.ts b/src/assistant.ts index 78488ce1..0435eadf 100644 --- a/src/assistant.ts +++ b/src/assistant.ts @@ -15,7 +15,7 @@ */ import { OmniHandler, StandardHandler, BuiltinFrameworks, builtin } from './framework' -import { debug, values, stringify, info } from './common' +import * as common from './common' /** @public */ export type AppHandler = OmniHandler & BaseApp @@ -70,7 +70,7 @@ export const attach = ( let app: (BaseApp & TService) | (AppHandler & TService) = Object.assign(create(options), service) // tslint:disable-next-line:no-any automatically detect any inputs const omni: OmniHandler = (...args: any[]) => { - for (const framework of values(app.frameworks)) { + for (const framework of common.values(app.frameworks)) { if (framework.check(...args)) { return framework.handle(app.handler)(...args) } @@ -78,14 +78,17 @@ export const attach = ( return app.handler(args[0], args[1]) } app = Object.assign(omni, app) - const handler = app.handler.bind(app) + const handler: typeof app.handler = app.handler.bind(app) const standard: StandardHandler = async (body, headers) => { - const log = app.debug ? info : debug - log('Request', stringify(body)) - log('Headers', stringify(headers)) + const log = app.debug ? common.info : common.debug + log('Request', common.stringify(body)) + log('Headers', common.stringify(headers)) const response = await handler(body, headers) - log('Response', stringify(response.body)) - log('Status', response.status) + if (!response.headers) { + response.headers = {} + } + response.headers['content-type'] = 'application/json; charset=utf-8' + log('Response', common.stringify(response)) return response } app.handler = standard diff --git a/src/framework/_test/express.test.ts b/src/framework/_test/express.test.ts index 08c0c6b5..b620541c 100644 --- a/src/framework/_test/express.test.ts +++ b/src/framework/_test/express.test.ts @@ -17,7 +17,7 @@ import ava, { RegisterContextual } from 'ava' import { Express } from '../express' import { JsonObject } from '../../common' -import { StandardResponse } from '..' +import { StandardResponse, Headers } from '../framework' interface AvaContext { express: Express @@ -134,3 +134,41 @@ test('handles error', async t => { t.deepEqual(receivedBody, expectedBody) t.is(receivedStatus, expectedStatus) }) + +test('handles valid headers fine', async t => { + const expectedHeaders = { + header1: 'header2', + } + const expectedStatus = 123 + const receivedHeaders: Headers = {} + let receivedStatus = -1 + let promise: Promise | null = null + t.context.express.handle((body, headers) => { + promise = Promise.resolve({ + body: {}, + status: expectedStatus, + headers: expectedHeaders, + }) + return promise + })({ + body: {}, + headers: {}, + get() {}, + // tslint:disable-next-line:no-any mocking request + } as any, { + status(status: number) { + receivedStatus = status + return this + }, + setHeader(key: string, value: string) { + receivedHeaders[key] = value + }, + send() { + return this + }, + // tslint:disable-next-line:no-any mocking response + } as any) + await promise + t.is(receivedStatus, expectedStatus) + t.deepEqual(receivedHeaders, expectedHeaders) +}) diff --git a/src/framework/_test/lambda.test.ts b/src/framework/_test/lambda.test.ts index 1131a4a6..ea43aac5 100644 --- a/src/framework/_test/lambda.test.ts +++ b/src/framework/_test/lambda.test.ts @@ -155,3 +155,35 @@ test('handles error', async t => { await new Promise(resolve => setTimeout(resolve)) t.is(receivedError, expectedError) }) + +test('handles valid headers fine', async t => { + const expectedHeaders = { + header1: 'header2', + } + const expectedStatus = 123 + let receivedHeaders: Headers | null = null + let receivedStatus = -1 + let promise: Promise | null = null + t.context.lambda.handle((body, headers) => { + promise = Promise.resolve({ + body: {}, + status: expectedStatus, + headers: expectedHeaders, + }) + return promise + })({ + body: JSON.stringify({}), + headers: {}, + }, { + succeed() {}, + // tslint:disable-next-line:no-any mocking context + } as any, (e: Error, body: JsonObject) => { + receivedStatus = body.statusCode + receivedHeaders = body.headers + }) + await promise + await new Promise(resolve => setTimeout(resolve)) + // tslint:disable-next-line:no-any change to string even if null + t.is(receivedStatus, expectedStatus) + t.is(receivedHeaders, expectedHeaders) +}) diff --git a/src/framework/express.ts b/src/framework/express.ts index a12f15a6..9971c8ac 100644 --- a/src/framework/express.ts +++ b/src/framework/express.ts @@ -20,7 +20,7 @@ import { Framework, StandardHandler } from './framework' import { Request, Response } from 'express' -import { error } from '../common' +import * as common from '../common' export interface ExpressHandler { /** @public */ @@ -32,9 +32,16 @@ export class Express implements Framework { handle(standard: StandardHandler) { return (request: Request, response: Response) => { standard(request.body, request.headers) - .then(({ status, body }) => response.status(status).send(body)) + .then(({ status, body, headers }) => { + if (headers) { + for (const key in headers) { + response.setHeader(key, headers[key]!) + } + } + response.status(status).send(body) + }) .catch((e: Error) => { - error(e.stack || e) + common.error(e.stack || e) response.status(500).send({ error: e.message }) }) } diff --git a/src/framework/framework.ts b/src/framework/framework.ts index d031c1dc..b89f5209 100644 --- a/src/framework/framework.ts +++ b/src/framework/framework.ts @@ -67,6 +67,9 @@ export interface StandardResponse { /** @public */ body: JsonObject + + /** @public */ + headers?: Headers } /** @public */ diff --git a/src/framework/lambda.ts b/src/framework/lambda.ts index f25bfd24..fbb6890f 100644 --- a/src/framework/lambda.ts +++ b/src/framework/lambda.ts @@ -19,7 +19,8 @@ */ import { Framework, StandardHandler, Headers } from './framework' -import { JsonObject, error } from '../common' +import { JsonObject } from '../common' +import * as common from '../common' import { Context, Callback } from 'aws-lambda' export interface LambdaHandler { @@ -37,7 +38,7 @@ export class Lambda implements Framework { return o }, {} as Headers) const result = await standard(JSON.parse(event.body), headers).catch((e: Error) => { - error(e.stack || e) + common.error(e.stack || e) callback(e) }) if (!result) { @@ -47,6 +48,7 @@ export class Lambda implements Framework { callback(null, { statusCode: status, body: JSON.stringify(body), + headers: result.headers, }) } } diff --git a/src/service/actionssdk/actionssdk.ts b/src/service/actionssdk/actionssdk.ts index 38246a44..61d6ad59 100644 --- a/src/service/actionssdk/actionssdk.ts +++ b/src/service/actionssdk/actionssdk.ts @@ -337,6 +337,7 @@ export const actionssdk: ActionsSdk = < } return { status: 200, + headers: {}, body: conv.serialize(), } }, diff --git a/src/service/dialogflow/dialogflow.ts b/src/service/dialogflow/dialogflow.ts index aa2b9719..f20de3a9 100644 --- a/src/service/dialogflow/dialogflow.ts +++ b/src/service/dialogflow/dialogflow.ts @@ -412,6 +412,7 @@ export const dialogflow: Dialogflow = < } return { status: 200, + headers: {}, body: conv.serialize(), } },