diff --git a/.github/workflows/on-merge-to-main.yml b/.github/workflows/on-merge-to-main.yml index b18306cb54..a22a8f1c45 100644 --- a/.github/workflows/on-merge-to-main.yml +++ b/.github/workflows/on-merge-to-main.yml @@ -7,7 +7,6 @@ on: jobs: publish: runs-on: ubuntu-latest - steps: - name: "Checkout" uses: actions/checkout@v2 diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml new file mode 100644 index 0000000000..6a8b862a4a --- /dev/null +++ b/.github/workflows/run-e2e-tests.yml @@ -0,0 +1,30 @@ +name: run-e2e-tests +on: + workflow_dispatch: {} +jobs: + run: + runs-on: ubuntu-latest + permissions: + id-token: write # needed to interact with GitHub's OIDC Token endpoint. + contents: read + steps: + - name: "Checkout" + uses: actions/checkout@v2 + ######################### + # Release new version + ######################### + - name: "Use NodeJS 14" + uses: actions/setup-node@v2 + with: + node-version: '14' + - name: Install packages + run: | + npm ci + npm run lerna-ci + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1.6.0 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN_TO_ASSUME }} + aws-region: eu-west-1 + - name: Run integration tests + run: npm run lerna-test:e2e diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11d5f69a9d..4dc383bd91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,6 +57,96 @@ You can build and start a local docs website by running these two commands. - `npm run docs-buildDockerImage` OR `docker build -t squidfunk/mkdocs-material ./docs/` - `npm run docs-runLocalDocker` OR `docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material` +### Tests + +Tests are under `tests` folder of each modules and split into two categories: unit tests and e2e (end-to-end) tests. + +You can run each group separately or all together thanks to [jest-runner-groups](https://www.npmjs.com/package/jest-runner-groups). + +Unit tests, under `tests/unit` folder are standard [Jest](https://jestjs.io) tests. + +End-to-end tests, under `tests/e2e` folder, will test the module features by deploying AWS Lambda functions into your AWS Account. We use [aws-cdk](https://docs.aws.amazon.com/cdk/v1/guide/getting_started.html) v1 library (not v2 due to [this cdk issue](https://github.com/aws/aws-cdk/issues/18211)) for Typescript for creating infrastructure, and [aws-sdk-js v2](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/) for invoking the functions and assert on the expected behaviors. All steps are also executed by Jest. + + +Running end-to-end tests will deploy AWS resources. You will need an AWS account and the tests might incur costs. The cost from **some services** are covered by the [AWS Free Tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) but not all of them. If you don't have an AWS Account follow [these instructions to create one](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). + +When contributing to this repository, these end-to-end tests are run by the maintainers before merging a PR. + + +**Unit tests** + +**Write** + +As mentioned before, tests are split into groups thanks to [jest-runner-groups](https://www.npmjs.com/package/jest-runner-groups) and therefore each test needs to be tagged properly by adding the following comments in the header of your unit test file: + +```typescript +/** + * Tests metrics + * + * @group unit// + */ +``` + +**Run** + +To run unit tests you can either use +* npm task `lerna-test:unit` (`npm run lerna-test:unit`) in root folder to run them all +* npm task `test:e2e` (`npm run test:unit`) in module folder (for example: `packages/metrics`) to run the module specific one +* jest directly `npx jest --group=unit` in module folder to run the module specific one (You can run selective tests by restricting the group to the one you want. For instance `npx jest --group=unit/metrics/class`) + +**e2e tests** + +**Write** + +As mentioned in the previous section, tests are split into groups thanks to [jest-runner-groups](https://www.npmjs.com/package/jest-runner-groups) and therefore each test needs to be tagged properly by adding the following comments in the header of your unit test file: + +```typescript +/** + * Tests data lake catalog + * + * @group e2e// + */ +``` + + See `metrics/tests/e2e/decorator.test.ts` as an example. + + +**Run** + +To run e2e tests you can either use +* npm task `lerna-test:e2e` (`npm run lerna-test:e2e`) in root folder +* npm task `test:e2e` (`npm run test:e2e`) in module folder (for example: `packages/metrics`) to run the module specific one +* jest directly `npx jest --group=e2e` in module folder. (You can run selective tests by restricting the group to the one you want. For instance `npx jest --group=e2e/metrics/decorator`) + +Two important env variable can be used: +* `AWS_PROFILE` to use the right AWS credentials +* `DISABLE_TEARDOWN` if you don't want your stack to be destroyed at the end of the test (useful in dev mode when iterating over your code). + +Example: `DISABLE_TEARDOWN=true AWS_PROFILE=dev-account npx jest --group=e2e/metrics/decorator` + +**Automate** + +You can run the end-to-end tests automatically on your forked project by following these steps: +1. Create an IAM role in your target AWS account, with the least amount of privilege. + + As mentioned above in this page, we are leveraging CDK to deploy and consequently clean-up resources on AWS. Therefore to run those tests through Github actions you will need to grant specific permissions to your workflow. + + We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) for the AWS credentials used in GitHub Actions workflows, including: + * Do not store credentials in your repository's code. + * [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) to the credentials used in GitHub Actions workflows. Grant only the permissions required to perform the actions in your GitHub Actions workflows. + * [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows. + + For an example of how to create a role in CDK, you can look at [@pahud/cdk-github-oidc](https://constructs.dev/packages/@pahud/cdk-github-oidc) construct. + + More information about: + + - [Github OpenID Connect](https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/ + - ["Configure AWS Credentials" Action For GitHub Actions](https://github.com/aws-actions/configure-aws-credentials/) +2. Add your new role into your [Github fork secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) with name `AWS_ROLE_ARN_TO_ASSUME`. +3. In your forked repository, go to the "Actions" tabs, select the `run-e2e-tests` workflow. +4. In the run-e2e-tests workflow page, select "Run workflow" and run it on the desired branch. + +> :warning: **Don't automatically run end-to-end tests on branch push or PRs**. A malicious attacker can submit a pull request to attack your AWS account. Ideally, use a blank account without any important workload/data, and limit `AWS_ROLE_ARN_TO_ASSUME` permission to least minimum privilege. ### Conventions Category | Convention @@ -89,4 +179,4 @@ TODO See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. -We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. \ No newline at end of file +We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/package.json b/package.json index b7e68afdb6..0cad9332d6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "package": "npm run package", "lerna-ci": "lerna exec -- npm ci", "lerna-test": "lerna exec -- npm run test", + "lerna-test:e2e": "lerna exec -- npm run test:e2e", "lerna-package": "lerna exec -- npm run package", "lerna-build": "lerna exec -- tsc", "lerna-lint": "lerna exec -- eslint \"./{src,tests}/**/*.ts ./src/*.ts\"", diff --git a/packages/commons/package.json b/packages/commons/package.json index f7f8630891..53e76542ea 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -11,7 +11,9 @@ }, "scripts": { "commit": "commit", - "test": "jest --detectOpenHandles --coverage --verbose", + "test": "npm run test:unit", + "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", + "test:e2e": "echo 'Not Applicable'", "watch": "jest --watch", "build": "tsc", "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", diff --git a/packages/logger/jest.config.js b/packages/logger/jest.config.js index f71eb10433..8ed8012be5 100644 --- a/packages/logger/jest.config.js +++ b/packages/logger/jest.config.js @@ -3,6 +3,7 @@ module.exports = { name: 'AWS Lambda Powertools utility: LOGGER', color: 'cyan', }, + 'runner': 'groups', 'preset': 'ts-jest', 'transform': { '^.+\\.ts?$': 'ts-jest', diff --git a/packages/logger/package-lock.json b/packages/logger/package-lock.json index 221800f1ab..84633a478f 100644 --- a/packages/logger/package-lock.json +++ b/packages/logger/package-lock.json @@ -29,6 +29,7 @@ "eslint-import-resolver-typescript": "^2.5.0", "eslint-plugin-import": "^2.25.3", "jest": "^27.0.4", + "jest-runner-groups": "^2.1.0", "ts-jest": "^27.0.3", "ts-node": "^10.0.0", "typescript": "^4.1.3" @@ -4234,6 +4235,19 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/jest-runner-groups": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jest-runner-groups/-/jest-runner-groups-2.1.0.tgz", + "integrity": "sha512-iHBIJ38yEW7qkPTW3tSulq/5kjgIiVtZjuYimBT1PltBYwsb1B1gPWGFMDdEfy9O3+6cyfe5MmVgMHafi69MUw==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + }, + "peerDependencies": { + "jest-docblock": ">= 24", + "jest-runner": ">= 24" + } + }, "node_modules/jest-runtime": { "version": "27.4.6", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", @@ -9317,6 +9331,13 @@ "throat": "^6.0.1" } }, + "jest-runner-groups": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jest-runner-groups/-/jest-runner-groups-2.1.0.tgz", + "integrity": "sha512-iHBIJ38yEW7qkPTW3tSulq/5kjgIiVtZjuYimBT1PltBYwsb1B1gPWGFMDdEfy9O3+6cyfe5MmVgMHafi69MUw==", + "dev": true, + "requires": {} + }, "jest-runtime": { "version": "27.4.6", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", diff --git a/packages/logger/package.json b/packages/logger/package.json index 3218316ee9..95ad2b5f19 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -11,7 +11,9 @@ }, "scripts": { "commit": "commit", - "test": "jest --detectOpenHandles --coverage --verbose", + "test": "npm run test:unit", + "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", + "test:e2e": "jest --group=e2e", "watch": "jest --watch", "build": "tsc", "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", @@ -53,6 +55,7 @@ "eslint-import-resolver-typescript": "^2.5.0", "eslint-plugin-import": "^2.25.3", "jest": "^27.0.4", + "jest-runner-groups": "^2.1.0", "ts-jest": "^27.0.3", "ts-node": "^10.0.0", "typescript": "^4.1.3" diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index 8e29686563..93ed91b95c 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -1,3 +1,9 @@ +/** + * Test Logger class + * + * @group unit/logger/all + */ + import { context as dummyContext } from '../../../../tests/resources/contexts/hello-world'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts b/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts index eb10b55ba8..ab810089c8 100644 --- a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts +++ b/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts @@ -1,3 +1,9 @@ +/** + * Test Logger EnvironmentVariablesService class + * + * @group unit/logger/all + */ + import { EnvironmentVariablesService } from '../../../src/config'; describe('Class: EnvironmentVariablesService', () => { diff --git a/packages/logger/tests/unit/formatter/PowertoolLogFormatter.test.ts b/packages/logger/tests/unit/formatter/PowertoolLogFormatter.test.ts index 0f7ae288e3..466699d550 100644 --- a/packages/logger/tests/unit/formatter/PowertoolLogFormatter.test.ts +++ b/packages/logger/tests/unit/formatter/PowertoolLogFormatter.test.ts @@ -1,3 +1,9 @@ +/** + * Test Logger formatter + * + * @group unit/logger/all + */ + import { AssertionError, strictEqual } from 'assert'; import { PowertoolLogFormatter } from '../../../src/formatter'; import { UnformattedAttributes } from '../../../src/types'; diff --git a/packages/logger/tests/unit/helpers.test.ts b/packages/logger/tests/unit/helpers.test.ts index 676568a428..5ef8db8f6a 100644 --- a/packages/logger/tests/unit/helpers.test.ts +++ b/packages/logger/tests/unit/helpers.test.ts @@ -1,3 +1,9 @@ +/** + * Test Logger helpers + * + * @group unit/logger/all + */ + import { ConfigServiceInterface, EnvironmentVariablesService } from '../../src/config'; import { LogFormatter, PowertoolLogFormatter } from '../../src/formatter'; import { LoggerOptions } from '../../src/types'; diff --git a/packages/logger/tests/unit/middleware/middy.test.ts b/packages/logger/tests/unit/middleware/middy.test.ts index 125fcad0e3..4472b45527 100644 --- a/packages/logger/tests/unit/middleware/middy.test.ts +++ b/packages/logger/tests/unit/middleware/middy.test.ts @@ -1,3 +1,9 @@ +/** + * Test Logger middleware + * + * @group unit/logger/all + */ + import { EnvironmentVariablesService } from '../../../src/config'; import { injectLambdaContext } from '../../../src/middleware/middy'; import { Logger } from './../../../src'; diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 89a5765b05..004b78c327 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -11,7 +11,8 @@ }, "scripts": { "commit": "commit", - "test": "jest --group=unit --detectOpenHandles --coverage --verbose", + "test": "npm run test:unit", + "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", "test:e2e": "jest --group=e2e", "watch": "jest --group=unit --watch ", "build": "tsc", diff --git a/packages/metrics/tests/e2e/decorator.test.MyFunction.ts b/packages/metrics/tests/e2e/decorator.test.MyFunction.ts index 61f1720017..1c8fe06209 100644 --- a/packages/metrics/tests/e2e/decorator.test.MyFunction.ts +++ b/packages/metrics/tests/e2e/decorator.test.MyFunction.ts @@ -14,7 +14,7 @@ const singleMetricName = process.env.EXPECTED_SINGLE_METRIC_NAME ?? 'MySingleMet const singleMetricUnit = (process.env.EXPECTED_SINGLE_METRIC_UNIT as MetricUnits) ?? MetricUnits.Percent; const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? '2'; -const metrics = new Metrics({ namespace: namespace, service: serviceName }); +const metrics = new Metrics({ namespace: namespace, serviceName: serviceName }); class Lambda implements LambdaInterface { diff --git a/packages/metrics/tests/e2e/decorator.test.ts b/packages/metrics/tests/e2e/decorator.test.ts index 47394d2981..eca5988752 100644 --- a/packages/metrics/tests/e2e/decorator.test.ts +++ b/packages/metrics/tests/e2e/decorator.test.ts @@ -23,6 +23,7 @@ const integTestApp = new App(); const stack = new Stack(integTestApp, 'MetricsE2EDecoratorStack'); // GIVEN +const invocationCount = 2; const startTime = new Date(); const expectedNamespace = randomUUID(); // to easily find metrics back at assert phase const expectedServiceName = 'decoratorService'; @@ -67,28 +68,29 @@ describe('happy cases', () => { // lambda function is deployed await cloudFormation.deployStack({ stack: stackArtifact, + quiet: true, }); - }, 200000); - it('capture ColdStart Metric', async () => { - // WHEN - // invoked - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); - // twice - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); + // and invoked + for (let i = 0; i < invocationCount; i++) { + await lambdaClient + .invoke({ + FunctionName: functionName, + }) + .promise(); + } // THEN // sleep to allow metrics to be collected - await new Promise((resolve) => setTimeout(resolve, 10000)); + await new Promise((resolve) => setTimeout(resolve, 15000)); + }, 200000); + it('capture ColdStart Metric', async () => { + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: 'function_name', Value: functionName }, + { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, + ]; // Check coldstart metric dimensions const coldStartMetrics = await cloudwatchClient .listMetrics({ @@ -98,24 +100,19 @@ describe('happy cases', () => { .promise(); expect(coldStartMetrics.Metrics?.length).toBe(1); const coldStartMetric = coldStartMetrics.Metrics?.[0]; - expect(coldStartMetric?.Dimensions).toStrictEqual([ - { Name: 'service', Value: expectedServiceName }, - { Name: 'function_name', Value: functionName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - ]); + expect(coldStartMetric?.Dimensions).toStrictEqual(expectedDimensions); // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); + const endTime = new Date(new Date().getTime() + 60 * 1000); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); const coldStartMetricStat = await cloudwatchClient .getMetricStatistics( { Namespace: expectedNamespace, - StartTime: new Date(startTime.getTime() - 60 * 1000), // minus 1 minute, - Dimensions: [ - { Name: 'service', Value: expectedServiceName }, - { Name: 'function_name', Value: functionName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - ], - EndTime: new Date(new Date().getTime() + 60 * 1000), + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, Period: 60, MetricName: 'ColdStart', Statistics: ['Sum'], @@ -126,27 +123,10 @@ describe('happy cases', () => { // Despite lambda has been called twice, coldstart metric sum should only be 1 const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; - expect(singleDataPoint.Sum).toBe(1); + expect(singleDataPoint?.Sum).toBe(1); }, 15000); it('produce added Metric with the default and extra one dimensions', async () => { - // GIVEN - const invocationCount = 2; - - // WHEN - // invoked - for (let i = 0; i < invocationCount; i++) { - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); - } - - // THEN - // sleep to allow metrics to be collected - await new Promise((resolve) => setTimeout(resolve, 10000)); - // Check metric dimensions const metrics = await cloudwatchClient .listMetrics({ @@ -164,6 +144,9 @@ describe('happy cases', () => { expect(metric?.Dimensions).toStrictEqual(expectedDimensions); // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); + const endTime = new Date(new Date().getTime() + 60 * 1000); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); const metricStat = await cloudwatchClient .getMetricStatistics( { @@ -181,7 +164,7 @@ describe('happy cases', () => { // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; - expect(singleDataPoint.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); + expect(singleDataPoint?.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); }, 15000); afterAll(async () => { @@ -195,6 +178,7 @@ describe('happy cases', () => { await cloudFormation.destroyStack({ stack: stackArtifact, + quiet: true, }); } }, 200000); diff --git a/packages/metrics/tests/e2e/standardFunctions.test.ts b/packages/metrics/tests/e2e/standardFunctions.test.ts index ba6cc448db..cf970277f6 100644 --- a/packages/metrics/tests/e2e/standardFunctions.test.ts +++ b/packages/metrics/tests/e2e/standardFunctions.test.ts @@ -11,7 +11,6 @@ import { randomUUID } from 'crypto'; import * as lambda from '@aws-cdk/aws-lambda-nodejs'; import { Tracing } from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { Tracing } from '@aws-cdk/aws-lambda'; import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; import * as AWS from 'aws-sdk'; @@ -24,6 +23,7 @@ const integTestApp = new App(); const stack = new Stack(integTestApp, 'MetricsE2EStandardFunctionsStack'); // GIVEN +const invocationCount = 2; const startTime = new Date(); const expectedNamespace = randomUUID(); // to easily find metrics back at assert phase const expectedServiceName = 'MyFunctionWithStandardHandler'; @@ -68,28 +68,24 @@ describe('happy cases', () => { // lambda function is deployed await cloudFormation.deployStack({ stack: stackArtifact, + quiet: true, }); - }, 200000); - it('capture ColdStart Metric', async () => { - // WHEN - // invoked - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); - // twice - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); + // and invoked + for (let i = 0; i < invocationCount; i++) { + await lambdaClient + .invoke({ + FunctionName: functionName, + }) + .promise(); + } // THEN // sleep to allow metrics to be collected - await new Promise((resolve) => setTimeout(resolve, 10000)); + await new Promise((resolve) => setTimeout(resolve, 15000)); + }, 200000); + it('capture ColdStart Metric', async () => { // Check coldstart metric dimensions const coldStartMetrics = await cloudwatchClient .listMetrics({ @@ -102,13 +98,16 @@ describe('happy cases', () => { expect(coldStartMetric?.Dimensions).toStrictEqual([{ Name: 'service', Value: expectedServiceName }]); // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); + const endTime = new Date(new Date().getTime() + 60 * 1000); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify([{ Name: 'service', Value: expectedServiceName }])}'`); const coldStartMetricStat = await cloudwatchClient .getMetricStatistics( { Namespace: expectedNamespace, - StartTime: new Date(startTime.getTime() - 60 * 1000), // minus 1 minute, + StartTime: adjustedStartTime, Dimensions: [{ Name: 'service', Value: expectedServiceName }], - EndTime: new Date(new Date().getTime() + 60 * 1000), + EndTime: endTime, Period: 60, MetricName: 'ColdStart', Statistics: ['Sum'], @@ -119,27 +118,10 @@ describe('happy cases', () => { // Despite lambda has been called twice, coldstart metric sum should only be 1 const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; - expect(singleDataPoint.Sum).toBe(1); + expect(singleDataPoint?.Sum).toBe(1); }, 15000); it('produce added Metric with the default and extra one dimensions', async () => { - // GIVEN - const invocationCount = 2; - - // WHEN - // invoked - for (let i = 0; i < invocationCount; i++) { - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); - } - - // THEN - // sleep to allow metrics to be collected - await new Promise((resolve) => setTimeout(resolve, 10000)); - // Check metric dimensions const metrics = await cloudwatchClient .listMetrics({ @@ -157,6 +139,9 @@ describe('happy cases', () => { expect(metric?.Dimensions).toStrictEqual(expectedDimensions); // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); + const endTime = new Date(new Date().getTime() + 60 * 1000); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); const metricStat = await cloudwatchClient .getMetricStatistics( { @@ -188,6 +173,7 @@ describe('happy cases', () => { await cloudFormation.destroyStack({ stack: stackArtifact, + quiet: true, }); } }, 200000); diff --git a/packages/tracing/package.json b/packages/tracing/package.json index c1600f94c8..ab4d267d57 100644 --- a/packages/tracing/package.json +++ b/packages/tracing/package.json @@ -11,7 +11,8 @@ }, "scripts": { "commit": "commit", - "test": "jest --group=unit --detectOpenHandles --coverage --verbose", + "test": "npm run test:unit", + "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", "test:e2e": "jest --group=e2e", "watch": "jest --watch", "build": "tsc", diff --git a/packages/tracing/tests/e2e/tracer.test.ts b/packages/tracing/tests/e2e/tracer.test.ts index a5210fded9..89270308f1 100644 --- a/packages/tracing/tests/e2e/tracer.test.ts +++ b/packages/tracing/tests/e2e/tracer.test.ts @@ -17,6 +17,7 @@ import type { ParsedDocument } from '../helpers/tracesUtils'; const xray = new AWS.XRay(); const lambdaClient = new AWS.Lambda(); +const stsClient = new AWS.STS(); describe('Tracer integration tests', () => { @@ -37,15 +38,12 @@ describe('Tracer integration tests', () => { // Prepare integTestApp = new App(); - const account = process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT; - const region = process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION; - stack = new Stack(integTestApp, 'TracerIntegTest', { - env: { - account, - region - } - }); + stack = new Stack(integTestApp, 'TracerIntegTest'); + const identity = await stsClient.getCallerIdentity().promise(); + const account = identity.Account; + const region = process.env.AWS_REGION; + const functions = [ 'Manual', 'Middleware', @@ -92,6 +90,7 @@ describe('Tracer integration tests', () => { const cloudFormation = new CloudFormationDeployments({ sdkProvider }); await cloudFormation.deployStack({ stack: stackArtifact, + quiet: true, }); // Act @@ -125,6 +124,7 @@ describe('Tracer integration tests', () => { await cloudFormation.destroyStack({ stack: stackArtifact, + quiet: true, }); } diff --git a/packages/tracing/tests/helpers/tracesUtils.ts b/packages/tracing/tests/helpers/tracesUtils.ts index 54de198f8c..0633a485c9 100644 --- a/packages/tracing/tests/helpers/tracesUtils.ts +++ b/packages/tracing/tests/helpers/tracesUtils.ts @@ -58,10 +58,12 @@ interface ParsedTrace { } const getTraces = async (xrayClient: XRay, startTime: Date, resourceArn: string, expectedTraces: number): Promise => { + const endTime = new Date(); + console.log(`Manual query: aws xray get-trace-summaries --start-time ${Math.floor(startTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --filter-expression 'resource.arn = "${resourceArn}"'`); const traces = await xrayClient .getTraceSummaries({ StartTime: startTime, - EndTime: new Date(), + EndTime: endTime, FilterExpression: `resource.arn = "${resourceArn}"`, }) .promise();