Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/chai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,20 @@ await expect({

### `to.have.log()`

Asserts log message of a lambda function
Asserts existence of a cloudwatch log message

```js
await expect({
region: 'us-east-1',
// use either an explicit log group
logGroupName: 'logGroupName',
// or a function name to match a lambda function logs
function: 'functionName',
startTime: 0 /* optional (millis since epoch in UTC, defaults to now-1 hour) */,
startTime: 0 /* optional (milliseconds since epoch in UTC, defaults to now-1 hour) */,
timeout: 0 /* optional (defaults to 2500) */,
pollEvery: 0 /* optional (defaults to 500) */,
}).to.have.log(
'some message written to log by the lambda' /* a pattern to match against log messages */,
'some message written to log' /* a pattern to match against log messages */,
);
```

Expand Down Expand Up @@ -157,7 +160,7 @@ await expect({
timeout: 0 /* optional (defaults to 10000) */,
pollEvery: 0 /* optional (defaults to 500) */,
}).to.have.record(
item => item.id === 'someId' /* predicate to match with the stream data */,
(item) => item.id === 'someId' /* predicate to match with the stream data */,
);
```

Expand Down Expand Up @@ -188,7 +191,7 @@ try {
pollEvery: 2500 /* optional (defaults to 500) */,
}).to.have.message(
/* predicate to match with the messages in the queue */
message =>
(message) =>
message.Subject === 'Some Subject' && message.Message === 'Some Message',
);
} finally {
Expand Down
29 changes: 25 additions & 4 deletions src/chai/cloudwatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ describe('cloudwatch', () => {

beforeEach(() => {
jest.clearAllMocks();

const { getLogGroupName } = require('../utils/cloudwatch');
getLogGroupName.mockImplementation(
(functionName: string) => `/aws/lambda/${functionName}`,
);
});

test('should throw error on filterLogEvents error', async () => {
Expand All @@ -50,14 +55,13 @@ describe('cloudwatch', () => {
expect(verifyProps).toHaveBeenCalledTimes(1);
expect(verifyProps).toHaveBeenCalledWith({ ...props, pattern }, [
'region',
'function',
'pattern',
]);

expect(filterLogEvents).toHaveBeenCalledTimes(1);
expect(filterLogEvents).toHaveBeenCalledWith(
region,
functionName,
`/aws/lambda/${functionName}`,
startTime,
pattern,
);
Expand All @@ -77,18 +81,35 @@ describe('cloudwatch', () => {
expect(filterLogEvents).toHaveBeenCalledTimes(1);
expect(filterLogEvents).toHaveBeenCalledWith(
propsNoTime.region,
propsNoTime.function,
`/aws/lambda/${functionName}`,
11 * 60 * 60 * 1000,
pattern,
);
expect(verifyProps).toHaveBeenCalledTimes(1);
expect(verifyProps).toHaveBeenCalledWith({ ...propsNoTime, pattern }, [
'region',
'function',
'pattern',
]);
});

test('should pass custom log group name to filterLogEvents', async () => {
const { filterLogEvents } = require('../utils/cloudwatch');

filterLogEvents.mockReturnValue(Promise.resolve({ events: ['event'] }));

await chai
.expect({ ...props, logGroupName: 'customLogGroup' })
.to.have.log(pattern);

expect(filterLogEvents).toHaveBeenCalledTimes(1);
expect(filterLogEvents).toHaveBeenCalledWith(
props.region,
'customLogGroup',
props.startTime,
pattern,
);
});

test('should pass on have log', async () => {
const { filterLogEvents } = require('../utils/cloudwatch');

Expand Down
11 changes: 7 additions & 4 deletions src/chai/cloudwatch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { epochDateMinusHours, verifyProps } from '../common';
import { expectedProps, ICloudwatchProps } from '../common/cloudwatch';
import { filterLogEvents } from '../utils/cloudwatch';
import { filterLogEvents, getLogGroupName } from '../utils/cloudwatch';
import { wrapWithRetries } from './utils';

const attemptCloudwatch = async function (this: any, pattern: string) {
Expand All @@ -12,18 +12,21 @@ const attemptCloudwatch = async function (this: any, pattern: string) {
region,
function: functionName,
startTime = epochDateMinusHours(1),
logGroupName,
} = props;

const { events } = await filterLogEvents(
region,
functionName,
logGroupName || getLogGroupName(functionName || ''),
startTime,
pattern,
);
const found = events.length > 0;

const messageSubject = logGroupName || functionName;
return {
message: `expected ${functionName} to have log matching ${pattern}`,
negateMessage: `expected ${functionName} not to have log matching ${pattern}`,
message: `expected ${messageSubject} to have log matching ${pattern}`,
negateMessage: `expected ${messageSubject} not to have log matching ${pattern}`,
pass: found,
};
};
Expand Down
5 changes: 3 additions & 2 deletions src/common/cloudwatch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ICommonProps } from './';

export interface ICloudwatchProps extends ICommonProps {
function: string;
function?: string;
startTime?: number;
logGroupName?: string;
}

export const expectedProps = ['region', 'function', 'pattern'];
export const expectedProps = ['region', 'pattern'];
9 changes: 6 additions & 3 deletions src/jest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,20 @@ await expect({

### `toHaveLog()`

Asserts log message of a lambda function
Asserts existence of a cloudwatch log message

```js
await expect({
region: 'us-east-1',
// use either an explicit log group
logGroupName: 'logGroupName',
// or a function name to match a lambda function logs
function: 'functionName',
startTime: 0 /* optional (millis since epoch in UTC, defaults to now-1 hour) */,
startTime: 0 /* optional (milliseconds since epoch in UTC, defaults to now-1 hour) */,
timeout: 0 /* optional (defaults to 2500) */,
pollEvery: 0 /* optional (defaults to 500) */,
}).toHaveLog(
'some message written to log by the lambda' /* a pattern to match against log messages */,
'some message written to log' /* a pattern to match against log messages */,
);
```

Expand Down
30 changes: 26 additions & 4 deletions src/jest/cloudwatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ describe('cloudwatch matchers', () => {

beforeEach(() => {
jest.clearAllMocks();

const { getLogGroupName } = require('../utils/cloudwatch');
getLogGroupName.mockImplementation(
(functionName: string) => `/aws/lambda/${functionName}`,
);
});

test('should throw error on filterLogEvents error', async () => {
Expand All @@ -50,7 +55,7 @@ describe('cloudwatch matchers', () => {
expect(filterLogEvents).toHaveBeenCalledTimes(1);
expect(filterLogEvents).toHaveBeenCalledWith(
props.region,
props.function,
`/aws/lambda/${props.function}`,
startTime,
pattern,
);
Expand All @@ -61,7 +66,6 @@ describe('cloudwatch matchers', () => {
expect(verifyProps).toHaveBeenCalledTimes(1);
expect(verifyProps).toHaveBeenCalledWith({ ...props, pattern }, [
'region',
'function',
'pattern',
]);
});
Expand All @@ -81,18 +85,36 @@ describe('cloudwatch matchers', () => {
expect(filterLogEvents).toHaveBeenCalledTimes(1);
expect(filterLogEvents).toHaveBeenCalledWith(
propsNoTime.region,
propsNoTime.function,
`/aws/lambda/${props.function}`,
11 * 60 * 60 * 1000,
pattern,
);
expect(verifyProps).toHaveBeenCalledTimes(1);
expect(verifyProps).toHaveBeenCalledWith({ ...propsNoTime, pattern }, [
'region',
'function',
'pattern',
]);
});

test('should pass custom log group name to filterLogEvents', async () => {
const { filterLogEvents } = require('../utils/cloudwatch');

filterLogEvents.mockReturnValue(Promise.resolve({ events: ['event'] }));

await toHaveLog.bind(matcherUtils)(
{ ...props, logGroupName: 'customLogGroup' },
pattern,
);

expect(filterLogEvents).toHaveBeenCalledTimes(1);
expect(filterLogEvents).toHaveBeenCalledWith(
props.region,
'customLogGroup',
props.startTime,
pattern,
);
});

test('should not pass when no events found', async () => {
const { filterLogEvents } = require('../utils/cloudwatch');

Expand Down
13 changes: 8 additions & 5 deletions src/jest/cloudwatch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EOL } from 'os';
import { expectedProps, ICloudwatchProps } from '../common/cloudwatch';
import { epochDateMinusHours, verifyProps } from '../common/index';
import { filterLogEvents } from '../utils/cloudwatch';
import { filterLogEvents, getLogGroupName } from '../utils/cloudwatch';

export const toHaveLog = async function (
this: jest.MatcherUtils,
Expand All @@ -13,10 +13,13 @@ export const toHaveLog = async function (
region,
function: functionName,
startTime = epochDateMinusHours(1),
logGroupName,
} = props;

try {
const printFunctionName = this.utils.printExpected(functionName);
const messageSubject = this.utils.printExpected(
logGroupName || functionName,
);
const printRegion = this.utils.printExpected(region);
const printPattern = this.utils.printExpected(pattern) + EOL;

Expand All @@ -25,7 +28,7 @@ export const toHaveLog = async function (

const { events } = await filterLogEvents(
region,
functionName,
logGroupName || getLogGroupName(functionName || ''),
startTime,
pattern,
);
Expand All @@ -34,14 +37,14 @@ export const toHaveLog = async function (
// matching log found
return {
message: () =>
`${notHint}Expected ${printFunctionName} at region ${printRegion} not to have log matching pattern ${printPattern}`,
`${notHint}Expected ${messageSubject} at region ${printRegion} not to have log matching pattern ${printPattern}`,
pass: true,
};
} else {
// matching log not found
return {
message: () =>
`${hint}Expected ${printFunctionName} at region ${printRegion} to have log matching pattern ${printPattern}`,
`${hint}Expected ${messageSubject} at region ${printRegion} to have log matching pattern ${printPattern}`,
pass: false,
};
}
Expand Down
12 changes: 5 additions & 7 deletions src/utils/cloudwatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ describe('cloudwatch utils', () => {
const AWS = require('aws-sdk');
const cloudWatchLogs = AWS.CloudWatchLogs;

const [region, functionName] = ['region', 'functionName'];

const logGroupName = `/aws/lambda/${functionName}`;
const [region, logGroupName] = ['region', `/aws/lambda/functionName`];

describe('deleteAllLogs', () => {
test('should not call deleteLogStream on no log streams', async () => {
Expand All @@ -33,7 +31,7 @@ describe('cloudwatch utils', () => {

jest.clearAllMocks();

await deleteAllLogs(region, functionName);
await deleteAllLogs(region, 'functionName');

expect(cloudWatchLogs).toHaveBeenCalledTimes(1);
expect(cloudWatchLogs).toHaveBeenCalledWith({ region });
Expand All @@ -59,7 +57,7 @@ describe('cloudwatch utils', () => {

jest.clearAllMocks();

await deleteAllLogs(region, functionName);
await deleteAllLogs(region, 'functionName');

expect(deleteLogStream).toHaveBeenCalledTimes(logStreams.length);

Expand All @@ -85,7 +83,7 @@ describe('cloudwatch utils', () => {
const filterPattern = 'filterPattern';
const actual = await getEvents(
region,
functionName,
logGroupName,
startTime,
filterPattern,
);
Expand Down Expand Up @@ -114,7 +112,7 @@ describe('cloudwatch utils', () => {
const filterPattern = 'filterPattern';
const actual = await getEvents(
region,
functionName,
logGroupName,
startTime,
filterPattern,
);
Expand Down
6 changes: 3 additions & 3 deletions src/utils/cloudwatch.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import AWS = require('aws-sdk');

const getLogGroupName = (functionName: string) => `/aws/lambda/${functionName}`;
export const getLogGroupName = (functionName: string) =>
`/aws/lambda/${functionName}`;

export const filterLogEvents = async (
region: string,
functionName: string,
logGroupName: string,
startTime: number,
pattern: string,
) => {
const cloudWatchLogs = new AWS.CloudWatchLogs({ region });
const logGroupName = getLogGroupName(functionName);
const filterPattern = `"${pattern}"`; // enclose with "" to support special characters

const { events = [] } = await cloudWatchLogs
Expand Down