diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40eabd9c71..301a291d15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,7 @@ You can run each group separately or all together thanks to [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 asserting on the expected behaviors. All steps are also executed by Jest. +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/v2/guide/getting_started.html) v2 library with TypeScript for creating infrastructure, and [aws-sdk-js v2](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/) for invoking the functions and asserting 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/). @@ -133,7 +133,7 @@ To run unit tests, you can either use: ##### Set up -We create e2e testing infrastructure with CDK. If you have never used it before, please check its [Getting started guide](https://docs.aws.amazon.com/cdk/v1/guide/getting_started.html). You need to run `cdk bootstrap` in the account and region you are going to run e2e tests in. +We create e2e testing infrastructure with CDK. If you have never used it before, please check its [Getting started guide](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html). You need to run `cdk bootstrap` in the account and region you are going to run e2e tests in. ##### Write @@ -147,7 +147,9 @@ As mentioned in the previous section, tests are split into groups thanks to [jes */ ``` - See `metrics/tests/e2e/decorator.test.ts` as an example. +Follow this convention for the test filename: `..test.ts`. (e.g. `sampleRate.decorator.test.ts`, `childLogger.manual.test.ts`) + +See `metrics/tests/e2e/basicFeatures.decorator.test.ts` as an example. ##### Run diff --git a/package-lock.json b/package-lock.json index 0394d7b075..85e31f9e3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5344,123 +5344,25 @@ }, "node_modules/cdk-assets/node_modules/@aws-cdk/cloud-assembly-schema": { "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-2.17.0.tgz", - "integrity": "sha512-rX7St1ZcffK4uGPYpelK59eibHBkGS/CkNhuFrkGDb16dEabeX12+6eLUafxrsNR6ijyKUyfFGM+ETz9Khc8aQ==", - "bundleDependencies": [ - "jsonschema", - "semver" - ], "dev": true, "dependencies": { "jsonschema": "^1.4.0", "semver": "^7.3.5" - }, - "engines": { - "node": ">= 14.15.0" - } - }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { - "version": "1.4.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" } }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.3.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/cdk-assets/node_modules/@aws-cdk/cx-api": { "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cx-api/-/cx-api-2.17.0.tgz", - "integrity": "sha512-xiQadv1wScrKgBgDuyJGsJZnTPWNRQsPYjNCZWMAiNDpt1tVd/hQZuVbTipJdGhKZOdAaKOc8f8mvj7mncANUw==", - "bundleDependencies": [ - "semver" - ], "dev": true, "dependencies": { "@aws-cdk/cloud-assembly-schema": "2.17.0", "semver": "^7.3.5" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@aws-cdk/cloud-assembly-schema": "2.17.0" } }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cx-api/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cx-api/node_modules/semver": { - "version": "7.3.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cdk-assets/node_modules/@aws-cdk/cx-api/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/cdk-assets/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "dev": true }, "node_modules/cdk-assets/node_modules/ansi-styles": { "version": "4.3.0", @@ -5469,12 +5371,6 @@ "dev": true, "dependencies": { "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/cdk-assets/node_modules/archiver": { @@ -5490,9 +5386,6 @@ "readdir-glob": "^1.0.0", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" } }, "node_modules/cdk-assets/node_modules/archiver-utils": { @@ -5511,9 +5404,6 @@ "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" } }, "node_modules/cdk-assets/node_modules/archiver-utils/node_modules/readable-stream": { @@ -5552,9 +5442,6 @@ "url": "0.10.3", "uuid": "3.3.2", "xml2js": "0.4.19" - }, - "engines": { - "node": ">= 10.0.0" } }, "node_modules/cdk-assets/node_modules/aws-sdk/node_modules/buffer": { @@ -5572,21 +5459,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, "node_modules/cdk-assets/node_modules/aws-sdk/node_modules/ieee754": { "version": "1.1.13", @@ -5604,21 +5477,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, "node_modules/cdk-assets/node_modules/bl": { "version": "4.1.0", @@ -5646,20 +5505,6 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -5669,10 +5514,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "engines": { - "node": "*" - } + "dev": true }, "node_modules/cdk-assets/node_modules/cliui": { "version": "7.0.4", @@ -5692,9 +5534,6 @@ "dev": true, "dependencies": { "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" } }, "node_modules/cdk-assets/node_modules/color-name": { @@ -5713,9 +5552,6 @@ "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" } }, "node_modules/cdk-assets/node_modules/concat-map": { @@ -5738,12 +5574,6 @@ "dependencies": { "exit-on-epipe": "~1.0.1", "printj": "~1.3.1" - }, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" } }, "node_modules/cdk-assets/node_modules/crc32-stream": { @@ -5754,9 +5584,6 @@ "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" } }, "node_modules/cdk-assets/node_modules/emoji-regex": { @@ -5778,28 +5605,19 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } + "dev": true }, "node_modules/cdk-assets/node_modules/events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } + "dev": true }, "node_modules/cdk-assets/node_modules/exit-on-epipe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692", "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "dev": true, - "engines": { - "node": ">=0.8" - } + "dev": true }, "node_modules/cdk-assets/node_modules/fs-constants": { "version": "1.0.0", @@ -5817,10 +5635,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } + "dev": true }, "node_modules/cdk-assets/node_modules/glob": { "version": "7.2.0", @@ -5834,12 +5649,6 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cdk-assets/node_modules/graceful-fs": { @@ -5852,21 +5661,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, "node_modules/cdk-assets/node_modules/inflight": { "version": "1.0.6", @@ -5888,10 +5683,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } + "dev": true }, "node_modules/cdk-assets/node_modules/isarray": { "version": "1.0.0", @@ -5903,10 +5695,13 @@ "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076", "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } + "dev": true + }, + "node_modules/cdk-assets/node_modules/jsonschema": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2", + "integrity": "sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==", + "dev": true }, "node_modules/cdk-assets/node_modules/lazystream": { "version": "1.0.1", @@ -5915,9 +5710,6 @@ "dev": true, "dependencies": { "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" } }, "node_modules/cdk-assets/node_modules/lazystream/node_modules/readable-stream": { @@ -5965,17 +5757,20 @@ "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", "dev": true }, + "node_modules/cdk-assets/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + } + }, "node_modules/cdk-assets/node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } + "dev": true }, "node_modules/cdk-assets/node_modules/minimatch": { "version": "3.1.2", @@ -5984,19 +5779,13 @@ "dev": true, "dependencies": { "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" } }, "node_modules/cdk-assets/node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, "node_modules/cdk-assets/node_modules/once": { "version": "1.4.0", @@ -6011,22 +5800,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, "node_modules/cdk-assets/node_modules/printj": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz#9af6b1d55647a1587ac44f4c1654a4b95b8e12cb", "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", - "dev": true, - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } + "dev": true }, "node_modules/cdk-assets/node_modules/process-nextick-args": { "version": "2.0.1", @@ -6044,11 +5824,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "dev": true, - "engines": { - "node": ">=0.4.x" - } + "dev": true }, "node_modules/cdk-assets/node_modules/readable-stream": { "version": "3.6.0", @@ -6059,30 +5835,13 @@ "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/cdk-assets/node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, "node_modules/cdk-assets/node_modules/readable-stream/node_modules/string_decoder": { "version": "1.3.0", @@ -6106,10 +5865,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, "node_modules/cdk-assets/node_modules/safe-buffer": { "version": "5.1.2", @@ -6123,6 +5879,15 @@ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", "dev": true }, + "node_modules/cdk-assets/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + } + }, "node_modules/cdk-assets/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8", @@ -6141,9 +5906,6 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" } }, "node_modules/cdk-assets/node_modules/strip-ansi": { @@ -6153,9 +5915,6 @@ "dev": true, "dependencies": { "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" } }, "node_modules/cdk-assets/node_modules/tar-stream": { @@ -6169,9 +5928,6 @@ "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" } }, "node_modules/cdk-assets/node_modules/url": { @@ -6194,11 +5950,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } + "dev": true }, "node_modules/cdk-assets/node_modules/wrap-ansi": { "version": "7.0.0", @@ -6209,12 +5961,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/cdk-assets/node_modules/wrappy": { @@ -6243,19 +5989,19 @@ "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", - "dev": true, - "engines": { - "node": ">=4.0" - } + "dev": true }, "node_modules/cdk-assets/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } + "dev": true + }, + "node_modules/cdk-assets/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/cdk-assets/node_modules/yargs": { "version": "16.2.0", @@ -6270,19 +6016,13 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" } }, "node_modules/cdk-assets/node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } + "dev": true }, "node_modules/cdk-assets/node_modules/zip-stream": { "version": "4.1.0", @@ -6293,9 +6033,6 @@ "archiver-utils": "^2.1.0", "compress-commons": "^4.1.0", "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" } }, "node_modules/chalk": { @@ -15697,10 +15434,12 @@ } }, "packages/commons": { + "name": "@aws-lambda-powertools/commons", "version": "0.7.1", "license": "MIT-0" }, "packages/logger": { + "name": "@aws-lambda-powertools/logger", "version": "0.7.1", "license": "MIT", "dependencies": { @@ -15717,6 +15456,7 @@ } }, "packages/metrics": { + "name": "@aws-lambda-powertools/metrics", "version": "0.7.1", "license": "MIT-0", "dependencies": { @@ -15728,6 +15468,7 @@ } }, "packages/tracing": { + "name": "@aws-lambda-powertools/tracer", "version": "0.7.1", "license": "MIT-0", "dependencies": { @@ -19995,73 +19736,18 @@ "dependencies": { "@aws-cdk/cloud-assembly-schema": { "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-2.17.0.tgz", - "integrity": "sha512-rX7St1ZcffK4uGPYpelK59eibHBkGS/CkNhuFrkGDb16dEabeX12+6eLUafxrsNR6ijyKUyfFGM+ETz9Khc8aQ==", "dev": true, "requires": { "jsonschema": "^1.4.0", "semver": "^7.3.5" - }, - "dependencies": { - "jsonschema": { - "version": "1.4.0", - "bundled": true, - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "bundled": true, - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.5", - "bundled": true, - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "bundled": true, - "dev": true - } } }, "@aws-cdk/cx-api": { "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cx-api/-/cx-api-2.17.0.tgz", - "integrity": "sha512-xiQadv1wScrKgBgDuyJGsJZnTPWNRQsPYjNCZWMAiNDpt1tVd/hQZuVbTipJdGhKZOdAaKOc8f8mvj7mncANUw==", "dev": true, "requires": { "@aws-cdk/cloud-assembly-schema": "2.17.0", "semver": "^7.3.5" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "bundled": true, - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.5", - "bundled": true, - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "bundled": true, - "dev": true - } } }, "ansi-regex": { @@ -20409,6 +20095,12 @@ "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", "dev": true }, + "jsonschema": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2", + "integrity": "sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==", + "dev": true + }, "lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638", @@ -20465,6 +20157,15 @@ "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367", @@ -20580,6 +20281,15 @@ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", "dev": true }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8", @@ -20691,6 +20401,12 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66", diff --git a/packages/commons/tests/unit/InvocationLogs.test.ts b/packages/commons/tests/unit/InvocationLogs.test.ts new file mode 100644 index 0000000000..1ef5e33cc2 --- /dev/null +++ b/packages/commons/tests/unit/InvocationLogs.test.ts @@ -0,0 +1,138 @@ +/** + * Test InvocationLogs class + * + * @group unit/commons/invocationLogs + * + */ + +import { InvocationLogs, LEVEL } from '../utils/InvocationLogs'; + +const exampleLogs = `START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"} +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is a second INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tERROR\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}} +END RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 +REPORT RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678\tDuration: 2.16 ms\tBilled Duration: 3 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\t`; + +describe('Constructor', () => { + test('it should parse base64 text correctly', () => { + const invocationLogs = new InvocationLogs(Buffer.from(exampleLogs).toString('base64')); + expect(invocationLogs.getFunctionLogs(LEVEL.DEBUG).length).toBe(1); + expect(invocationLogs.getFunctionLogs(LEVEL.INFO).length).toBe(2); + expect(invocationLogs.getFunctionLogs(LEVEL.ERROR).length).toBe(1); + }); + +}); + +describe('doesAnyFunctionLogsContains()', () => { + let invocationLogs: InvocationLogs; + + beforeEach(() => { + invocationLogs = new InvocationLogs(Buffer.from(exampleLogs).toString('base64')); + }); + test('it should return true if the text appear in any logs', () => { + const phraseInMessage = 'This is'; + expect(invocationLogs.doesAnyFunctionLogsContains(phraseInMessage)).toBe(true); + + }); + test('it should return false if the text does not appear in any logs', () => { + const phraseNotInMessage = 'A quick brown fox jumps over the lazy dog'; + expect(invocationLogs.doesAnyFunctionLogsContains(phraseNotInMessage)).toBe(false); + }); + + test('it should return true for key in the log', () => { + const keyInLog = 'error'; + expect(invocationLogs.doesAnyFunctionLogsContains(keyInLog)).toBe(true); + }); + + test('it should return true for a text in an error key', () => { + const textInError = '/var/task/index.js:2778'; + expect(invocationLogs.doesAnyFunctionLogsContains(textInError)).toBe(true); + }); + test('it should return false for the text that appears only on the ', () => { + const textInStartLine = 'Version: $LATEST'; + const textInEndLine = 'END RequestId'; + const textInReportLine = 'Billed Duration'; + expect(invocationLogs.doesAnyFunctionLogsContains(textInStartLine)).toBe(false); + expect(invocationLogs.doesAnyFunctionLogsContains(textInEndLine)).toBe(false); + expect(invocationLogs.doesAnyFunctionLogsContains(textInReportLine)).toBe(false); + }); + + test('it should apply filter log based on the given level', () => { + const debugLogHasWordINFO = invocationLogs.doesAnyFunctionLogsContains('INFO', LEVEL.DEBUG); + expect(debugLogHasWordINFO).toBe(true); + + const infoLogHasWordINFO = invocationLogs.doesAnyFunctionLogsContains('INFO', LEVEL.INFO); + expect(infoLogHasWordINFO).toBe(true); + + const errorLogHasWordINFO = invocationLogs.doesAnyFunctionLogsContains('INFO', LEVEL.ERROR); + expect(errorLogHasWordINFO).toBe(false); + + }); +}); + +describe('getFunctionLogs()', () => { + let invocationLogs: InvocationLogs; + + beforeEach(() => { + invocationLogs = new InvocationLogs(Buffer.from(exampleLogs).toString('base64')); + }); + + test('it should retrive logs of the given level only', () => { + const infoLogs = invocationLogs.getFunctionLogs(LEVEL.INFO); + expect(infoLogs.length).toBe(2); + expect(infoLogs[0].includes('INFO')).toBe(true); + expect(infoLogs[1].includes('INFO')).toBe(true); + expect(infoLogs[0].includes('ERROR')).toBe(false); + expect(infoLogs[1].includes('ERROR')).toBe(false); + + const errorLogs = invocationLogs.getFunctionLogs(LEVEL.ERROR); + expect(errorLogs.length).toBe(1); + expect(errorLogs[0].includes('INFO')).toBe(false); + expect(errorLogs[0].includes('ERROR')).toBe(true); + }); + + test('it should NOT return logs generated by Lambda service (e.g. START, END, and REPORT)', () => { + const errorLogs = invocationLogs.getFunctionLogs(LEVEL.ERROR); + expect(errorLogs.length).toBe(1); + expect(errorLogs[0].includes('START')).toBe(false); + expect(errorLogs[0].includes('END')).toBe(false); + expect(errorLogs[0].includes('REPORT')).toBe(false); + }); +}); + +describe('parseFunctionLog()', () => { + test('it should return object with the correct values based on the given log', () => { + const rawLogStr = '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}'; + + const logObj = InvocationLogs.parseFunctionLog(rawLogStr); + expect(logObj.timestamp).toBe('2022-01-27T16:04:39.323Z'); + expect(logObj.invocationId).toBe('c6af9ac6-7b61-11e6-9a41-93e812345678'); + expect(logObj.logLevel).toBe(LEVEL.DEBUG); + expect(logObj.logObject).toStrictEqual({ + cold_start: true, + function_arn: 'arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c', + function_memory_size: 128, + function_name: 'loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c', + function_request_id: '7f586697-238a-4c3b-9250-a5f057c1119c', + level: 'DEBUG', + message: 'This is a DEBUG log but contains the word INFO some context and persistent key', + service: 'logger-e2e-testing', + timestamp: '2022-01-27T16:04:39.323Z', + persistentKey: 'works', + }); + }); + + test('it should throw an error if receive incorrect formatted raw log string', () => { + const noTabString = 'no-tab-character'; + expect(() => { + InvocationLogs.parseFunctionLog(noTabString); + }).toThrow(Error); + + const missSomeElements = '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t'; + expect(() => { + InvocationLogs.parseFunctionLog(missSomeElements); + }).toThrow(Error); + }); +}); \ No newline at end of file diff --git a/packages/logger/tests/helpers/InvocationLogs.ts b/packages/commons/tests/utils/InvocationLogs.ts similarity index 91% rename from packages/logger/tests/helpers/InvocationLogs.ts rename to packages/commons/tests/utils/InvocationLogs.ts index fab917ca8d..f220581090 100644 --- a/packages/logger/tests/helpers/InvocationLogs.ts +++ b/packages/commons/tests/utils/InvocationLogs.ts @@ -41,20 +41,20 @@ export class InvocationLogs { } /** - * Find all functional logs whether it contains a given key - * @param key + * Find all functional logs whether it contains a given text + * @param text * @param log level to filter * @returns */ - public doesAnyFunctionLogsContains(key: string, levelToFilter?: LEVEL): boolean { + public doesAnyFunctionLogsContains(text: string, levelToFilter?: LEVEL): boolean { const filteredLogs = this.getFunctionLogs(levelToFilter) - .filter(log => log.includes(key)); + .filter(log => log.includes(text)); return filteredLogs.length > 0; } /** - * Return only logs from function, excude START, END, and REPORT generated by Lambda service + * Return only logs from function, exclude START, END, and REPORT generated by Lambda service * @param log level to filter * @returns Array of function logs */ @@ -62,7 +62,7 @@ export class InvocationLogs { let filteredLogs = this.logs.slice(1, -2); if (levelToFilter) { - filteredLogs = filteredLogs.filter(log => log.includes(levelToFilter.toString())); + filteredLogs = filteredLogs.filter(log => log.split('\t')[2] == levelToFilter); } return filteredLogs; diff --git a/packages/logger/tests/helpers/e2eUtils.ts b/packages/commons/tests/utils/e2eUtils.ts similarity index 81% rename from packages/logger/tests/helpers/e2eUtils.ts rename to packages/commons/tests/utils/e2eUtils.ts index c5528e078c..00143f4788 100644 --- a/packages/logger/tests/helpers/e2eUtils.ts +++ b/packages/commons/tests/utils/e2eUtils.ts @@ -1,16 +1,19 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 +/** + * E2E utils is used by e2e tests. They are helper function that calls either CDK or SDK + * to interact with services. +*/ import { App, CfnOutput, Stack } from 'aws-cdk-lib'; import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs'; -import { Runtime } from 'aws-cdk-lib/aws-lambda'; +import { Runtime, Tracing } from 'aws-cdk-lib/aws-lambda'; import * as AWS from 'aws-sdk'; import { InvocationLogs } from './InvocationLogs'; const lambdaClient = new AWS.Lambda(); -const NAME_PREFIX = 'Logger-E2E'; const testRuntimeKeys = [ 'nodejs12x', 'nodejs14x' ]; export type TestRuntimesKey = typeof testRuntimeKeys[number]; const TEST_RUNTIMES: Record = { @@ -23,8 +26,9 @@ export type StackWithLambdaFunctionOptions = { stackName: string functionName: string functionEntry: string + tracing?: Tracing environment: {[key: string]: string} - logGroupOutputKey: string + logGroupOutputKey?: string runtime: string }; @@ -36,19 +40,22 @@ export const createStackWithLambdaFunction = (params: StackWithLambdaFunctionOpt const testFunction = new lambda.NodejsFunction(stack, `testFunction`, { functionName: params.functionName, entry: params.functionEntry, + tracing: params.tracing, environment: params.environment, runtime: TEST_RUNTIMES[params.runtime as TestRuntimesKey], }); - new CfnOutput(stack, params.logGroupOutputKey, { - value: testFunction.logGroup.logGroupName, - }); + if (params.logGroupOutputKey) { + new CfnOutput(stack, params.logGroupOutputKey, { + value: testFunction.logGroup.logGroupName, + }); + } return stack; }; -export const generateUniqueName = (uuid: string, runtime: string, testName: string): string => - `${NAME_PREFIX}-${runtime}-${testName}-${uuid}`.substring(0, 64); +export const generateUniqueName = (name_prefix: string, uuid: string, runtime: string, testName: string): string => + `${name_prefix}-${runtime}-${testName}-${uuid}`.substring(0, 64); export const invokeFunction = async (functionName: string, times: number = 1, invocationMode: 'PARALLEL' | 'SEQUENTIAL' = 'PARALLEL'): Promise => { const invocationLogs: InvocationLogs[] = []; diff --git a/packages/logger/package.json b/packages/logger/package.json index ca81589b4f..9974c7f602 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -13,8 +13,8 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e/logger", - "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e/logger", + "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", "test:e2e": "concurrently \"npm:test:e2e:nodejs12x\" \"npm:test:e2e:nodejs14x\"", "watch": "jest --watch --group=unit", "build": "tsc", diff --git a/packages/logger/tests/e2e/basicFeatures.middy.test.ts b/packages/logger/tests/e2e/basicFeatures.middy.test.ts index 48ea8aed7a..bbf31c228b 100644 --- a/packages/logger/tests/e2e/basicFeatures.middy.test.ts +++ b/packages/logger/tests/e2e/basicFeatures.middy.test.ts @@ -10,9 +10,21 @@ import path from 'path'; import { randomUUID } from 'crypto'; import { App, Stack } from 'aws-cdk-lib'; -import { createStackWithLambdaFunction, generateUniqueName, invokeFunction, isValidRuntimeKey } from '../helpers/e2eUtils'; +import { + createStackWithLambdaFunction, + generateUniqueName, + invokeFunction, + isValidRuntimeKey +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; -import { InvocationLogs } from '../helpers/InvocationLogs'; +import { + RESOURCE_NAME_PREFIX, + STACK_OUTPUT_LOG_GROUP, + SETUP_TIMEOUT, + TEST_CASE_TIMEOUT, + TEARDOWN_TIMEOUT +} from './constants'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; @@ -21,14 +33,10 @@ if (!isValidRuntimeKey(runtime)) { } const LEVEL = InvocationLogs.LEVEL; -const TEST_CASE_TIMEOUT = 20000; // 20 seconds -const SETUP_TIMEOUT = 300000; // 300 seconds -const TEARDOWN_TIMEOUT = 200000; -const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; const uuid = randomUUID(); -const stackName = generateUniqueName(uuid, runtime, 'BasicFeatures-Middy'); -const functionName = generateUniqueName(uuid, runtime, 'BasicFeatures-Middy'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'BasicFeatures-Middy'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'BasicFeatures-Middy'); const lambdaFunctionCodeFile = 'basicFeatures.middy.test.FunctionCode.ts'; // Text to be used by Logger in the Lambda function diff --git a/packages/logger/tests/e2e/childLogger.manual.test.ts b/packages/logger/tests/e2e/childLogger.manual.test.ts index fe23332aea..8f821663c6 100644 --- a/packages/logger/tests/e2e/childLogger.manual.test.ts +++ b/packages/logger/tests/e2e/childLogger.manual.test.ts @@ -10,10 +10,22 @@ import path from 'path'; import { randomUUID } from 'crypto'; import { App, Stack } from 'aws-cdk-lib'; -import { createStackWithLambdaFunction, generateUniqueName, invokeFunction, isValidRuntimeKey } from '../helpers/e2eUtils'; +import { + createStackWithLambdaFunction, + generateUniqueName, + invokeFunction, + isValidRuntimeKey +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; -import { InvocationLogs } from '../helpers/InvocationLogs'; - +import { + RESOURCE_NAME_PREFIX, + STACK_OUTPUT_LOG_GROUP, + SETUP_TIMEOUT, + TEST_CASE_TIMEOUT, + TEARDOWN_TIMEOUT +} from './constants'; + const runtime: string = process.env.RUNTIME || 'nodejs14x'; if (!isValidRuntimeKey(runtime)) { @@ -21,14 +33,10 @@ if (!isValidRuntimeKey(runtime)) { } const LEVEL = InvocationLogs.LEVEL; -const TEST_CASE_TIMEOUT = 20000; // 20 seconds -const SETUP_TIMEOUT = 300000; // 300 seconds -const TEARDOWN_TIMEOUT = 200000; -const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; const uuid = randomUUID(); -const stackName = generateUniqueName(uuid, runtime, 'ChildLogger-Manual'); -const functionName = generateUniqueName(uuid, runtime, 'ChildLogger-Manual'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'ChildLogger-Manual'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'ChildLogger-Manual'); const lambdaFunctionCodeFile = 'childLogger.manual.test.FunctionCode.ts'; // Parameters to be used by Logger in the Lambda function diff --git a/packages/logger/tests/e2e/constants.ts b/packages/logger/tests/e2e/constants.ts new file mode 100644 index 0000000000..d99be1f441 --- /dev/null +++ b/packages/logger/tests/e2e/constants.ts @@ -0,0 +1,5 @@ +export const RESOURCE_NAME_PREFIX = 'Logger-E2E'; +export const TEST_CASE_TIMEOUT = 20_000; // 20 seconds +export const SETUP_TIMEOUT = 300_000; // 300 seconds +export const TEARDOWN_TIMEOUT = 200_000; +export const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; \ No newline at end of file diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index 068208bed2..ac0b2e83dc 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -10,9 +10,21 @@ import path from 'path'; import { randomUUID } from 'crypto'; import { App, Stack } from 'aws-cdk-lib'; -import { createStackWithLambdaFunction, generateUniqueName, invokeFunction, isValidRuntimeKey } from '../helpers/e2eUtils'; +import { + createStackWithLambdaFunction, + generateUniqueName, + invokeFunction, + isValidRuntimeKey +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; -import { InvocationLogs } from '../helpers/InvocationLogs'; +import { + RESOURCE_NAME_PREFIX, + STACK_OUTPUT_LOG_GROUP, + SETUP_TIMEOUT, + TEST_CASE_TIMEOUT, + TEARDOWN_TIMEOUT +} from './constants'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; @@ -21,23 +33,21 @@ if (!isValidRuntimeKey(runtime)) { } const LEVEL = InvocationLogs.LEVEL; -const TEST_CASE_TIMEOUT = 30000; // 30 seconds -const SETUP_TIMEOUT = 300000; // 300 seconds -const TEARDOWN_TIMEOUT = 200000; -const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; const uuid = randomUUID(); -const stackName = generateUniqueName(uuid, runtime, 'SampleRate-Decorator'); -const functionName = generateUniqueName(uuid, runtime, 'SampleRate-Decorator'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'SampleRate-Decorator'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'SampleRate-Decorator'); const lambdaFunctionCodeFile = 'sampleRate.decorator.test.FunctionCode.ts'; // Parameters to be used by Logger in the Lambda function const LOG_MSG = `Log message ${uuid}`; const SAMPLE_RATE = '0.5'; const LOG_LEVEL = LEVEL.ERROR.toString(); + const integTestApp = new App(); -let logGroupName: string; // We do not know it until deployment let stack: Stack; +let logGroupName: string; // We do not know the exact name until deployment + describe(`logger E2E tests sample rate and injectLambdaContext() for runtime: ${runtime}`, () => { let invocationLogs: InvocationLogs[]; diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 68e5fcd93a..703ce5b5b4 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -13,7 +13,9 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e": "jest --group=e2e", + "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", + "test:e2e": "concurrently \"npm:test:e2e:nodejs12x\" \"npm:test:e2e:nodejs14x\"", "watch": "jest --group=unit --watch ", "build": "tsc", "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", diff --git a/packages/metrics/tests/e2e/decorator.test.MyFunction.ts b/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts similarity index 100% rename from packages/metrics/tests/e2e/decorator.test.MyFunction.ts rename to packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts diff --git a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts new file mode 100644 index 0000000000..c23bb78086 --- /dev/null +++ b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts @@ -0,0 +1,182 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +/** + * Test metrics standard functions + * + * @group e2e/metrics/decorator + */ + +import path from 'path'; +import { randomUUID } from 'crypto'; +import { Tracing } from 'aws-cdk-lib/aws-lambda'; +import { App, Stack } from 'aws-cdk-lib'; +import * as AWS from 'aws-sdk'; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, +} from '../../../commons/tests/utils/e2eUtils'; +import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { MetricUnits } from '../../src'; +import { + ONE_MINUTE, + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT +} from './constants'; +import { getMetrics } from '../helpers/metricsUtils'; + +const runtime: string = process.env.RUNTIME || 'nodejs14x'; + +if (!isValidRuntimeKey(runtime)) { + throw new Error(`Invalid runtime key value: ${runtime}`); +} + +const uuid = randomUUID(); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); +const lambdaFunctionCodeFile = 'basicFeatures.decorator.test.functionCode.ts'; + +const cloudwatchClient = new AWS.CloudWatch(); + +const invocationCount = 2; +const startTime = new Date(); + +// Parameters to be used by Metrics in the Lambda function +const expectedNamespace = uuid; // to easily find metrics back at assert phase +const expectedServiceName = 'e2eDecorator'; +const expectedMetricName = 'MyMetric'; +const expectedMetricUnit = MetricUnits.Count; +const expectedMetricValue = '1'; +const expectedDefaultDimensions = { MyDimension: 'MyValue' }; +const expectedExtraDimension = { MyExtraDimension: 'MyExtraValue' }; +const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' }; +const expectedSingleMetricName = 'MySingleMetric'; +const expectedSingleMetricUnit = MetricUnits.Percent; +const expectedSingleMetricValue = '2'; + +const integTestApp = new App(); +let stack: Stack; + +describe(`metrics E2E tests (decorator) for runtime: ${runtime}`, () => { + beforeAll(async () => { + // GIVEN a stack + stack = createStackWithLambdaFunction({ + app: integTestApp, + stackName: stackName, + functionName: functionName, + functionEntry: path.join(__dirname, lambdaFunctionCodeFile), + tracing: Tracing.ACTIVE, + environment: { + POWERTOOLS_SERVICE_NAME: 'metrics-e2e-testing', + UUID: uuid, + + // Parameter(s) to be used by Metrics in the Lambda function + EXPECTED_NAMESPACE: expectedNamespace, + EXPECTED_SERVICE_NAME: expectedServiceName, + EXPECTED_METRIC_NAME: expectedMetricName, + EXPECTED_METRIC_UNIT: expectedMetricUnit, + EXPECTED_METRIC_VALUE: expectedMetricValue, + EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), + EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), + EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), + EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, + EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, + EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, + }, + runtime: runtime, + }); + + await deployStack(integTestApp, stack); + + // and invoked + await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); + + }, SETUP_TIMEOUT); + describe('ColdStart metrics', () => { + it('should 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 getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); + + expect(coldStartMetrics.Metrics?.length).toBe(1); + const coldStartMetric = coldStartMetrics.Metrics?.[0]; + expect(coldStartMetric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + 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: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: 'ColdStart', + Statistics: ['Sum'], + }, + undefined + ) + .promise(); + + // 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); + }, TEST_CASE_TIMEOUT); + }); + + describe('Default and extra dimensions', () => { + + it('should produce a Metric with the default and extra one dimensions', async () => { + // Check metric dimensions + const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); + + expect(metrics.Metrics?.length).toBe(1); + const metric = metrics.Metrics?.[0]; + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, + { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, + ]; + expect(metric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + 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( + { + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: expectedMetricName, + Statistics: ['Sum'], + }, + undefined + ) + .promise(); + + // 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); + }, TEST_CASE_TIMEOUT); + }); + afterAll(async () => { + if (!process.env.DISABLE_TEARDOWN) { + await destroyStack(integTestApp, stack); + } + }, TEARDOWN_TIMEOUT); +}); diff --git a/packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts similarity index 100% rename from packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts rename to packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts diff --git a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts new file mode 100644 index 0000000000..56548fac78 --- /dev/null +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts @@ -0,0 +1,176 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +/** + * Test metrics standard functions + * + * @group e2e/metrics/standardFunctions + */ + +import path from 'path'; +import { randomUUID } from 'crypto'; +import { Tracing } from 'aws-cdk-lib/aws-lambda'; +import { App, Stack } from 'aws-cdk-lib'; +import * as AWS from 'aws-sdk'; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, +} from '../../../commons/tests/utils/e2eUtils'; +import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { MetricUnits } from '../../src'; +import { + ONE_MINUTE, + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT +} from './constants'; +import { getMetrics } from '../helpers/metricsUtils'; + +const runtime: string = process.env.RUNTIME || 'nodejs14x'; + +if (!isValidRuntimeKey(runtime)) { + throw new Error(`Invalid runtime key value: ${runtime}`); +} + +const uuid = randomUUID(); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); +const lambdaFunctionCodeFile = 'basicFeatures.manual.test.functionCode.ts'; + +const cloudwatchClient = new AWS.CloudWatch(); + +const invocationCount = 2; +const startTime = new Date(); + +// Parameters to be used by Metrics in the Lambda function +const expectedNamespace = uuid; // to easily find metrics back at assert phase +const expectedServiceName = 'e2eManual'; +const expectedMetricName = 'MyMetric'; +const expectedMetricUnit = MetricUnits.Count; +const expectedMetricValue = '1'; +const expectedDefaultDimensions = { MyDimension: 'MyValue' }; +const expectedExtraDimension = { MyExtraDimension: 'MyExtraValue' }; +const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' }; +const expectedSingleMetricName = 'MySingleMetric'; +const expectedSingleMetricUnit = MetricUnits.Percent; +const expectedSingleMetricValue = '2'; + +const integTestApp = new App(); +let stack: Stack; + +describe(`metrics E2E tests (manual) for runtime: ${runtime}`, () => { + beforeAll(async () => { + // GIVEN a stack + stack = createStackWithLambdaFunction({ + app: integTestApp, + stackName: stackName, + functionName: functionName, + functionEntry: path.join(__dirname, lambdaFunctionCodeFile), + tracing: Tracing.ACTIVE, + environment: { + POWERTOOLS_SERVICE_NAME: 'metrics-e2e-testing', + UUID: uuid, + + // Parameter(s) to be used by Metrics in the Lambda function + EXPECTED_NAMESPACE: expectedNamespace, + EXPECTED_SERVICE_NAME: expectedServiceName, + EXPECTED_METRIC_NAME: expectedMetricName, + EXPECTED_METRIC_UNIT: expectedMetricUnit, + EXPECTED_METRIC_VALUE: expectedMetricValue, + EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), + EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), + EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), + EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, + EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, + EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, + }, + runtime: runtime, + }); + await deployStack(integTestApp, stack); + + // and invoked + await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); + + }, SETUP_TIMEOUT); + + describe('ColdStart metrics', () => { + it('should capture ColdStart Metric', async () => { + // Check coldstart metric dimensions + const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); + expect(coldStartMetrics.Metrics?.length).toBe(1); + const coldStartMetric = coldStartMetrics.Metrics?.[0]; + 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: adjustedStartTime, + Dimensions: [{ Name: 'service', Value: expectedServiceName }], + EndTime: endTime, + Period: 60, + MetricName: 'ColdStart', + Statistics: ['Sum'], + }, + undefined + ) + .promise(); + + // 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); + }, TEST_CASE_TIMEOUT); + }); + + describe('Default and extra dimensions', () => { + it('should produce a Metric with the default and extra one dimensions', async () => { + // Check metric dimensions + const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); + + expect(metrics.Metrics?.length).toBe(1); + const metric = metrics.Metrics?.[0]; + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, + { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, + ]; + expect(metric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + 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( + { + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: expectedMetricName, + Statistics: ['Sum'], + }, + undefined + ) + .promise(); + + // 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); + }, TEST_CASE_TIMEOUT); + }); + + afterAll(async () => { + if (!process.env.DISABLE_TEARDOWN) { + await destroyStack(integTestApp, stack); + } + }, TEARDOWN_TIMEOUT); +}); diff --git a/packages/metrics/tests/e2e/constants.ts b/packages/metrics/tests/e2e/constants.ts new file mode 100644 index 0000000000..c89fdfad20 --- /dev/null +++ b/packages/metrics/tests/e2e/constants.ts @@ -0,0 +1,5 @@ +export const RESOURCE_NAME_PREFIX = 'Metrics-E2E'; +export const ONE_MINUTE = 60 * 10_00; +export const TEST_CASE_TIMEOUT = 90_000; // 90 seconds +export const SETUP_TIMEOUT = 300_000; // 300 seconds +export const TEARDOWN_TIMEOUT = 200_000; \ No newline at end of file diff --git a/packages/metrics/tests/e2e/decorator.test.ts b/packages/metrics/tests/e2e/decorator.test.ts deleted file mode 100644 index e6fb53e2a5..0000000000 --- a/packages/metrics/tests/e2e/decorator.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -/** - * Test metrics standard functions - * - * @group e2e/metrics/decorator - */ - -import { randomUUID } from 'crypto'; -import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs'; -import { Tracing } from 'aws-cdk-lib/aws-lambda'; -import { App, Stack } from 'aws-cdk-lib'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; -import * as AWS from 'aws-sdk'; -import { MetricUnits } from '../../src'; -import { getMetrics } from '../helpers/metricsUtils'; - -const ONE_MINUTE = 1000 * 60; - -const cloudwatchClient = new AWS.CloudWatch(); -const lambdaClient = new AWS.Lambda(); - -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'; -const expectedMetricName = 'MyMetric'; -const expectedMetricUnit = MetricUnits.Count; -const expectedMetricValue = '1'; -const expectedDefaultDimensions = { MyDimension: 'MyValue' }; -const expectedExtraDimension = { MyExtraDimension: 'MyExtraValue' }; -const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' }; -const expectedSingleMetricName = 'MySingleMetric'; -const expectedSingleMetricUnit = MetricUnits.Percent; -const expectedSingleMetricValue = '2'; -const functionName = 'MyFunctionWithDecoratedHandler'; -new lambda.NodejsFunction(stack, 'MyFunction', { - functionName: functionName, - tracing: Tracing.ACTIVE, - environment: { - EXPECTED_NAMESPACE: expectedNamespace, - EXPECTED_SERVICE_NAME: expectedServiceName, - EXPECTED_METRIC_NAME: expectedMetricName, - EXPECTED_METRIC_UNIT: expectedMetricUnit, - EXPECTED_METRIC_VALUE: expectedMetricValue, - EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), - EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), - EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), - EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, - EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, - EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, - }, -}); - -describe('happy cases', () => { - beforeAll(async () => { - await deployStack(integTestApp, stack); - - // and invoked - for (let i = 0; i < invocationCount; i++) { - await lambdaClient - .invoke({ - FunctionName: functionName, - }) - .promise(); - } - - }, ONE_MINUTE * 3); - - 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 getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - - expect(coldStartMetrics.Metrics?.length).toBe(1); - const coldStartMetric = coldStartMetrics.Metrics?.[0]; - 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: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: 'ColdStart', - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // 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); - }, ONE_MINUTE * 3); - - it('produce added Metric with the default and extra one dimensions', async () => { - // Check metric dimensions - const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); - - expect(metrics.Metrics?.length).toBe(1); - const metric = metrics.Metrics?.[0]; - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, - ]; - expect(metric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - 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( - { - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: expectedMetricName, - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // 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); - }, ONE_MINUTE * 3); - - afterAll(async () => { - if (!process.env.DISABLE_TEARDOWN) { - await destroyStack(integTestApp, stack); - } - }, ONE_MINUTE * 3); -}); diff --git a/packages/metrics/tests/e2e/standardFunctions.test.ts b/packages/metrics/tests/e2e/standardFunctions.test.ts deleted file mode 100644 index 4c860e0f06..0000000000 --- a/packages/metrics/tests/e2e/standardFunctions.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -/** - * Test metrics standard functions - * - * @group e2e/metrics/standardFunctions - */ - -import { randomUUID } from 'crypto'; -import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs'; -import { Tracing } from 'aws-cdk-lib/aws-lambda'; -import { App, Stack } from 'aws-cdk-lib'; -import * as AWS from 'aws-sdk'; -import { MetricUnits } from '../../src'; -import { getMetrics } from '../helpers/metricsUtils'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; - -const ONE_MINUTE = 1000 * 60; - -const cloudwatchClient = new AWS.CloudWatch(); -const lambdaClient = new AWS.Lambda(); - -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'; -const expectedMetricName = 'MyMetric'; -const expectedMetricUnit = MetricUnits.Count; -const expectedMetricValue = '1'; -const expectedDefaultDimensions = { MyDimension: 'MyValue' }; -const expectedExtraDimension = { MyExtraDimension: 'MyExtraValue' }; -const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' }; -const expectedSingleMetricName = 'MySingleMetric'; -const expectedSingleMetricUnit = MetricUnits.Percent; -const expectedSingleMetricValue = '2'; -const functionName = 'MyFunctionWithStandardHandler'; -new lambda.NodejsFunction(stack, 'MyFunction', { - functionName: functionName, - tracing: Tracing.ACTIVE, - environment: { - EXPECTED_NAMESPACE: expectedNamespace, - EXPECTED_SERVICE_NAME: expectedServiceName, - EXPECTED_METRIC_NAME: expectedMetricName, - EXPECTED_METRIC_UNIT: expectedMetricUnit, - EXPECTED_METRIC_VALUE: expectedMetricValue, - EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), - EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), - EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), - EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, - EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, - EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, - }, -}); - -describe('happy cases', () => { - beforeAll(async () => { - - await deployStack(integTestApp, stack); - - // 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, 15000)); - }, ONE_MINUTE * 3); - - it('capture ColdStart Metric', async () => { - // Check coldstart metric dimensions - const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - - expect(coldStartMetrics.Metrics?.length).toBe(1); - const coldStartMetric = coldStartMetrics.Metrics?.[0]; - 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: adjustedStartTime, - Dimensions: [{ Name: 'service', Value: expectedServiceName }], - EndTime: endTime, - Period: 60, - MetricName: 'ColdStart', - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // 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); - }, ONE_MINUTE * 3); - - it('produce added Metric with the default and extra one dimensions', async () => { - // Check metric dimensions - const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); - - expect(metrics.Metrics?.length).toBe(1); - const metric = metrics.Metrics?.[0]; - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, - ]; - expect(metric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - 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( - { - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: expectedMetricName, - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // 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); - }, ONE_MINUTE * 3); - - afterAll(async () => { - if (!process.env.DISABLE_TEARDOWN) { - await destroyStack(integTestApp, stack); - } - }, ONE_MINUTE * 3); -});