From 4a66df1483e4556e5733eb2830f22db79a864f73 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 22 Jan 2020 10:00:16 +0100 Subject: [PATCH 1/3] package.json only deploy necessary files to registry, fix import --- tsconfig.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 5c74d33..9002822 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,10 @@ "exclude": [ "./test/**", "./dist/**" - ] + ], + "paths": { + "*": [ + "dist/*" + ] + } } From fb6b833c03f2fe6d4d3a91e2602c7a4566eb5f39 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 22 Jan 2020 10:04:27 +0100 Subject: [PATCH 2/3] only files from dist should be published --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index de648d9..8e40920 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,9 @@ "routing", "s3" ], + "files": [ + "dist/**/*" + ], "author": "Christian Gohlke (https://www.welt.de)", "license": "Apache-2.0", "bugs": { From 69a9b696d2fa96697fd832dd7e6989d8c2d01903 Mon Sep 17 00:00:00 2001 From: Jan Pavek Date: Wed, 22 Jan 2020 12:50:46 +0100 Subject: [PATCH 3/3] styles clean up --- index.ts | 26 +-- lib/proxyIntegration.ts | 141 +++++++------- lib/s3.ts | 100 +++++----- lib/sns.ts | 38 ++-- lib/sqs.ts | 64 +++---- test/index.test.ts | 6 +- test/proxyIntegration-example.json | 72 +++---- test/proxyIntegration.test.ts | 171 +++++++++++------ test/s3.test.ts | 296 ++++++++++++++--------------- test/sns.test.ts | 102 +++++----- test/sqs.test.ts | 153 ++++++++------- tslint.json | 6 + 12 files changed, 626 insertions(+), 549 deletions(-) create mode 100644 tslint.json diff --git a/index.ts b/index.ts index 42d9fa1..613c8ba 100644 --- a/index.ts +++ b/index.ts @@ -1,9 +1,9 @@ -import { ProxyIntegrationConfig, ProxyIntegrationEvent } from "./lib/proxyIntegration"; -import { SnsConfig, SnsEvent } from "./lib/sns"; -import { SqsConfig, SqsEvent } from "./lib/sqs"; -import { S3Config, S3Event } from "./lib/s3"; -import { Context } from "aws-lambda"; -import { EventProcessor } from "./lib/EventProcessor"; +import { ProxyIntegrationConfig, ProxyIntegrationEvent } from './lib/proxyIntegration' +import { SnsConfig, SnsEvent } from './lib/sns' +import { SqsConfig, SqsEvent } from './lib/sqs' +import { S3Config, S3Event } from './lib/s3' +import { Context } from 'aws-lambda' +import { EventProcessor } from './lib/EventProcessor' export interface RouteConfig { proxyIntegration?: ProxyIntegrationConfig @@ -16,12 +16,12 @@ export interface RouteConfig { export type RouterEvent = ProxyIntegrationEvent | SnsEvent | SqsEvent | S3Event export const handler = (routeConfig: RouteConfig) => { - const eventProcessorMapping = extractEventProcessorMapping(routeConfig); + const eventProcessorMapping = extractEventProcessorMapping(routeConfig) - return async (event: RouterEvent, context: TContext) => { + return async (event: RouterEvent, context: TContext) => { if (routeConfig.debug) { - console.log("Lambda invoked with request:", event); - console.log("Lambda invoked with context:", context); + console.log('Lambda invoked with request:', event) + console.log('Lambda invoked with context:', context) } for (const [eventProcessorName, eventProcessor] of eventProcessorMapping.entries()) { @@ -34,13 +34,13 @@ export const handler = (routeConfig: RouteConfig) => { // - throws Error: the 'error.toString()' is taken as the error message of processing the event // - returns object: this is taken as the result of processing the event // - returns promise: when the promise is resolved, this is taken as the result of processing the event - const result = eventProcessor.process((routeConfig as any)[eventProcessorName], event, context); + const result = eventProcessor.process((routeConfig as any)[eventProcessorName], event, context) if (result) { // be resilient against a processor returning a value instead of a promise: return await result } else { if (routeConfig.debug) { - console.log("Event processor couldn't handle request.") + console.log('Event processor couldn\'t handle request.') } } } catch (error) { @@ -64,5 +64,5 @@ const extractEventProcessorMapping = (routeConfig: RouteConfig) => { throw new Error(`The event processor '${key}', that is mentioned in the routerConfig, cannot be instantiated (${error.toString()})`) } } - return processorMap; + return processorMap } diff --git a/lib/proxyIntegration.ts b/lib/proxyIntegration.ts index 18aa0aa..6bf5257 100644 --- a/lib/proxyIntegration.ts +++ b/lib/proxyIntegration.ts @@ -1,5 +1,5 @@ -import { APIGatewayProxyEvent, APIGatewayEventRequestContext, APIGatewayProxyResult } from "aws-lambda"; -import { ProcessMethod } from "./EventProcessor"; +import { APIGatewayEventRequestContext, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda' +import { ProcessMethod } from './EventProcessor' export type ProxyIntegrationEvent = APIGatewayProxyEvent type ProxyIntegrationParams = { @@ -8,12 +8,12 @@ type ProxyIntegrationParams = { export type ProxyIntegrationEventWithParams = APIGatewayProxyEvent & ProxyIntegrationParams export interface ProxyIntegrationRoute { - path: string; - method: string; + path: string + method: string action: ( request: ProxyIntegrationEventWithParams, context: APIGatewayEventRequestContext - ) => APIGatewayProxyResult | Promise; + ) => APIGatewayProxyResult | Promise } export type ProxyIntegrationErrorMapping = { @@ -29,12 +29,12 @@ export type ProxyIntegrationError = { } export interface ProxyIntegrationConfig { - cors?: boolean; - routes: ProxyIntegrationRoute[]; - debug?: boolean; - errorMapping?: ProxyIntegrationErrorMapping; - defaultHeaders?: APIGatewayProxyResult['headers']; - proxyPath?: string; + cors?: boolean + routes: ProxyIntegrationRoute[] + debug?: boolean + errorMapping?: ProxyIntegrationErrorMapping + defaultHeaders?: APIGatewayProxyResult['headers'] + proxyPath?: string } const NO_MATCHING_ACTION = (request: APIGatewayProxyEvent) => { @@ -42,27 +42,27 @@ const NO_MATCHING_ACTION = (request: APIGatewayProxyEvent) => { reason: 'NO_MATCHING_ACTION', message: `Could not find matching action for ${request.path} and method ${request.httpMethod}` } -}; +} const addCorsHeaders = (toAdd: APIGatewayProxyResult['headers'] = {}) => { - toAdd["Access-Control-Allow-Origin"] = "*"; - toAdd["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,HEAD,PATCH"; - toAdd["Access-Control-Allow-Headers"] = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token"; - return toAdd; + toAdd['Access-Control-Allow-Origin'] = '*' + toAdd['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,HEAD,PATCH' + toAdd['Access-Control-Allow-Headers'] = 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' + return toAdd } const processActionAndReturn = async (actionConfig: Pick, event: ProxyIntegrationEventWithParams, - context: APIGatewayEventRequestContext, headers: APIGatewayProxyResult['headers']) => { + context: APIGatewayEventRequestContext, headers: APIGatewayProxyResult['headers']) => { const res = await actionConfig.action(event, context) if (!res || !res.body) { - const consolidateBody = res && JSON.stringify(res) || '{}'; + const consolidateBody = res && JSON.stringify(res) || '{}' return { statusCode: 200, headers, body: consolidateBody - }; + } } return { @@ -79,91 +79,91 @@ export const process: ProcessMethod { if (proxyIntegrationConfig.debug) { - console.log("Lambda proxyIntegrationConfig: ", proxyIntegrationConfig); - console.log("Lambda event: ", event); - console.log("Lambda context: ", context); + console.log('Lambda proxyIntegrationConfig: ', proxyIntegrationConfig) + console.log('Lambda event: ', event) + console.log('Lambda context: ', context) } //validate config if (!Array.isArray(proxyIntegrationConfig.routes) || proxyIntegrationConfig.routes.length < 1) { - throw new Error('proxyIntegration.routes must not be empty'); + throw new Error('proxyIntegration.routes must not be empty') } // detect if it's an http-call at all: if (!event.httpMethod || !event.path) { - return null; + return null } - const headers: APIGatewayProxyResult['headers'] = {}; + const headers: APIGatewayProxyResult['headers'] = {} if (proxyIntegrationConfig.cors) { - addCorsHeaders(headers); + addCorsHeaders(headers) if (event.httpMethod === 'OPTIONS') { return Promise.resolve({ statusCode: 200, headers, body: '' - }); + }) } } Object.assign(headers, { 'Content-Type': 'application/json' }, proxyIntegrationConfig.defaultHeaders) // assure necessary values have sane defaults: - const errorMapping = proxyIntegrationConfig.errorMapping || {}; - errorMapping['NO_MATCHING_ACTION'] = 404; + const errorMapping = proxyIntegrationConfig.errorMapping || {} + errorMapping['NO_MATCHING_ACTION'] = 404 if (proxyIntegrationConfig.proxyPath) { - console.log("proxy path is set: " + proxyIntegrationConfig.proxyPath) - event.path = (event.pathParameters || {})[proxyIntegrationConfig.proxyPath]; - console.log("proxy path with event path: " + event.path) + console.log('proxy path is set: ' + proxyIntegrationConfig.proxyPath) + event.path = (event.pathParameters || {})[proxyIntegrationConfig.proxyPath] + console.log('proxy path with event path: ' + event.path) } else { - event.path = normalizeRequestPath(event); + event.path = normalizeRequestPath(event) } try { const actionConfig = findMatchingActionConfig(event.httpMethod, event.path, proxyIntegrationConfig) || { action: NO_MATCHING_ACTION, paths: undefined - }; + } - event.paths = actionConfig.paths; + event.paths = actionConfig.paths if (event.body) { try { - event.body = JSON.parse(event.body); + event.body = JSON.parse(event.body) } catch (parseError) { - console.log(`Could not parse body as json: ${event.body}`, parseError); + console.log(`Could not parse body as json: ${event.body}`, parseError) return { statusCode: 400, headers, - body: JSON.stringify({ message: "body is not a valid JSON", error: "ParseError" }) + body: JSON.stringify({ message: 'body is not a valid JSON', error: 'ParseError' }) } } } return processActionAndReturn(actionConfig, event, context, headers).catch(error => { console.log('Error while handling action function.', error) - return convertError(error, errorMapping, headers); + return convertError(error, errorMapping, headers) }) } catch (error) { - console.log('Error while evaluating matching action handler', error); - return convertError(error, errorMapping, headers); + console.log('Error while evaluating matching action handler', error) + return convertError(error, errorMapping, headers) } } const normalizeRequestPath = (event: APIGatewayProxyEvent): string => { if (isLocalExecution(event)) { - return event.path; + return event.path } // ugly hack: if host is from API-Gateway 'Custom Domain Name Mapping', then event.path has the value '/basepath/resource-path/'; // if host is from amazonaws.com, then event.path is just '/resource-path': - const apiId = event.requestContext ? event.requestContext.apiId : null; // the apiId that is the first part of the amazonaws.com-host + const apiId = event.requestContext ? event.requestContext.apiId : null // the apiId that is the first part of the amazonaws.com-host if ((apiId && event.headers && event.headers.Host && event.headers.Host.substring(0, apiId.length) !== apiId)) { // remove first path element: - const groups = /\/[^\/]+(.*)/.exec(event.path) || [null, null]; - return groups[1] || '/'; + const groups: any = /\/[^\/]+(.*)/.exec(event.path) || [null, null] + return groups[1] || '/' } - return event.path; + return event.path } const hasReason = (error: any): error is { reason: string } => typeof error.reason === 'string' @@ -175,73 +175,74 @@ const convertError = (error: ProxyIntegrationError | Error, errorMapping?: Proxy statusCode: errorMapping[error.reason], body: JSON.stringify({ message: error.message, error: error.reason }), headers - }; + } } else if (hasStatus(error)) { return { statusCode: error.status, body: JSON.stringify({ message: error.message, error: error.status }), headers: addCorsHeaders({}) - }; + } } try { return { statusCode: 500, - body: JSON.stringify({ error: "ServerError", message: `Generic error:${JSON.stringify(error)}` }), + body: JSON.stringify({ error: 'ServerError', message: `Generic error:${JSON.stringify(error)}` }), headers: addCorsHeaders({}) - }; - } catch (stringifyError) { } + } + } catch (stringifyError) { + } return { statusCode: 500, - body: JSON.stringify({ error: "ServerError", message: `Generic error` }) - }; + body: JSON.stringify({ error: 'ServerError', message: `Generic error` }) + } } const findMatchingActionConfig = (httpMethod: string, httpPath: string, routeConfig: ProxyIntegrationConfig): Pick & ProxyIntegrationParams | null => { - const paths: ProxyIntegrationParams['paths'] = {}; - const matchingMethodRoutes = routeConfig.routes.filter(route => route.method === httpMethod); + const paths: ProxyIntegrationParams['paths'] = {} + const matchingMethodRoutes = routeConfig.routes.filter(route => route.method === httpMethod) for (let route of matchingMethodRoutes) { if (routeConfig.debug) { - console.log(`Examining route ${route.path} to match ${httpPath}`); + console.log(`Examining route ${route.path} to match ${httpPath}`) } - const pathPartNames = extractPathNames(route.path); - const pathValues = extractPathValues(route.path, httpPath); + const pathPartNames = extractPathNames(route.path) + const pathValues = extractPathValues(route.path, httpPath) if (pathValues && pathPartNames) { for (let ii = 0; ii < pathValues.length; ii++) { - paths[pathPartNames[ii]] = decodeURIComponent(pathValues[ii]); + paths[pathPartNames[ii]] = decodeURIComponent(pathValues[ii]) } if (routeConfig.debug) { - console.log(`Found matching route ${route.path} with paths`, paths); + console.log(`Found matching route ${route.path} with paths`, paths) } return { action: route.action, paths: paths - }; + } } } if (routeConfig.debug) { - console.log(`No match for ${httpPath}`); + console.log(`No match for ${httpPath}`) } - return null; + return null } const extractPathValues = (pathExpression: string, httpPath: string) => { - const pathValueRegex = new RegExp('^' + pathExpression.replace(/:[\w]+/g, "([^/]+)") + '$'); - const pathValues = pathValueRegex.exec(httpPath); - return pathValues && pathValues.length > 0 ? pathValues.slice(1) : null; + const pathValueRegex = new RegExp('^' + pathExpression.replace(/:[\w]+/g, '([^/]+)') + '$') + const pathValues = pathValueRegex.exec(httpPath) + return pathValues && pathValues.length > 0 ? pathValues.slice(1) : null } const extractPathNames = (pathExpression: string) => { - const pathNameRegex = new RegExp('^' + pathExpression.replace(/:[\w.]+/g, ":([\\w]+)") + '$'); - const pathNames = pathNameRegex.exec(pathExpression); - return pathNames && pathNames.length > 0 ? pathNames.slice(1) : null; + const pathNameRegex = new RegExp('^' + pathExpression.replace(/:[\w.]+/g, ':([\\w]+)') + '$') + const pathNames = pathNameRegex.exec(pathExpression) + return pathNames && pathNames.length > 0 ? pathNames.slice(1) : null } const isLocalExecution = (event: ProxyIntegrationEvent) => { return event.headers && event.headers.Host - && (event.headers.Host.startsWith('localhost') || event.headers.Host.startsWith('127.0.0.1')); + && (event.headers.Host.startsWith('localhost') || event.headers.Host.startsWith('127.0.0.1')) } diff --git a/lib/s3.ts b/lib/s3.ts index f23f425..45c2848 100644 --- a/lib/s3.ts +++ b/lib/s3.ts @@ -1,99 +1,97 @@ -import { S3Event as awsS3Event, Context, S3EventRecord } from "aws-lambda"; -import { ProcessMethod } from "./EventProcessor"; +import { Context, S3Event as awsS3Event, S3EventRecord } from 'aws-lambda' +import { ProcessMethod } from './EventProcessor' + export type S3Event = awsS3Event + export interface S3Route { - bucketName?: string | RegExp; - eventName?: string | RegExp; - objectKeyPrefix?: string; - action: (s3Record: S3EventRecord, context: Context) => Promise | any; + bucketName?: string | RegExp + eventName?: string | RegExp + objectKeyPrefix?: string + action: (s3Record: S3EventRecord, context: Context) => Promise | any } export interface S3Config { - routes: S3Route[]; - debug?: boolean; + routes: S3Route[] + debug?: boolean } const validateArguments = (s3Config: S3Config, event: S3Event) => { if (!Array.isArray(event.Records) || event.Records.length < 1 || event.Records[0].eventSource !== 'aws:s3') { - console.log('Event does not look like S3'); - return false; + console.log('Event does not look like S3') + return false } //validate config if (!Array.isArray(s3Config.routes) || s3Config.routes.length < 1) { - throw new Error('s3Config.routes must not be empty'); + throw new Error('s3Config.routes must not be empty') } //validate config for (const route of s3Config.routes) { - if (!route.action) { - throw new Error('s3Config.routes.action must not be empty'); - } + if (!route.action) { + throw new Error('s3Config.routes.action must not be empty') + } } - return true; + return true } const matchConfigToEventValue = (config: string | RegExp | undefined, eventValue: string): boolean => { if (!config) { - return true; + return true } if (config instanceof RegExp) { - if (config.test(eventValue)) { - return true; - } + if (config.test(eventValue)) { + return true + } } else { - if (config === eventValue) { - return true; - } + if (config === eventValue) { + return true + } } - return false; + return false } -function matchObjectKeyWithPrefix(prefix: string | undefined, key: string): boolean { - if (!prefix || key.startsWith(prefix)) { - return true; - } - - return false; +function matchObjectKeyWithPrefix (prefix: string | undefined, key: string): boolean { + return !prefix || key.startsWith(prefix) } export const process: ProcessMethod = (s3Config, event, context) => { if (s3Config.debug) { - console.log('s3:Event', JSON.stringify(event)); - console.log('s3:context', context); + console.log('s3:Event', JSON.stringify(event)) + console.log('s3:context', context) } if (!validateArguments(s3Config, event)) { - return null; + return null } - const resultPromises = []; + const resultPromises = [] for (const record of event.Records) { - for (const routeConfig of s3Config.routes) { - const bucketNameMatched = matchConfigToEventValue(routeConfig.bucketName, record.s3.bucket.name); - const eventNameMatched = matchConfigToEventValue(routeConfig.eventName, record.eventName); - const objectKeyPrefixMatched = matchObjectKeyWithPrefix(routeConfig.objectKeyPrefix, record.s3.object.key); - - if (s3Config.debug) { - console.log(`match record '${record.eventName}'/'${record.s3.bucket.name}'/'${record.s3.object.key}': bucketMatch ' - ${bucketNameMatched}', eventMatch '${eventNameMatched}', key '${objectKeyPrefixMatched}' for route '${JSON.stringify(routeConfig)}'`); - } - - if (bucketNameMatched && eventNameMatched && objectKeyPrefixMatched) { - const resultPromise = routeConfig.action(record, context); - if (resultPromise) { - resultPromises.push(resultPromise); - } - break; - } + for (const routeConfig of s3Config.routes) { + const bucketNameMatched = matchConfigToEventValue(routeConfig.bucketName, record.s3.bucket.name) + const eventNameMatched = matchConfigToEventValue(routeConfig.eventName, record.eventName) + const objectKeyPrefixMatched = matchObjectKeyWithPrefix(routeConfig.objectKeyPrefix, record.s3.object.key) + + if (s3Config.debug) { + console.log(`match record '${record.eventName}'/'${record.s3.bucket.name}'/'${record.s3.object.key}': bucketMatch ' + ${bucketNameMatched}', eventMatch '${eventNameMatched}', key '${objectKeyPrefixMatched}' for route '${JSON.stringify(routeConfig)}'`) + } + + if (bucketNameMatched && eventNameMatched && objectKeyPrefixMatched) { + const resultPromise = routeConfig.action(record, context) + if (resultPromise) { + resultPromises.push(resultPromise) + } + break } + } } - return Promise.all(resultPromises); + return Promise.all(resultPromises) } diff --git a/lib/sns.ts b/lib/sns.ts index eb52294..73cc204 100644 --- a/lib/sns.ts +++ b/lib/sns.ts @@ -1,11 +1,11 @@ -import { SNSEvent, Context, SNSMessage } from "aws-lambda"; -import { ProcessMethod } from "./EventProcessor"; +import { Context, SNSEvent, SNSMessage } from 'aws-lambda' +import { ProcessMethod } from './EventProcessor' export type SnsEvent = SNSEvent export interface SnsRoute { - subject: RegExp; - action: (sns: SNSMessage, context: Context) => Promise | any; + subject: RegExp + action: (sns: SNSMessage, context: Context) => Promise | any } export interface SnsConfig { @@ -16,32 +16,32 @@ export interface SnsConfig { export const process: ProcessMethod = (snsConfig, event, context) => { // detect if it's an sns-event at all: if (snsConfig.debug) { - console.log('sns:Event', JSON.stringify(event)); - console.log('sns:context', context); + console.log('sns:Event', JSON.stringify(event)) + console.log('sns:context', context) } - if (!Array.isArray(event.Records) || event.Records.length<1 || !event.Records[0].Sns) { - console.log('Event does not look like SNS'); - return null; + if (!Array.isArray(event.Records) || event.Records.length < 1 || !event.Records[0].Sns) { + console.log('Event does not look like SNS') + return null } - const sns = event.Records[0].Sns; + const sns = event.Records[0].Sns for (let routeConfig of snsConfig.routes) { - if (routeConfig.subject instanceof RegExp) { - if (routeConfig.subject.test(sns.Subject)) { - const result = routeConfig.action(sns, context); - return result || {}; - } - } else { - console.log(`SNS-Route with subject-regex '${routeConfig.subject}' is not a Regex; it is ignored!`); + if (routeConfig.subject instanceof RegExp) { + if (routeConfig.subject.test(sns.Subject)) { + const result = routeConfig.action(sns, context) + return result || {} } + } else { + console.log(`SNS-Route with subject-regex '${routeConfig.subject}' is not a Regex; it is ignored!`) + } } if (snsConfig.debug) { - console.log(`No subject-match for ${sns.Subject}`); + console.log(`No subject-match for ${sns.Subject}`) } - return null; + return null } /* diff --git a/lib/sqs.ts b/lib/sqs.ts index 7b580bd..df7f543 100644 --- a/lib/sqs.ts +++ b/lib/sqs.ts @@ -1,11 +1,11 @@ -import { SQSEvent, Context, SQSRecord } from "aws-lambda"; -import { ProcessMethod } from "./EventProcessor"; +import { Context, SQSEvent, SQSRecord } from 'aws-lambda' +import { ProcessMethod } from './EventProcessor' export type SqsEvent = SQSEvent export interface SqsRoute { - source: string | RegExp; - action: (messages: SQSRecord['body'][], context: Context) => Promise | any; + source: string | RegExp + action: (messages: SQSRecord['body'][], context: Context) => Promise | any } export interface SqsConfig { @@ -13,39 +13,39 @@ export interface SqsConfig { debug?: boolean; } -export const process: ProcessMethod = (sqsConfig, event, context) => { - // detect if it's an sqs-event at all: - if (sqsConfig.debug) { - console.log('sqs:Event', JSON.stringify(event)); - console.log('sqs:context', context); - } +export const process: ProcessMethod = (sqsConfig, event, context) => { + // detect if it's an sqs-event at all: + if (sqsConfig.debug) { + console.log('sqs:Event', JSON.stringify(event)) + console.log('sqs:context', context) + } - if (!Array.isArray(event.Records) || event.Records.length < 1 || event.Records[0].eventSource !== 'aws:sqs') { - console.log('Event does not look like SQS'); - return null; - } + if (!Array.isArray(event.Records) || event.Records.length < 1 || event.Records[0].eventSource !== 'aws:sqs') { + console.log('Event does not look like SQS') + return null + } - const records = event.Records; - const recordSourceArn = records[0].eventSourceARN; - for (let routeConfig of sqsConfig.routes) { - if (routeConfig.source instanceof RegExp) { - if (routeConfig.source.test(recordSourceArn)) { - const result = routeConfig.action(records.map(record => record.body) , context); - return result || {}; - } - } else { - if (routeConfig.source === recordSourceArn) { - const result = routeConfig.action(records.map(record => record.body) , context); - return result || {}; - } - } + const records = event.Records + const recordSourceArn = records[0].eventSourceARN + for (let routeConfig of sqsConfig.routes) { + if (routeConfig.source instanceof RegExp) { + if (routeConfig.source.test(recordSourceArn)) { + const result = routeConfig.action(records.map(record => record.body), context) + return result || {} + } + } else { + if (routeConfig.source === recordSourceArn) { + const result = routeConfig.action(records.map(record => record.body), context) + return result || {} + } } + } - if (sqsConfig.debug) { - console.log(`No source-match for ${recordSourceArn}`); - } + if (sqsConfig.debug) { + console.log(`No source-match for ${recordSourceArn}`) + } - return null; + return null } /* diff --git a/test/index.test.ts b/test/index.test.ts index cac5803..2809773 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -6,7 +6,7 @@ const lib99 = '../test/__mocks__/lib99' const lib1Config = { [lib1]: {} } as any const event = {} as any -const context = {} as any +const context = {} as any describe('processor.configuration', () => { (global as any).libMock = { @@ -61,7 +61,9 @@ describe('processor.results', () => { }) it('should call reject synchronous with errormessage', async () => { (global as any).libMock = { - lib1: () => { throw new Error('myerror') } + lib1: () => { + throw new Error('myerror') + } } const result = handler(lib1Config)(event, context) diff --git a/test/proxyIntegration-example.json b/test/proxyIntegration-example.json index ce072c4..78aee09 100644 --- a/test/proxyIntegration-example.json +++ b/test/proxyIntegration-example.json @@ -1,41 +1,47 @@ -{ "resource": "/{proxy+}", +{ + "resource": "/{proxy+}", "path": "/article/id/123", "httpMethod": "GET", "headers": null, "multiValueHeaders": null, "queryStringParameters": null, "multiValueQueryStringParameters": null, - "pathParameters": { "proxy": "/article/id/123" }, + "pathParameters": { + "proxy": "/article/id/123" + }, "stageVariables": null, - "requestContext": - { "resourceId": "1ykadr", - "resourcePath": "/{path+}", - "httpMethod": "GET", - "extendedRequestId": "CUdEOHguFiAFu7g=", - "requestTime": "29/Oct/2019:10:05:27 +0000", - "path": "/{path+}", - "accountId": "272144222552", - "protocol": "HTTP/1.1", - "stage": "test-invoke-stage", - "domainPrefix": "testPrefix", - "requestTimeEpoch": 1572343527768, - "requestId": "5e2ae356-64d0-4b9c-a25e-a57c7902128e", - "identity": - { "cognitoIdentityPoolId": null, - "cognitoIdentityId": null, - "apiKey": "test-invoke-api-key", - "principalOrgId": null, - "cognitoAuthenticationType": null, - "userArn": "arn:aws:iam::272144222552:user/christian.gohlke", - "apiKeyId": "test-invoke-api-key-id", - "userAgent": "aws-internal/3 aws-sdk-java/1.11.648 Linux/4.9.184-0.1.ac.235.83.329.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.222-b10 java/1.8.0_222 vendor/Oracle_Corporation", - "accountId": "272144222552", - "caller": "AIDAILF7A6IE7C62DPESO", - "sourceIp": "test-invoke-source-ip", - "accessKey": "ASIAT6XIPJFMHGSWDU6C", - "cognitoAuthenticationProvider": null, - "user": "AIDAILF7A6IE7C62DPESO" }, - "domainName": "testPrefix.testDomainName", - "apiId": "vkjrwsy9fl" }, + "requestContext": { + "resourceId": "1ykadr", + "resourcePath": "/{path+}", + "httpMethod": "GET", + "extendedRequestId": "CUdEOHguFiAFu7g=", + "requestTime": "29/Oct/2019:10:05:27 +0000", + "path": "/{path+}", + "accountId": "272144222552", + "protocol": "HTTP/1.1", + "stage": "test-invoke-stage", + "domainPrefix": "testPrefix", + "requestTimeEpoch": 1572343527768, + "requestId": "5e2ae356-64d0-4b9c-a25e-a57c7902128e", + "identity": { + "cognitoIdentityPoolId": null, + "cognitoIdentityId": null, + "apiKey": "test-invoke-api-key", + "principalOrgId": null, + "cognitoAuthenticationType": null, + "userArn": "arn:aws:iam::272144222552:user/christian.gohlke", + "apiKeyId": "test-invoke-api-key-id", + "userAgent": "aws-internal/3 aws-sdk-java/1.11.648 Linux/4.9.184-0.1.ac.235.83.329.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.222-b10 java/1.8.0_222 vendor/Oracle_Corporation", + "accountId": "272144222552", + "caller": "AIDAILF7A6IE7C62DPESO", + "sourceIp": "test-invoke-source-ip", + "accessKey": "ASIAT6XIPJFMHGSWDU6C", + "cognitoAuthenticationProvider": null, + "user": "AIDAILF7A6IE7C62DPESO" + }, + "domainName": "testPrefix.testDomainName", + "apiId": "vkjrwsy9fl" + }, "body": null, - "isBase64Encoded": false } + "isBase64Encoded": false +} diff --git a/test/proxyIntegration.test.ts b/test/proxyIntegration.test.ts index c3fee36..6aed42d 100644 --- a/test/proxyIntegration.test.ts +++ b/test/proxyIntegration.test.ts @@ -1,7 +1,8 @@ // helper for parameterized tests (http://blog.piotrturski.net/2015/04/jasmine-parameterized-tests.html) -import {log} from "util"; +import { process as proxyIntegration, ProxyIntegrationConfig } from '../lib/proxyIntegration' +import { APIGatewayProxyEvent } from 'aws-lambda' -function forEach(arrayOfArrays: any) { +function forEach (arrayOfArrays: any) { return { it: (description: string, testCaseFunction: (...args: any[]) => void | Promise) => { arrayOfArrays.forEach((innerArray: any) => { @@ -14,13 +15,10 @@ function forEach(arrayOfArrays: any) { } } -import { process as proxyIntegration, ProxyIntegrationConfig } from '../lib/proxyIntegration' -import { APIGatewayProxyEvent } from 'aws-lambda' - const expectedCorsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,HEAD,PATCH", - "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token" + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,HEAD,PATCH', + 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' } const context = {} as any @@ -136,7 +134,7 @@ describe('proxyIntegration.routeHandler.selection', () => { expect(result).toEqual({ statusCode: 200, - headers: Object.assign({ "Content-Type": "application/json" }, expectedCorsHeaders), + headers: Object.assign({ 'Content-Type': 'application/json' }, expectedCorsHeaders), body: '"/"' }) }) @@ -147,13 +145,13 @@ describe('proxyIntegration.routeHandler', () => { const actionSpy = jasmine.createSpy('action') const event = { httpMethod: 'GET', - path: "/shortcut-itemsdev", - headers: { Host: "api.ep.welt.de" }, + path: '/shortcut-itemsdev', + headers: { Host: 'api.ep.welt.de' }, requestContext: { apiId: 'blabla' } } const context = { - awsRequestId: "ab-dc", - functionName: "name" + awsRequestId: 'ab-dc', + functionName: 'name' } proxyIntegration({ @@ -165,15 +163,15 @@ describe('proxyIntegration.routeHandler', () => { }, event as any, context as any) expect(actionSpy).toHaveBeenCalledWith({ - httpMethod: 'GET', headers: jasmine.anything(), requestContext: jasmine.anything(), path: "/", paths: {} + httpMethod: 'GET', headers: jasmine.anything(), requestContext: jasmine.anything(), path: '/', paths: {} }, context) }) it('should remove basepath from root path if coming over custom domain name', () => { const actionSpy = jasmine.createSpy('action') const event = { - httpMethod: 'GET', path: "/shortcut-itemsdev", - headers: { Host: "api.ep.welt.de" }, + httpMethod: 'GET', path: '/shortcut-itemsdev', + headers: { Host: 'api.ep.welt.de' }, requestContext: { apiId: 'blabla' } } proxyIntegration({ @@ -184,14 +182,14 @@ describe('proxyIntegration.routeHandler', () => { }] }, event as any, context) expect(actionSpy).toHaveBeenCalledWith({ - httpMethod: 'GET', headers: jasmine.anything(), requestContext: jasmine.anything(), path: "/", paths: {} + httpMethod: 'GET', headers: jasmine.anything(), requestContext: jasmine.anything(), path: '/', paths: {} }, context) }) it('should remove basepath from multi-slash-path if coming over custom domain name', () => { const actionSpy = jasmine.createSpy('action') const event = { - httpMethod: 'GET', path: "/shortcut-itemsdev/123/456", - headers: { Host: "api.ep.welt.de" }, + httpMethod: 'GET', path: '/shortcut-itemsdev/123/456', + headers: { Host: 'api.ep.welt.de' }, requestContext: { apiId: 'blabla' } } proxyIntegration({ @@ -205,27 +203,30 @@ describe('proxyIntegration.routeHandler', () => { httpMethod: 'GET', headers: jasmine.anything(), requestContext: jasmine.anything(), - path: "/123/456", + path: '/123/456', paths: {} }, context) }) it('should not change path if not coming over custom domain name', async () => { - await assertPathIsUnchanged("blabla.execute-api.eu-central-1.amazonaws.com") + await assertPathIsUnchanged('blabla.execute-api.eu-central-1.amazonaws.com') }) it('should not change path if coming over localhost', async () => { - await assertPathIsUnchanged("localhost") + await assertPathIsUnchanged('localhost') }) it('should return 400 for an invalid body', async () => { const result = await proxyIntegration({ routes: [{} as any] }, { httpMethod: 'GET', path: '/', body: '{keinJson' } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 400, - body: JSON.stringify({ "message": "body is not a valid JSON", "error": "ParseError" }), + body: JSON.stringify({ 'message': 'body is not a valid JSON', 'error': 'ParseError' }), headers: jasmine.anything() }) }) it('should return error for no process found', async () => { - const result = await proxyIntegration({ routes: [{} as any] }, { httpMethod: 'GET', path: '/' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration({ routes: [{} as any] }, { + httpMethod: 'GET', + path: '/' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 404, @@ -291,11 +292,14 @@ describe('proxyIntegration.routeHandler', () => { } ] } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 200, - headers: { "Content-Type": "application/json", "a": "1", "b": "2" }, - body: "{}" + headers: { 'Content-Type': 'application/json', 'a': '1', 'b': '2' }, + body: '{}' }) }) @@ -310,11 +314,14 @@ describe('proxyIntegration.routeHandler', () => { } ] } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 200, - headers: { "Content-Type": "application/json" }, - body: "{}" + headers: { 'Content-Type': 'application/json' }, + body: '{}' }) }) @@ -332,11 +339,14 @@ describe('proxyIntegration.routeHandler', () => { ], errorMapping: { 'myerror': 501 } } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 501, body: '{"message":"bla","error":"myerror"}', - headers: Object.assign({ "Content-Type": "application/json" }, expectedCorsHeaders) + headers: Object.assign({ 'Content-Type': 'application/json' }, expectedCorsHeaders) }) }) it('should modify incorrect error', async () => { @@ -352,10 +362,13 @@ describe('proxyIntegration.routeHandler', () => { } ] } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 500, - body: JSON.stringify({ error: "ServerError", message: "Generic error:" + JSON.stringify(incorrectError) }), + body: JSON.stringify({ error: 'ServerError', message: 'Generic error:' + JSON.stringify(incorrectError) }), headers: expectedCorsHeaders }) }) @@ -373,7 +386,10 @@ describe('proxyIntegration.routeHandler', () => { } ] } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 666, body: '{"message":{"reason":"oops"},"error":666}', @@ -385,7 +401,7 @@ describe('proxyIntegration.routeHandler', () => { describe('proxyIntegration.proxyPath', () => { it('single proxy path', async () => { - const spiedAction = jasmine.createSpy('action').and.returnValue({ }) + const spiedAction = jasmine.createSpy('action').and.returnValue({}) const routeConfig: ProxyIntegrationConfig = { proxyPath: 'apiPath', routes: [ @@ -397,23 +413,31 @@ describe('proxyIntegration.proxyPath', () => { ] } - const result = await proxyIntegration(routeConfig, { resource: "/{apiPath+}", - path: "/article/list", - pathParameters: { apiPath: "/article/list" }, - httpMethod: 'GET' } as any, context) - - expect(spiedAction).toHaveBeenCalledWith({ resource: "/{apiPath+}", paths: {}, path: "/article/list", httpMethod: 'GET', pathParameters: { apiPath: "/article/list" } }, context) + const result = await proxyIntegration(routeConfig, { + resource: '/{apiPath+}', + path: '/article/list', + pathParameters: { apiPath: '/article/list' }, + httpMethod: 'GET' + } as any, context) + + expect(spiedAction).toHaveBeenCalledWith({ + resource: '/{apiPath+}', + paths: {}, + path: '/article/list', + httpMethod: 'GET', + pathParameters: { apiPath: '/article/list' } + }, context) expect(result).toEqual({ statusCode: 200, body: '{}', - headers: Object.assign({ "Content-Type": "application/json" }) + headers: Object.assign({ 'Content-Type': 'application/json' }) }) }) it('multiple proxy path', async () => { - const articleAction = jasmine.createSpy('action').and.returnValue({ }) - const sectionAction = jasmine.createSpy('action').and.returnValue({ }) + const articleAction = jasmine.createSpy('action').and.returnValue({}) + const sectionAction = jasmine.createSpy('action').and.returnValue({}) const routeConfig: ProxyIntegrationConfig = { proxyPath: 'apiPath', routes: [ @@ -429,18 +453,34 @@ describe('proxyIntegration.proxyPath', () => { } ] } - await proxyIntegration(routeConfig, { resource: "/{apiPath+}", - path: "/article/list", - pathParameters: { apiPath: "/article/list" }, - httpMethod: 'GET' } as any, context) - expect(articleAction).toHaveBeenCalledWith({ resource: "/{apiPath+}", paths: {}, path: "/article/list", httpMethod: 'GET', pathParameters: { apiPath: "/article/list" } }, context) + await proxyIntegration(routeConfig, { + resource: '/{apiPath+}', + path: '/article/list', + pathParameters: { apiPath: '/article/list' }, + httpMethod: 'GET' + } as any, context) + expect(articleAction).toHaveBeenCalledWith({ + resource: '/{apiPath+}', + paths: {}, + path: '/article/list', + httpMethod: 'GET', + pathParameters: { apiPath: '/article/list' } + }, context) expect(sectionAction).not.toHaveBeenCalled() - await proxyIntegration(routeConfig, { resource: "/{apiPath+}", - path: "/section/list", - pathParameters: { apiPath: "/section/list" }, - httpMethod: 'GET' } as any, context) - expect(sectionAction).toHaveBeenCalledWith({ resource: "/{apiPath+}", paths: {}, path: "/section/list", httpMethod: 'GET', pathParameters: { apiPath: "/section/list" } }, context) + await proxyIntegration(routeConfig, { + resource: '/{apiPath+}', + path: '/section/list', + pathParameters: { apiPath: '/section/list' }, + httpMethod: 'GET' + } as any, context) + expect(sectionAction).toHaveBeenCalledWith({ + resource: '/{apiPath+}', + paths: {}, + path: '/section/list', + httpMethod: 'GET', + pathParameters: { apiPath: '/section/list' } + }, context) }) }) @@ -461,7 +501,10 @@ describe('proxyIntegration.routeHandler.returnvalues', () => { { method: 'GET', path: '/', action: () => Promise.resolve(customBody) } ] } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 201, headers: { @@ -478,7 +521,10 @@ describe('proxyIntegration.routeHandler.returnvalues', () => { { method: 'GET', path: '/', action: () => Promise.resolve({ foo: 'bar' } as any) } ] } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 200, headers: jasmine.anything(), @@ -493,11 +539,14 @@ describe('proxyIntegration.routeHandler.returnvalues', () => { ], errorMapping: { 'myError': 599 } } - const result = await proxyIntegration(routeConfig, { path: '/', httpMethod: 'GET' } as APIGatewayProxyEvent, context) + const result = await proxyIntegration(routeConfig, { + path: '/', + httpMethod: 'GET' + } as APIGatewayProxyEvent, context) expect(result).toEqual({ statusCode: 599, body: '{"message":"doof","error":"myError"}', - headers: { "Content-Type": "application/json" } + headers: { 'Content-Type': 'application/json' } }) }) }) @@ -505,7 +554,7 @@ describe('proxyIntegration.routeHandler.returnvalues', () => { const assertPathIsUnchanged = async (hostname: string) => { const actionSpy = jasmine.createSpy('action') const event = { - httpMethod: 'GET', path: "/123/456", + httpMethod: 'GET', path: '/123/456', headers: { Host: hostname }, requestContext: { apiId: 'blabla' } } @@ -520,7 +569,7 @@ const assertPathIsUnchanged = async (hostname: string) => { httpMethod: 'GET', headers: jasmine.anything(), requestContext: jasmine.anything(), - path: "/123/456", + path: '/123/456', paths: {} }, context) } diff --git a/test/s3.test.ts b/test/s3.test.ts index 85e751c..92d631a 100644 --- a/test/s3.test.ts +++ b/test/s3.test.ts @@ -1,220 +1,220 @@ import { process as s3 } from '../lib/s3' -import { S3Event, S3EventRecord } from 'aws-lambda'; +import { S3Event, S3EventRecord } from 'aws-lambda' describe('s3.processor', () => { - const context = { bla: "blup" } as any; + const context = { bla: 'blup' } as any it('eventName/bucketName: two events in one record', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: /.*/, bucketName: 'buckettest2', action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: /.*/, bucketName: 'buckettest2', action: actionSpy }] } - const eventFixture = createTwoEvents('ObjectCreated:Put', 'buckettest', 'ObjectCreated:Post', 'buckettest2'); + const eventFixture = createTwoEvents('ObjectCreated:Put', 'buckettest', 'ObjectCreated:Post', 'buckettest2') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[1], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[1], context) + }) it('eventName/bucketName: regex event name with fix bucket name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: /.*/, bucketName: 'buckettest', action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: /.*/, bucketName: 'buckettest', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('eventName/bucketName: regex bucket name with fix event name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: /.*/, action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: /.*/, action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('eventName/bucketName: regex bucket all over should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: /.*/, bucketName: /.*/, action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: /.*/, bucketName: /.*/, action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('eventName/bucketName: exact name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: 'buckettest', action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: 'buckettest', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('eventName/bucketName: not match, because bucket not match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: 'buckettest', action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: 'buckettest', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'wrong', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'wrong', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).not.toHaveBeenCalled(); - }); + expect(actionSpy).not.toHaveBeenCalled() + }) it('eventName/bucketName: not match, because event not match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: 'buckettest', action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', bucketName: 'buckettest', action: actionSpy }] } - const eventFixture = createTestEvent('wrong', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('wrong', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).not.toHaveBeenCalled(); - }); + expect(actionSpy).not.toHaveBeenCalled() + }) it('eventName: exact name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: 'ObjectCreated:Put', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('eventName: regex name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: /ObjectCreated:.*/, action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: /ObjectCreated:.*/, action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('eventName: regex name should not match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ eventName: /ObjectRestore:.*/, action: actionSpy }] }; + const s3Cfg = { routes: [{ eventName: /ObjectRestore:.*/, action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).not.toHaveBeenCalled(); - }); + expect(actionSpy).not.toHaveBeenCalled() + }) it('bucketname: exact name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ bucketName: 'buckettest', action: actionSpy }] }; + const s3Cfg = { routes: [{ bucketName: 'buckettest', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('bucketname: regex not match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ bucketName: /bucket.*/, action: actionSpy }] }; + const s3Cfg = { routes: [{ bucketName: /bucket.*/, action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'buck-wrong', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'buck-wrong', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).not.toHaveBeenCalled(); - }); + expect(actionSpy).not.toHaveBeenCalled() + }) it('action should not call if bucket name no match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ bucketName: 'buckettest', action: actionSpy }] }; + const s3Cfg = { routes: [{ bucketName: 'buckettest', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'wrong-bucket', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'wrong-bucket', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).not.toHaveBeenCalled(); - }); + expect(actionSpy).not.toHaveBeenCalled() + }) it('action should call if no bucket or event is specified', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ action: actionSpy }] }; + const s3Cfg = { routes: [{ action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'wrong-bucket', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'wrong-bucket', 'key/path/to/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('object key prefix with bucket name should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ objectKeyPrefix: 'upload', bucketName: 'test', action: actionSpy }] }; + const s3Cfg = { routes: [{ objectKeyPrefix: 'upload', bucketName: 'test', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'test', 'upload/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'test', 'upload/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('object key prefix should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ objectKeyPrefix: 'upload', action: actionSpy }] }; + const s3Cfg = { routes: [{ objectKeyPrefix: 'upload', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'upload/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'upload/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context); - }); + expect(actionSpy).toHaveBeenCalledWith(eventFixture.Records[0], context) + }) it('object key prefix not should match', () => { - const actionSpy = jasmine.createSpy('action'); + const actionSpy = jasmine.createSpy('action') - const s3Cfg = { routes: [{ objectKeyPrefix: '/upload', action: actionSpy }] }; + const s3Cfg = { routes: [{ objectKeyPrefix: '/upload', action: actionSpy }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'upload/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'upload/file.jpg') - s3(s3Cfg, eventFixture, context); + s3(s3Cfg, eventFixture, context) - expect(actionSpy).not.toHaveBeenCalled(); - }); + expect(actionSpy).not.toHaveBeenCalled() + }) it('object key prefix wih more then one route', () => { - const actionSpyDpa = jasmine.createSpy('action'); - const actionSpyAfp = jasmine.createSpy('action'); - const actionSpyRtr = jasmine.createSpy('action'); + const actionSpyDpa = jasmine.createSpy('action') + const actionSpyAfp = jasmine.createSpy('action') + const actionSpyRtr = jasmine.createSpy('action') const s3Cfg = { routes: [ @@ -222,76 +222,76 @@ describe('s3.processor', () => { { objectKeyPrefix: 'afp', action: actionSpyAfp }, { objectKeyPrefix: 'rtr', action: actionSpyRtr } ] - }; + } - const eventFixture1 = singleEvent('ObjectCreated:Put', 'bucket', 'rtr/file.jpg'); - const eventFixture2 = singleEvent('ObjectCreated:Put', 'bucket', 'afp/file.jpg'); - const eventFixture3 = singleEvent('ObjectCreated:Put', 'bucket', 'rtr/file.jpg'); - const eventFixture4 = singleEvent('ObjectCreated:Put', 'bucket', 'dpa/file.jpg'); + const eventFixture1 = singleEvent('ObjectCreated:Put', 'bucket', 'rtr/file.jpg') + const eventFixture2 = singleEvent('ObjectCreated:Put', 'bucket', 'afp/file.jpg') + const eventFixture3 = singleEvent('ObjectCreated:Put', 'bucket', 'rtr/file.jpg') + const eventFixture4 = singleEvent('ObjectCreated:Put', 'bucket', 'dpa/file.jpg') const records = [] - records.push(eventFixture1, eventFixture2, eventFixture3, eventFixture4); + records.push(eventFixture1, eventFixture2, eventFixture3, eventFixture4) - s3(s3Cfg, { Records: records }, context); + s3(s3Cfg, { Records: records }, context) - expect(actionSpyDpa).toHaveBeenCalledTimes(1); - expect(actionSpyDpa).toHaveBeenCalledWith(eventFixture4, context); + expect(actionSpyDpa).toHaveBeenCalledTimes(1) + expect(actionSpyDpa).toHaveBeenCalledWith(eventFixture4, context) - expect(actionSpyAfp).toHaveBeenCalledTimes(1); - expect(actionSpyAfp).toHaveBeenCalledWith(eventFixture2, context); + expect(actionSpyAfp).toHaveBeenCalledTimes(1) + expect(actionSpyAfp).toHaveBeenCalledWith(eventFixture2, context) - expect(actionSpyRtr).toHaveBeenCalledTimes(2); - }); + expect(actionSpyRtr).toHaveBeenCalledTimes(2) + }) it('should ignore event if it is no S3 event', () => { - const s3Cfg = { routes: [{ subject: /.*/, action: () => 1 }] }; - expect(s3(s3Cfg, {} as any, context)).toBe(null); - expect(s3(s3Cfg, { Records: 1 } as any, context)).toBe(null); - expect(s3(s3Cfg, { Records: [] }, context)).toBe(null); - }); + const s3Cfg = { routes: [{ subject: /.*/, action: () => 1 }] } + expect(s3(s3Cfg, {} as any, context)).toBe(null) + expect(s3(s3Cfg, { Records: 1 } as any, context)).toBe(null) + expect(s3(s3Cfg, { Records: [] }, context)).toBe(null) + }) it('should call first action with matching bucketname', () => { - const actionSpy1 = jasmine.createSpy('action'); - const actionSpy2 = jasmine.createSpy('action'); + const actionSpy1 = jasmine.createSpy('action') + const actionSpy2 = jasmine.createSpy('action') const s3Cfg = { routes: [ { bucketName: 'buckettest', action: actionSpy1 }, { bucketName: 'buckettest', action: actionSpy2 } ] - }; - const event = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg'); + } + const event = createTestEvent('ObjectCreated:Put', 'buckettest', 'key/path/to/file.jpg') - s3(s3Cfg, event, context); + s3(s3Cfg, event, context) - expect(actionSpy1).toHaveBeenCalledWith(event.Records[0], context); - expect(actionSpy2).not.toHaveBeenCalled(); - }); + expect(actionSpy1).toHaveBeenCalledWith(event.Records[0], context) + expect(actionSpy2).not.toHaveBeenCalled() + }) it('should fail on missing action', () => { const s3Cfg = { routes: [{ bucketName: 'buckettest' }] } - const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'key/path/to/file.jpg'); + const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'key/path/to/file.jpg') try { - s3(s3Cfg as any, eventFixture, context); - fail(); + s3(s3Cfg as any, eventFixture, context) + fail() } catch (e) { } - }); + }) it('should fail on missing routes', () => { - const s3Cfg = { routes: [] }; - const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'key/path/to/file.jpg'); + const s3Cfg = { routes: [] } + const eventFixture = createTestEvent('ObjectCreated:Put', 'bucket', 'key/path/to/file.jpg') try { - s3(s3Cfg, eventFixture, context); - fail(); + s3(s3Cfg, eventFixture, context) + fail() } catch (e) { - console.log(e); + console.log(e) } - }); + }) -}); +}) -function createTestEvent(eventName: string, bucketName: string, objectKey: string): S3Event { +function createTestEvent (eventName: string, bucketName: string, objectKey: string): S3Event { return { Records: [ { @@ -310,7 +310,7 @@ function createTestEvent(eventName: string, bucketName: string, objectKey: strin } } -function singleEvent(eventName: string, bucketName: string, key: string): S3EventRecord { +function singleEvent (eventName: string, bucketName: string, key: string): S3EventRecord { return { eventSource: 'aws:s3', eventName: eventName, @@ -325,7 +325,7 @@ function singleEvent(eventName: string, bucketName: string, key: string): S3Even } as S3EventRecord } -function createTwoEvents(eventName: string, bucketName: string, eventName2: string, bucketName2: string): S3Event { +function createTwoEvents (eventName: string, bucketName: string, eventName2: string, bucketName2: string): S3Event { return { Records: [ { @@ -353,5 +353,5 @@ function createTwoEvents(eventName: string, bucketName: string, eventName2: stri } } as S3EventRecord ] - }; + } } diff --git a/test/sns.test.ts b/test/sns.test.ts index 4592114..c331ed0 100644 --- a/test/sns.test.ts +++ b/test/sns.test.ts @@ -1,67 +1,67 @@ import { process as sns, SnsConfig, SnsEvent } from '../lib/sns' describe('sns.processor', () => { - const context = {} as any + const context = {} as any - it('context should be pass through', () => { - const actionSpy = jasmine.createSpy('action'); + it('context should be pass through', () => { + const actionSpy = jasmine.createSpy('action') - const context = {bla: "blup"} as any - const snsCfg: SnsConfig = {routes: [{subject: /.*/, action: actionSpy}]} - const event = {Records: [{Sns: {Subject: 'S', Message: 'M'}}]} as SnsEvent + const context = { bla: 'blup' } as any + const snsCfg: SnsConfig = { routes: [{ subject: /.*/, action: actionSpy }] } + const event = { Records: [{ Sns: { Subject: 'S', Message: 'M' } }] } as SnsEvent - sns(snsCfg, event, context); + sns(snsCfg, event, context) - expect(actionSpy).toHaveBeenCalledWith(event.Records[0].Sns, context); - }); + expect(actionSpy).toHaveBeenCalledWith(event.Records[0].Sns, context) + }) - it('should ignore event if it is no SNS event', () => { - const snsCfg = {routes: [{subject: /.*/, action: () => 1}]}; - expect(sns(snsCfg, {} as any, context)).toBe(null); - expect(sns(snsCfg, {Records: 1 as any}, context)).toBe(null); - expect(sns(snsCfg, {Records: []}, context)).toBe(null); - }); + it('should ignore event if it is no SNS event', () => { + const snsCfg = { routes: [{ subject: /.*/, action: () => 1 }] } + expect(sns(snsCfg, {} as any, context)).toBe(null) + expect(sns(snsCfg, { Records: 1 as any }, context)).toBe(null) + expect(sns(snsCfg, { Records: [] }, context)).toBe(null) + }) - it('should match empty subject for ".*"', () => { - const snsCfg = {routes: [{subject: /.*/, action: () => 1}]}; - expect(sns(snsCfg, {Records: [{Sns: {}} as any]}, context)).toBe(1); - }); + it('should match empty subject for ".*"', () => { + const snsCfg = { routes: [{ subject: /.*/, action: () => 1 }] } + expect(sns(snsCfg, { Records: [{ Sns: {} } as any] }, context)).toBe(1) + }) - it('should match null subject for ".*"', () => { - const snsCfg = {routes: [{subject: /.*/, action: () => 1}]}; - expect(sns(snsCfg, {Records: [{Sns: {Subject: null}} as any]}, context)).toBe(1); - }); + it('should match null subject for ".*"', () => { + const snsCfg = { routes: [{ subject: /.*/, action: () => 1 }] } + expect(sns(snsCfg, { Records: [{ Sns: { Subject: null } } as any] }, context)).toBe(1) + }) - it('should match subject for "/ubjec/"', () => { - const snsCfg = {routes: [{subject: /ubjec/, action: () => 1}]}; - expect(sns(snsCfg, {Records: [{Sns: {Subject: 'Subject'}} as any]}, context)).toBe(1); - }); + it('should match subject for "/ubjec/"', () => { + const snsCfg = { routes: [{ subject: /ubjec/, action: () => 1 }] } + expect(sns(snsCfg, { Records: [{ Sns: { Subject: 'Subject' } } as any] }, context)).toBe(1) + }) - it('should call action with sns-message', () => { - const snsCfg = {routes: [{subject: /.*/, action: (sns: any) => sns}]}; - const event = {Records: [{Sns: {Subject: 'S', Message: 'M'}}]} as SnsEvent - expect(sns(snsCfg, event, context)).toBe(event.Records[0].Sns); - }); + it('should call action with sns-message', () => { + const snsCfg = { routes: [{ subject: /.*/, action: (sns: any) => sns }] } + const event = { Records: [{ Sns: { Subject: 'S', Message: 'M' } }] } as SnsEvent + expect(sns(snsCfg, event, context)).toBe(event.Records[0].Sns) + }) - it('should call first action with matching subject', () => { - const snsCfg = { - routes: [ - {subject: /^123$/, action: () => 1}, - {subject: /123/, action: () => 2} - ] - }; - const event = {Records: [{Sns: {Subject: '1234', Message: 'M'}}]} as SnsEvent - expect(sns(snsCfg, event, context)).toBe(2); - }); + it('should call first action with matching subject', () => { + const snsCfg = { + routes: [ + { subject: /^123$/, action: () => 1 }, + { subject: /123/, action: () => 2 } + ] + } + const event = { Records: [{ Sns: { Subject: '1234', Message: 'M' } }] } as SnsEvent + expect(sns(snsCfg, event, context)).toBe(2) + }) - it('should not fail on missing subject', () => { - const snsCfg = {routes: [{action: () => 1}]} as any - sns(snsCfg, {Records: [{Sns: {Subject: 'Subject'}}]} as SnsEvent, context); - }); + it('should not fail on missing subject', () => { + const snsCfg = { routes: [{ action: () => 1 }] } as any + sns(snsCfg, { Records: [{ Sns: { Subject: 'Subject' } }] } as SnsEvent, context) + }) - it('should fail on missing action', () => { - const snsCfg = {routes: [{subject: /.*/}]} as any - expect(() => sns(snsCfg, {Records: [{Sns: {Subject: 'Subject'}}]} as SnsEvent, context)).toThrow() - }); + it('should fail on missing action', () => { + const snsCfg = { routes: [{ subject: /.*/ }] } as any + expect(() => sns(snsCfg, { Records: [{ Sns: { Subject: 'Subject' } }] } as SnsEvent, context)).toThrow() + }) -}); +}) diff --git a/test/sqs.test.ts b/test/sqs.test.ts index 40200f9..299cffc 100644 --- a/test/sqs.test.ts +++ b/test/sqs.test.ts @@ -1,74 +1,89 @@ import { process as sqs, SqsEvent } from '../lib/sqs' describe('sqs.processor', () => { - const context = {} as any - - it('context should be passed through', () => { - const actionSpy = jasmine.createSpy('action') - - const context = {bla: "blup"} as any - const sqsCfg = {routes: [{source: /.*/, action: actionSpy}]} - const event = {Records: [{eventSource: 'aws:sqs', body: 'B'}]} as SqsEvent - - sqs(sqsCfg, event, context) - - expect(actionSpy).toHaveBeenCalledWith([event.Records[0].body], context) - }) - - it('should ignore event if it is no SQS event', () => { - const sqsCfg = {routes: [{source: /.*/, action: () => 1}]} - expect(sqs(sqsCfg, {} as any, context)).toBe(null) - expect(sqs(sqsCfg, {Records: 1 as any}, context)).toBe(null) - expect(sqs(sqsCfg, {Records: []}, context)).toBe(null) - }) - - it('should match null source for ".*"', () => { - const sqsCfg = {routes: [{source: /.*/, action: () => 1}]} - expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: null}]} as any, context)).toBe(1) - }) - - it('should match empty subject for ".*"', () => { - const sqsCfg = {routes: [{subject: /.*/, action: () => 1}]} as any - expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', body: 'B'}]} as SqsEvent, context)).toBe(1) - }) - - it('should match source for "/porter/"', () => { - const sqsCfg = {routes: [{source: /porter/, action: () => 1}]} - expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer'}]} as SqsEvent, context)).toBe(1) - }) - - it('should call action with sqs-message', () => { - const sqsCfg = {routes: [{source: /porter/, action: (events: any) => events}]} - const event = {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer', body: 'B'}]} as SqsEvent - - expect(sqs(sqsCfg, event, context)).toEqual([event.Records[0].body]) - }) - - it('should call first action with matching subject', () => { - const sqsCfg = { - routes: [ - {source: /^123$/, action: () => 1}, - {source: /123/, action: () => 2}, - {source: /1234/, action: () => 3} - ] - } - const event = {Records: [{eventSource: 'aws:sqs', eventSourceARN: '1234', body: 'B'}]} as SqsEvent - expect(sqs(sqsCfg, event, context)).toBe(2) - }) - - it('should match complete source', () => { - const sqsCfg = {routes: [{source: 'aws:123:importer', action: () => 1}]} - expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'aws:123:importer'}]} as SqsEvent, context)).toBe(1) - }) - - it('should not throw error on missing source', () => { - const sqsCfg = {routes: [{action: () => 1}]} as any - sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer'}]} as SqsEvent, context) - }) - - it('should fail on missing action', () => { - const sqsCfg = {routes: [{source: /.*/}]} as any - expect(() => sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer'}]} as SqsEvent, context)).toThrow() - }) + const context = {} as any + + it('context should be passed through', () => { + const actionSpy = jasmine.createSpy('action') + + const context = { bla: 'blup' } as any + const sqsCfg = { routes: [{ source: /.*/, action: actionSpy }] } + const event = { Records: [{ eventSource: 'aws:sqs', body: 'B' }] } as SqsEvent + + sqs(sqsCfg, event, context) + + expect(actionSpy).toHaveBeenCalledWith([event.Records[0].body], context) + }) + + it('should ignore event if it is no SQS event', () => { + const sqsCfg = { routes: [{ source: /.*/, action: () => 1 }] } + expect(sqs(sqsCfg, {} as any, context)).toBe(null) + expect(sqs(sqsCfg, { Records: 1 as any }, context)).toBe(null) + expect(sqs(sqsCfg, { Records: [] }, context)).toBe(null) + }) + + it('should match null source for ".*"', () => { + const sqsCfg = { routes: [{ source: /.*/, action: () => 1 }] } + expect(sqs(sqsCfg, { Records: [{ eventSource: 'aws:sqs', eventSourceARN: null }] } as any, context)).toBe(1) + }) + + it('should match empty subject for ".*"', () => { + const sqsCfg = { routes: [{ subject: /.*/, action: () => 1 }] } as any + expect(sqs(sqsCfg, { Records: [{ eventSource: 'aws:sqs', body: 'B' }] } as SqsEvent, context)).toBe(1) + }) + + it('should match source for "/porter/"', () => { + const sqsCfg = { routes: [{ source: /porter/, action: () => 1 }] } + expect(sqs(sqsCfg, { + Records: [{ + eventSource: 'aws:sqs', + eventSourceARN: 'importer' + }] + } as SqsEvent, context)).toBe(1) + }) + + it('should call action with sqs-message', () => { + const sqsCfg = { routes: [{ source: /porter/, action: (events: any) => events }] } + const event = { Records: [{ eventSource: 'aws:sqs', eventSourceARN: 'importer', body: 'B' }] } as SqsEvent + + expect(sqs(sqsCfg, event, context)).toEqual([event.Records[0].body]) + }) + + it('should call first action with matching subject', () => { + const sqsCfg = { + routes: [ + { source: /^123$/, action: () => 1 }, + { source: /123/, action: () => 2 }, + { source: /1234/, action: () => 3 } + ] + } + const event = { Records: [{ eventSource: 'aws:sqs', eventSourceARN: '1234', body: 'B' }] } as SqsEvent + expect(sqs(sqsCfg, event, context)).toBe(2) + }) + + it('should match complete source', () => { + const sqsCfg = { routes: [{ source: 'aws:123:importer', action: () => 1 }] } + expect(sqs(sqsCfg, { + Records: [{ + eventSource: 'aws:sqs', + eventSourceARN: 'aws:123:importer' + }] + } as SqsEvent, context)).toBe(1) + }) + + it('should not throw error on missing source', () => { + const sqsCfg = { routes: [{ action: () => 1 }] } as any + sqs(sqsCfg, { Records: [{ eventSource: 'aws:sqs', eventSourceARN: 'importer' }] } as SqsEvent, context) + }) + + it('should fail on missing action', () => { + const sqsCfg = { routes: [{ source: /.*/ }] } as any + expect(() => sqs(sqsCfg, { + Records: [{ + eventSource: 'aws:sqs', + eventSourceARN: 'importer' + }] + } as SqsEvent, context)).toThrow() + }) }) diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..3893f76 --- /dev/null +++ b/tslint.json @@ -0,0 +1,6 @@ +{ + "defaultSeverity": "warning", + "extends": [ + "tslint-config-standard" + ] +}