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
3 changes: 2 additions & 1 deletion packages/testing/src/resources/TestNodejsFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class TestNodejsFunction extends NodejsFunction {
extraProps: ExtraTestProps
) {
const isESM = extraProps.outputFormat === 'ESM';
const { shouldPolyfillRequire = false } = extraProps;
const { bundling, ...restProps } = props;
const functionName = concatenateResourceName({
testName: stack.testName,
Expand All @@ -45,7 +46,7 @@ class TestNodejsFunction extends NodejsFunction {
mainFields: isESM ? ['module', 'main'] : ['main', 'module'],
sourceMap: false,
format: isESM ? OutputFormat.ESM : OutputFormat.CJS,
banner: isESM
banner: shouldPolyfillRequire
? `import { createRequire } from 'module';const require = createRequire(import.meta.url);`
: '',
},
Expand Down
7 changes: 7 additions & 0 deletions packages/testing/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ interface ExtraTestProps {
* @default 'CJS'
*/
outputFormat?: 'CJS' | 'ESM';
/**
* Determines whether to polyfil the `require` function, this is useful when the bundler
* output is ESM and you are using a package that only ships ESM
*
* @default 'false'
*/
shouldPolyfillRequire?: boolean;
/**
* Whether to create an alias for the function.
*
Expand Down
308 changes: 160 additions & 148 deletions packages/tracer/tests/e2e/decorator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,159 +17,171 @@ import {
RESOURCE_NAME_PREFIX,
} from './constants.js';

describe('Tracer E2E tests, decorator instrumentation', () => {
const testStack = new TestStack({
stackNameProps: {
stackNamePrefix: RESOURCE_NAME_PREFIX,
testName: 'Decorator',
},
});

// Location of the lambda function code
const lambdaFunctionCodeFilePath = join(
__dirname,
'decorator.test.functionCode.ts'
);
const startTime = new Date();

const testTable = new TestDynamodbTable(
testStack,
{},
{
nameSuffix: 'TestTable',
}
);

const fnDecorator = new TestNodejsFunction(
testStack,
{
entry: lambdaFunctionCodeFilePath,
environment: {
TEST_TABLE_NAME: testTable.tableName,
POWERTOOLS_SERVICE_NAME: 'Decorator',
},
},
{
nameSuffix: 'Decorator',
}
);
testTable.grantWriteData(fnDecorator);

const invocationCount = 2;
let traceData: EnrichedXRayTraceDocumentParsed[] = [];

beforeAll(async () => {
// Deploy the stack
await testStack.deploy();

// Get the actual function names from the stack outputs
const fnNameDecorator = testStack.findAndGetStackOutputValue('Decorator');

// Act
await invokeAllTestCases(fnNameDecorator, invocationCount);
traceData = await getTraces({
startTime,
resourceName: fnNameDecorator,
expectedTracesCount: invocationCount,
/**
* The trace should have 4 segments:
* 1. Lambda Context (AWS::Lambda)
* 2. Lambda Function (AWS::Lambda::Function)
* 4. DynamoDB (AWS::DynamoDB)
* 4. Remote call (docs.aws.amazon.com)
*/
expectedSegmentsCount: 4,
});
});

afterAll(async () => {
if (!process.env.DISABLE_TEARDOWN) {
await testStack.destroy();
}
});

it('should generate all trace data correctly', () => {
// Assess
const mainSubsegment = traceData[0];
const { subsegments, annotations, metadata } = mainSubsegment;

// Check the main segment name
expect(mainSubsegment.name).toBe('## index.handler');

// Check the subsegments of the main segment
expect(subsegments.size).toBe(3);

// Check remote call subsegment
expect(subsegments.has('docs.aws.amazon.com')).toBe(true);
const httpSubsegment = subsegments.get('docs.aws.amazon.com');
expect(httpSubsegment?.namespace).toBe('remote');
expect(httpSubsegment?.http?.request?.url).toEqual(
'https://docs.aws.amazon.com/powertools/typescript/latest/'
);
expect(httpSubsegment?.http?.request?.method).toBe('GET');
expect(httpSubsegment?.http?.response?.status).toEqual(expect.any(Number));
expect(httpSubsegment?.http?.response?.status).toEqual(expect.any(Number));

// Check the custom subsegment name & metadata
expect(subsegments.has(expectedCustomSubSegmentName)).toBe(true);
expect(
subsegments.get(expectedCustomSubSegmentName)?.metadata
).toStrictEqual({
Decorator: {
'myMethod response': expectedCustomResponseValue,
describe.each([
{ outputFormat: 'CJS' as const },
{ outputFormat: 'ESM' as const },
])(
'Tracer E2E tests, decorator instrumentation ($outputFormat)',
({ outputFormat }) => {
const testStack = new TestStack({
stackNameProps: {
stackNamePrefix: RESOURCE_NAME_PREFIX,
testName: `Decorator${outputFormat}`,
},
});

// Check the other custom subsegment and its subsegments
expect(subsegments.has('### methodNoResponse')).toBe(true);
expect(subsegments.get('### methodNoResponse')?.metadata).toBeUndefined();
expect(subsegments.get('### methodNoResponse')?.subsegments?.length).toBe(
1
// Location of the lambda function code
const lambdaFunctionCodeFilePath = join(
__dirname,
'decorator.test.functionCode.ts'
);
expect(
subsegments.get('### methodNoResponse')?.subsegments?.[0]?.name ===
'DynamoDB'
).toBe(true);

// Check the annotations of the main segment
if (!annotations) {
throw new Error('No annotations found on the main segment');
}
expect(annotations.ColdStart).toEqual(true);
expect(annotations.Service).toEqual('Decorator');
expect(annotations[expectedCustomAnnotationKey]).toEqual(
expectedCustomAnnotationValue
const startTime = new Date();

const testTable = new TestDynamodbTable(
testStack,
{},
{
nameSuffix: 'TestTable',
}
);

// Check the metadata of the main segment
if (!metadata) {
throw new Error('No metadata found on the main segment');
}
expect(metadata.Decorator[expectedCustomMetadataKey]).toEqual(
expectedCustomMetadataValue
const fnDecorator = new TestNodejsFunction(
testStack,
{
entry: lambdaFunctionCodeFilePath,
environment: {
TEST_TABLE_NAME: testTable.tableName,
POWERTOOLS_SERVICE_NAME: 'Decorator',
},
},
{
nameSuffix: 'Decorator',
outputFormat,
shouldPolyfillRequire: outputFormat === 'ESM',
}
);
testTable.grantWriteData(fnDecorator);

const invocationCount = 2;
let traceData: EnrichedXRayTraceDocumentParsed[] = [];

beforeAll(async () => {
// Deploy the stack
await testStack.deploy();

// Get the actual function names from the stack outputs
const fnNameDecorator = testStack.findAndGetStackOutputValue('Decorator');

// Act
await invokeAllTestCases(fnNameDecorator, invocationCount);
traceData = await getTraces({
startTime,
resourceName: fnNameDecorator,
expectedTracesCount: invocationCount,
/**
* The trace should have 4 segments:
* 1. Lambda Context (AWS::Lambda)
* 2. Lambda Function (AWS::Lambda::Function)
* 4. DynamoDB (AWS::DynamoDB)
* 4. Remote call (docs.aws.amazon.com)
*/
expectedSegmentsCount: 4,
});
});

// Check the response is present in the metadata
expect(metadata.Decorator['index.handler response']).toEqual(
expectedCustomResponseValue
);
});

it('should annotate the trace with error data correctly', () => {
const mainSubsegment = traceData[1];
const { annotations } = mainSubsegment;

// Check the annotations of the main segment
if (!annotations) {
throw new Error('No annotations found on the main segment');
}
expect(annotations.ColdStart).toEqual(false);

// Check that the main segment has error data
expect(mainSubsegment.fault).toBe(true);
expect(Object.hasOwn(mainSubsegment, 'cause')).toBe(true);
expect(mainSubsegment.cause?.exceptions[0].message).toBe(
expectedCustomErrorMessage
);
});
});
afterAll(async () => {
if (!process.env.DISABLE_TEARDOWN) {
await testStack.destroy();
}
});

it('should generate all trace data correctly', () => {
// Assess
const mainSubsegment = traceData[0];
const { subsegments, annotations, metadata } = mainSubsegment;

// Check the main segment name
expect(mainSubsegment.name).toBe('## index.handler');

// Check the subsegments of the main segment
expect(subsegments.size).toBe(3);

// Check remote call subsegment
expect(subsegments.has('docs.aws.amazon.com')).toBe(true);
const httpSubsegment = subsegments.get('docs.aws.amazon.com');
expect(httpSubsegment?.namespace).toBe('remote');
expect(httpSubsegment?.http?.request?.url).toEqual(
'https://docs.aws.amazon.com/powertools/typescript/latest/'
);
expect(httpSubsegment?.http?.request?.method).toBe('GET');
expect(httpSubsegment?.http?.response?.status).toEqual(
expect.any(Number)
);
expect(httpSubsegment?.http?.response?.status).toEqual(
expect.any(Number)
);

// Check the custom subsegment name & metadata
expect(subsegments.has(expectedCustomSubSegmentName)).toBe(true);
expect(
subsegments.get(expectedCustomSubSegmentName)?.metadata
).toStrictEqual({
Decorator: {
'myMethod response': expectedCustomResponseValue,
},
});

// Check the other custom subsegment and its subsegments
expect(subsegments.has('### methodNoResponse')).toBe(true);
expect(subsegments.get('### methodNoResponse')?.metadata).toBeUndefined();
expect(subsegments.get('### methodNoResponse')?.subsegments?.length).toBe(
1
);
expect(
subsegments.get('### methodNoResponse')?.subsegments?.[0]?.name ===
'DynamoDB'
).toBe(true);

// Check the annotations of the main segment
if (!annotations) {
throw new Error('No annotations found on the main segment');
}
expect(annotations.ColdStart).toEqual(true);
expect(annotations.Service).toEqual('Decorator');
expect(annotations[expectedCustomAnnotationKey]).toEqual(
expectedCustomAnnotationValue
);

// Check the metadata of the main segment
if (!metadata) {
throw new Error('No metadata found on the main segment');
}
expect(metadata.Decorator[expectedCustomMetadataKey]).toEqual(
expectedCustomMetadataValue
);

// Check the response is present in the metadata
expect(metadata.Decorator['index.handler response']).toEqual(
expectedCustomResponseValue
);
});

it('should annotate the trace with error data correctly', () => {
const mainSubsegment = traceData[1];
const { annotations } = mainSubsegment;

// Check the annotations of the main segment
if (!annotations) {
throw new Error('No annotations found on the main segment');
}
expect(annotations.ColdStart).toEqual(false);

// Check that the main segment has error data
expect(mainSubsegment.fault).toBe(true);
expect(Object.hasOwn(mainSubsegment, 'cause')).toBe(true);
expect(mainSubsegment.cause?.exceptions[0].message).toBe(
expectedCustomErrorMessage
);
});
}
);
Loading
Loading