diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 4de62829..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: "54 19 * * 1" - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ javascript ] - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - queries: +security-and-quality - - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{ matrix.language }}" diff --git a/.gitignore b/.gitignore index 4562b131..76f81824 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ logs test-results.xml .env .idea/ +.npmrc diff --git a/package-lock.json b/package-lock.json index 7911077e..83258098 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,15 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@adobe/fetch": "^4.1.0", + "@adobe/fetch": "4.1.1", "@adobe/helix-shared-secrets": "^2.1.2", "@adobe/helix-shared-wrap": "2.0.0", "@adobe/helix-status": "10.0.10", "@adobe/helix-universal-logger": "3.0.11", "@aws-sdk/client-dynamodb": "^3.427.0", - "@aws-sdk/client-s3": "^3.427.0", - "@aws-sdk/client-sqs": "^3.427.0", - "@aws-sdk/lib-dynamodb": "^3.427.0", - "axios": "1.5.1", + "@aws-sdk/client-s3": "3.427.0", + "@aws-sdk/client-sqs": "3.427.0", + "@aws-sdk/lib-dynamodb": "3.427.0", "esm": "3.2.25" }, "devDependencies": { @@ -88,10 +87,33 @@ "node": ">=14.0.0" } }, - "node_modules/@adobe/fetch": { + "node_modules/@adobe/fastly-native-promises/node_modules/@adobe/fetch": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@adobe/fastly-native-promises/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@adobe/fetch": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.1.tgz", + "integrity": "sha512-EechqpvFBFs1Fq8/E3hNxxyv2HU9xDKFV5tyUTESNQwaMn9kKHidqu2pv0WAJuuBvxRVdVc3gRhUEePFLwXfmw==", "dependencies": { "debug": "4.3.4", "http-cache-semantics": "4.1.1", @@ -157,6 +179,20 @@ "@adobe/helix-universal": ">=4.1.1" } }, + "node_modules/@adobe/helix-deploy/node_modules/@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, "node_modules/@adobe/helix-deploy/node_modules/@aws-sdk/client-s3": { "version": "3.421.0", "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/@aws-sdk/client-s3/-/client-s3-3.421.0.tgz", @@ -344,6 +380,15 @@ "node": ">=14.0.0" } }, + "node_modules/@adobe/helix-deploy/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@adobe/helix-log": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@adobe/helix-log/-/helix-log-6.0.0.tgz", @@ -387,6 +432,27 @@ "@adobe/fetch": "4.1.0" } }, + "node_modules/@adobe/helix-status/node_modules/@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@adobe/helix-status/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/@adobe/helix-universal": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@adobe/helix-universal/-/helix-universal-4.4.0.tgz", @@ -406,6 +472,50 @@ "@adobe/helix-log": "6.0.0" } }, + "node_modules/@adobe/helix-universal-logger/node_modules/@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@adobe/helix-universal-logger/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@adobe/helix-universal/node_modules/@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "devOptional": true, + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@adobe/helix-universal/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "devOptional": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@adobe/semantic-release-coralogix": { "version": "1.1.26", "resolved": "https://registry.npmjs.org/@adobe/semantic-release-coralogix/-/semantic-release-coralogix-1.1.26.tgz", @@ -415,6 +525,29 @@ "@adobe/fetch": "4.1.0" } }, + "node_modules/@adobe/semantic-release-coralogix/node_modules/@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@adobe/semantic-release-coralogix/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@adobe/semantic-release-skms-cmr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@adobe/semantic-release-skms-cmr/-/semantic-release-skms-cmr-1.0.12.tgz", @@ -425,6 +558,29 @@ "cookie": "0.5.0" } }, + "node_modules/@adobe/semantic-release-skms-cmr/node_modules/@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@adobe/semantic-release-skms-cmr/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -1091,15 +1247,6 @@ } } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@aws-sdk/client-lambda": { "version": "3.421.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.421.0.tgz", @@ -1686,15 +1833,6 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@aws-sdk/client-sqs": { "version": "3.427.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.427.0.tgz", @@ -2193,15 +2331,6 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@aws-sdk/client-sso": { "version": "3.421.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.421.0.tgz", @@ -7344,14 +7473,6 @@ "node": ">=14.0.0" } }, - "node_modules/@smithy/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@smithy/middleware-serde": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.11.tgz", @@ -8414,7 +8535,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/available-typed-arrays": { "version": "1.0.5", @@ -8433,16 +8555,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, - "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -9196,6 +9308,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -9702,6 +9815,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -10960,25 +11074,6 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -11011,6 +11106,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -13228,6 +13324,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -13236,6 +13333,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -18154,11 +18252,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -20821,10 +20914,9 @@ } }, "node_modules/uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", - "dev": true, + "version": "8.3.2", + "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "bin": { "uuid": "dist/bin/uuid" } @@ -21351,12 +21443,31 @@ "@adobe/fetch": "4.1.0", "form-data": "4.0.0", "object-hash": "3.0.0" + }, + "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } } }, "@adobe/fetch": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", - "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.1.tgz", + "integrity": "sha512-EechqpvFBFs1Fq8/E3hNxxyv2HU9xDKFV5tyUTESNQwaMn9kKHidqu2pv0WAJuuBvxRVdVc3gRhUEePFLwXfmw==", "requires": { "debug": "4.3.4", "http-cache-semantics": "4.1.1", @@ -21409,6 +21520,17 @@ "yargs": "17.7.2" }, "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, "@aws-sdk/client-s3": { "version": "3.421.0", "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/@aws-sdk/client-s3/-/client-s3-3.421.0.tgz", @@ -21563,6 +21685,12 @@ "@smithy/types": "^2.3.3", "tslib": "^2.5.0" } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true } } }, @@ -21607,6 +21735,23 @@ "integrity": "sha512-t+OxP29nMUiG+Ewbj+oTw0x5XsKXwZWh1vIwvthrZ0JA0mtn3Zg08Hs6XeYkP/6PEFIj02NDWF2AJNaIYqWplQ==", "requires": { "@adobe/fetch": "4.1.0" + }, + "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + } } }, "@adobe/helix-universal": { @@ -21617,6 +21762,25 @@ "requires": { "@adobe/fetch": "4.1.0", "aws4": "1.12.0" + }, + "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "devOptional": true, + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "devOptional": true + } } }, "@adobe/helix-universal-logger": { @@ -21626,6 +21790,23 @@ "requires": { "@adobe/fetch": "4.1.0", "@adobe/helix-log": "6.0.0" + }, + "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + } } }, "@adobe/semantic-release-coralogix": { @@ -21635,6 +21816,25 @@ "dev": true, "requires": { "@adobe/fetch": "4.1.0" + }, + "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } } }, "@adobe/semantic-release-skms-cmr": { @@ -21645,6 +21845,25 @@ "requires": { "@adobe/fetch": "4.1.0", "cookie": "0.5.0" + }, + "dependencies": { + "@adobe/fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.0.tgz", + "integrity": "sha512-s2YmZYSrsgQ7K+LRT4iaYcKlkMp9pI6ODjEXfCsYN8cKZo58mCoqnpXJ9UzzJu72S41dKP3HzTa1bOmLZ4zzGQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "http-cache-semantics": "4.1.1", + "lru-cache": "7.18.3" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } } }, "@ampproject/remapping": { @@ -22228,11 +22447,6 @@ "@smithy/types": "^2.3.4", "tslib": "^2.5.0" } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" } } }, @@ -22727,14 +22941,6 @@ "@smithy/util-utf8": "^2.0.0", "tslib": "^2.5.0", "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - } } }, "@aws-sdk/client-sqs": { @@ -23164,14 +23370,6 @@ "@smithy/util-waiter": "^2.0.9", "tslib": "^2.5.0", "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - } } }, "@aws-sdk/client-sso": { @@ -26825,13 +27023,6 @@ "@smithy/util-retry": "^2.0.4", "tslib": "^2.5.0", "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } } }, "@smithy/middleware-serde": { @@ -27706,7 +27897,8 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "available-typed-arrays": { "version": "1.0.5", @@ -27719,16 +27911,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, - "axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -28260,6 +28442,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -28629,7 +28812,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true }, "depd": { "version": "2.0.0", @@ -29599,11 +29783,6 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, - "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" - }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -29633,6 +29812,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -31234,12 +31414,14 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "requires": { "mime-db": "1.52.0" } @@ -34611,11 +34793,6 @@ "ipaddr.js": "1.9.1" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -36543,10 +36720,9 @@ "dev": true }, "uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", - "dev": true + "version": "8.3.2", + "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-to-istanbul": { "version": "9.1.3", diff --git a/package.json b/package.json index 9c775b95..ea2b7b36 100644 --- a/package.json +++ b/package.json @@ -50,19 +50,19 @@ "require": "test/setup-env.js", "recursive": "true", "reporter": "mocha-multi-reporters", - "reporter-options": "configFile=.mocha-multi.json" + "reporter-options": "configFile=.mocha-multi.json", + "loader": "esmock" }, "dependencies": { - "@adobe/fetch": "^4.1.0", + "@adobe/fetch": "4.1.1", "@adobe/helix-shared-secrets": "^2.1.2", "@adobe/helix-shared-wrap": "2.0.0", "@adobe/helix-status": "10.0.10", "@adobe/helix-universal-logger": "3.0.11", "@aws-sdk/client-dynamodb": "^3.427.0", - "@aws-sdk/client-s3": "^3.427.0", - "@aws-sdk/client-sqs": "^3.427.0", - "@aws-sdk/lib-dynamodb": "^3.427.0", - "axios": "1.5.1", + "@aws-sdk/client-s3": "3.427.0", + "@aws-sdk/client-sqs": "3.427.0", + "@aws-sdk/lib-dynamodb": "3.427.0", "esm": "3.2.25" }, "devDependencies": { diff --git a/src/util.js b/src/db-wrapper.js similarity index 55% rename from src/util.js rename to src/db-wrapper.js index b2c184ad..c34cbe64 100644 --- a/src/util.js +++ b/src/db-wrapper.js @@ -9,25 +9,15 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -const log = (level, message, ...args) => { - const timestamp = new Date().toISOString(); - switch (level) { - case 'info': - console.info(`[${timestamp}] INFO: ${message}`, ...args); - break; - case 'error': - console.error(`[${timestamp}] ERROR: ${message}`, ...args); - break; - case 'warn': - console.warn(`[${timestamp}] WARN: ${message}`, ...args); - break; - default: - console.log(`[${timestamp}] ${message}`, ...args); - break; - } -}; +import DB from './db.js'; -export { - log, -}; +export default function dynamoDBWrapper(func) { + return async (request, context) => { + if (!context.db) { + context.db = new DB(context); + } + + return func(request, context); + }; +} diff --git a/src/db.js b/src/db.js index d1f43b3d..6aca7a6a 100644 --- a/src/db.js +++ b/src/db.js @@ -10,48 +10,71 @@ * governing permissions and limitations under the License. */ import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient, GetItemCommand, PutCommand } from '@aws-sdk/lib-dynamodb'; -import { log } from './util.js'; +import { DynamoDBDocumentClient, GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb'; const TABLE_SITES = 'spacecat-site'; const TABLE_AUDITS = 'spacecat-audit-index'; -function DB(config) { - const client = new DynamoDBClient({ region: config.region }); - const docClient = DynamoDBDocumentClient.from(client); +export default class DB { + constructor(context) { + this.client = new DynamoDBClient({ region: context.runtime.region }); + this.log = context.log; + this.docClient = DynamoDBDocumentClient.from(this.client); + } /** * Save a record to the DynamoDB. * @param {object} record - The new record to save. * @param tableName - The name of the table to save the record to. */ - async function saveRecord(record, tableName) { + async saveRecord(record, tableName) { try { const command = new PutCommand({ TableName: tableName, Item: record, }); - await docClient.send(command); + await this.docClient.send(command); } catch (error) { - log('error', 'Error saving record: ', error); + this.log.error(`Error saving record: ${error}`); } } + /** - * Saves an audit to the DynamoDB. - * @param {object} site - Site object containing details of the audited site. - * @param {object} audit - Audit object containing the type and result of the audit. - * @returns {Promise} Resolves once audit is saved. - */ - async function saveAuditIndex(site, audit) { + * Saves an audit to the DynamoDB. + * @param {object} site - Site object containing details of the audited site. + * @param {object} audit - Audit object containing the type and result of the audit. + * @typedef {Object} audit + * @property {string} uuid - Audit unique identifier + * @property {string} siteId - Site unique identifier composed of domain and path. + * @property {string} auditDate - Date when audit was run. + * @property {string} type - Type of the run audit (default psi) + * @property {boolean} isLive - Indicates if the site is live at the time of the audit + * @property {string} git_hashes - Git commit hashes since the last audit was run. + * @property {string} tag_manager - Tag manager used for the site and the version. + * @property {string} error - The error if auditing returned an error. + * @property {Object[]} auditResults - The minified audit results from the psi checks + * @param {string} auditResults[].strategy - The psi audit strategy can be desktop or mobile. + * @param {object} auditResults[].scores - The minified results of the psi audit for the strategy. + * @param {object} auditResults[].score.performance - The performance score of psi check for + * the site strategy. + * @param {object} auditResults[].scores.seo - The seo score of psi check for the site strategy. + * @param {object} auditResults[].scores.best-practices - The best-practices score of psi check + * for the site strategy. + * @param {object} auditResults[].scores.accessibility - The accessibility score of psi check for + * the site strategy. + * @returns {Promise} Resolves the new saved audit. + */ + async saveAuditIndex(site, audit) { const now = new Date().toISOString(); - const uuid = Date.now().toString(); + const uuid = Date.now() + .toString(); const newAudit = { id: uuid, - siteId: site.id, + siteId: `${site.domain}/${site.path}`, audit_date: now, type: 'psi', - is_live: site.isLive, + is_live: false, content_publication_date: '', git_hashes: [], tag_manager: '', @@ -77,34 +100,36 @@ function DB(config) { }, ], }; - log('info', `Audit for domain ${site.domain} saved successfully at ${now}`); - await saveRecord(newAudit, TABLE_AUDITS); + await this.saveRecord(newAudit, TABLE_AUDITS); + this.log.info(`Saving successful audit for domain ${site.domain} saved successfully`); return newAudit; } /** - * Save an error that occurred during a Lighthouse audit to the DynamoDB. - * @param {object} site - site audited. - * @param {Error} error - The error that occurred during the audit. - */ - async function saveAuditError(site, error) { + * Save an error that occurred during a Lighthouse audit to the DynamoDB. + * @param {object} site - site audited. + * @param {Error} error - The error that occurred during the audit. + */ + async saveAuditError(site, error) { const now = new Date().toISOString(); const newAudit = { - siteId: site.id, + siteId: `${site.domain}/${site.path}`, auditDate: now, - isLive: site.isLive, error: error.message, scores: {}, }; - await saveRecord(newAudit, TABLE_AUDITS); + await this.saveRecord(newAudit, TABLE_AUDITS); + this.log.info(`Saving error audit for domain ${site.domain} saved successfully`); } + /** - * Fetches a site by its ID and gets its latest audit. - * @param {string} siteId - The ID of the site to fetch. - * @returns {Promise} Site document with its latest audit. - */ - async function getSite(domain, path) { - const params = { + * Fetches a site by its ID and gets its latest audit. + * @param {string} domain - The domain of the site to fetch. + * @param {string} path - The path of the site to fetch. + * @returns {Promise} Site document with its latest audit. + */ + async getSite(domain, path) { + const commandParams = { TableName: TABLE_SITES, // Replace with your table name Key: { Domain: { S: domain }, // Partition key @@ -113,26 +138,24 @@ function DB(config) { }; try { - const command = new GetItemCommand(params); - const response = await client.send(command); + const command = new GetCommand(commandParams); + const response = await this.docClient.send(command); const item = response.Item; if (item) { - log('info', `Item retrieved successfully: ${item}`); + this.log.info(`Item retrieved successfully: ${item}`); return item; } else { - log('info', 'Item not found.'); + this.log.info('Item not found.'); return null; } } catch (error) { - log('error', `Error ${error}`); + this.log.error(`Error ${error}`); throw error; } } - return { - getSite, - saveAuditIndex, - saveAuditError, - }; -} -export default DB; + destroy() { + this.docClient.destroy(); + this.client.destroy(); + } +} diff --git a/src/index.js b/src/index.js index d0c80753..7ca0db04 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ /* - * Copyright 2019 Adobe. All rights reserved. + * Copyright 2023 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 @@ -11,11 +11,10 @@ */ import secrets from '@adobe/helix-shared-secrets'; import wrap from '@adobe/helix-shared-wrap'; -import { logger } from '@adobe/helix-universal-logger'; import { helixStatus } from '@adobe/helix-status'; -import SQSQueue from './sqs-queue.js'; -import DB from './db.js'; // Assuming the exported content of './db' is default exported -import PSIClient from './psi-client.js'; // Assuming the exported content of './psi-client' is default exported +import dynamoDBWrapper from './db-wrapper.js'; +import PSIClient from './psi-client.js'; +import queueWrapper from './queue-wrapper.js'; /** * This is the main function @@ -24,32 +23,58 @@ import PSIClient from './psi-client.js'; // Assuming the exported content of './ * @returns {Response} a response */ async function run(request, context) { - const db = DB({ - region: process.env.REGION, - }); - const sqsQueue = SQSQueue(); - const { message } = JSON.parse(context.invocation.event.Records[0].body); + const { db, queue } = context; + const event = context.invocation?.event; + if (!event) { + return new Response('', { + status: 400, + headers: { + 'x-error': 'Action was not triggered by an event', + }, + }); + } + let message = event.Records[0]?.body?.message; + if (!message) { + return new Response('', { + status: 400, + headers: { + 'x-error': 'Event does not contain a message body', + }, + }); + } + message = JSON.parse(message); + if (!message.domain) { + return new Response('', { + status: 400, + headers: { + 'x-error': 'Event message does not contain a domain', + }, + }); + } const psiClient = PSIClient({ - apiKey: process.env.PAGESPEED_API_KEY, - baseUrl: process.env.PAGESPEED_API_BASE_URL, + apiKey: context.env.PAGESPEED_API_KEY, + baseUrl: context.env.PAGESPEED_API_BASE_URL, }); const site = { - id: message.siteId, - githubURL: message.githubURL, domain: message.domain, path: message.path, - isLive: message.isLive, }; - const auditResult = await psiClient.runAudit(`https://${site.domain}/${site.path}`); - const auditResultMin = await db.saveAuditIndex(site, auditResult); - await sqsQueue.sendMessage(auditResultMin); + try { + const auditResult = await psiClient.runAudit(`https://${site.domain}/${site.path}`); + const auditResultMin = await db.saveAuditIndex(site, auditResult); + await queue.sendAuditResult(auditResultMin); + } catch (e) { + await db.saveAuditError(site, e); + } + db.destroy(); + queue.destroy(); return new Response('SUCCESS'); } export const main = wrap(run) - .with(helixStatus) - .with(logger.trace) - .with(logger) - .with(secrets); + .with(dynamoDBWrapper) + .with(queueWrapper) + .with(secrets) + .with(helixStatus); diff --git a/src/psi-client.js b/src/psi-client.js index 37289858..1f33b12f 100644 --- a/src/psi-client.js +++ b/src/psi-client.js @@ -9,8 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import axios from 'axios'; -import { log } from './util.js'; +import { fetch } from '@adobe/fetch'; function PSIClient(config) { const AUDIT_TYPE = 'PSI'; @@ -136,15 +135,16 @@ function PSIClient(config) { * @returns {Promise} The processed PageSpeed Insights audit data. */ const performPSICheck = async (domain, strategy) => { + // eslint-disable-next-line no-useless-catch try { const apiURL = getPSIApiUrl(domain, strategy); - const { data: lhs } = await axios.get(apiURL); + const response = await fetch(apiURL); + const { data: lhs } = await response.json(); const { lighthouseResult } = processAuditData(lhs); return processLighthouseResult(lighthouseResult); } catch (e) { - log('error', `Error happened during PSI check: ${e}`); throw e; } }; @@ -153,12 +153,8 @@ function PSIClient(config) { const auditResults = {}; for (const strategy of PSI_STRATEGIES) { - const strategyStartTime = process.hrtime(); // eslint-disable-next-line no-await-in-loop const psiResult = await performPSICheck(domain, strategy); - const strategyEndTime = process.hrtime(strategyStartTime); - const strategyElapsedTime = (strategyEndTime[0] + strategyEndTime[1] / 1e9).toFixed(2); - log('info', `Audited ${domain} for ${strategy} strategy in ${strategyElapsedTime} seconds`); auditResults[strategy] = psiResult; } diff --git a/src/queue-wrapper.js b/src/queue-wrapper.js new file mode 100644 index 00000000..10d4da9d --- /dev/null +++ b/src/queue-wrapper.js @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + + +import SqsQueue from './sqs-queue.js'; + +export default function queueWrapper(func) { + return async (request, context) => { + const queueUrl = context.env.AUDIT_RESULTS_QUEUE_URL; + + if (!queueUrl) { + throw new Error('AUDIT_RESULTS_QUEUE_URL env variable is empty/not provided'); + } + + context.attributes.queueUrl = queueUrl; + + if (!context.queue) { + context.queue = new SqsQueue(context); + } + + return func(request, context); + }; +} diff --git a/src/sqs-queue.js b/src/sqs-queue.js index 0278c8b5..a26abb11 100644 --- a/src/sqs-queue.js +++ b/src/sqs-queue.js @@ -9,47 +9,48 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs'; +import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; -// Set up the region -const REGION = 'us-east-1'; // change this to your desired region +/** + * @class SQSQueue class to send audit results to SQS + * @param {string} region - AWS region + * @param {string} queueUrl - SQS queue URL + * @param {object} log - OpenWhisk log object + */ +export default class SQSQueue { + constructor(context) { + const { region, log } = context; + const { queueUrl } = context.attributes; -// Create SQS service client object -const sqsClient = new SQSClient({ region: REGION }); + this.queueUrl = queueUrl; + this.log = log; -// Your SQS queue URL -const queueURL = 'https://sqs.us-east-1.amazonaws.com/282898975672/spacecat-audit-results'; + this.sqsClient = new SQSClient({ region }); + log.info(`Creating SQS client in region ${region}`); + } -function SQSQueue() { - async function sendMessage(message) { + async sendAuditResult(message) { const body = { message, timestamp: new Date().toISOString(), }; - // Set up the parameters for the send message command const params = { DelaySeconds: 10, MessageBody: JSON.stringify(body), - QueueUrl: queueURL, + QueueUrl: this.queueUrl, }; try { - const data = await sqsClient.send(new SendMessageCommand(params)); - console.log('Success, message sent. MessageID:', data.MessageId); + const data = await this.sqsClient.send(new SendMessageCommand(params)); + this.log.info(`Success, message sent. MessageID: ${data.MessageId}`); } catch (err) { - console.log('Error:', err); + this.log.error(`${err}`); throw err; } + } - return { - statusCode: 200, - body: JSON.stringify({ message: 'SQS message sent!' }), - }; + destroy() { + this.sqsClient.destroy(); } - return { - sendMessage, - }; } - -export default SQSQueue; diff --git a/src/storage.js b/src/storage.js index a52b9eb4..c75f9c00 100644 --- a/src/storage.js +++ b/src/storage.js @@ -9,8 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { S3 } from "@aws-sdk/client-s3"; -import { log } from 'util'; +import { S3 } from '@aws-sdk/client-s3'; function storage(config) { const s3 = new S3({ region: config.region }); @@ -22,11 +21,11 @@ function storage(config) { ContentType: 'application/json', }; + // eslint-disable-next-line no-useless-catch try { - log('info', `Data saved to S3 with key: ${key}`); await s3.putObject(params); } catch (error) { - log('error', 'Error saving data to S3: ', error); + throw error; } } } diff --git a/test/db-wrapper.test.js b/test/db-wrapper.test.js new file mode 100644 index 00000000..cc20669b --- /dev/null +++ b/test/db-wrapper.test.js @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import assert from 'assert'; +import dynamoDBWrapper from '../src/db-wrapper.js'; +import DB from '../src/db.js'; + +describe('DB Wrapper Tests', () => { + let mockFunc; + let mockRequest; + let mockContext; + + beforeEach(() => { + mockFunc = async () => {}; + mockRequest = {}; + mockContext = { + attributes: {}, + runtime: { + region: 'test-region', + }, + log: { + info: () => {}, + error: () => {}, + }, + }; + }); + + it('should create db if not present in context', async () => { + await dynamoDBWrapper(mockFunc)(mockRequest, mockContext); + assert(mockContext.db instanceof DB, 'context.db was not correctly instantiated.'); + }); + + it('should not re-initialize db if already present in context', async () => { + const mockDB = new DB(mockContext); + mockContext.db = mockDB; + + await dynamoDBWrapper(mockFunc)(mockRequest, mockContext); + + assert.strictEqual(mockContext.db, mockDB, 'context.db was re-initialized.'); + }); +}); diff --git a/test/db.test.js b/test/db.test.js new file mode 100644 index 00000000..a9e12634 --- /dev/null +++ b/test/db.test.js @@ -0,0 +1,103 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import assert from 'assert'; +import esmock from 'esmock'; +import DB from '../src/db.js'; +import DBDocClientMock from './dynamo-db-doc-client-mock.js'; + +describe('DB Tests', () => { + const region = 'test-region'; + + let mockContext; + let logInfo = ''; + let logError = ''; + const testSite = { domain: 'www.testdomain.com', path: '/testpath' }; + + beforeEach(() => { + mockContext = { + runtime: { region }, + log: { + info: (message) => { + logInfo = message; + }, + error: (message) => { + logError = message; + }, + }, + }; + }); + + it('should initialize with provided context', () => { + const db = new DB(mockContext); + assert.strictEqual(db.log, mockContext.log); + }); + + it('should save the successful audit to dynamodb and log success', async () => { + const DBDocMock = await esmock('../src/db.js', { + '@aws-sdk/lib-dynamodb': { + DynamoDBDocumentClient: DBDocClientMock, + }, + }); + const db = new DBDocMock(mockContext); + await db.saveAuditIndex(testSite, { + result: { + mobile: { + categories: { + performance: { score: 0.90 }, + seo: { score: 0.90 }, + 'best-practices': { score: 0.90 }, + accessibility: { score: 0.90 }, + }, + }, + desktop: { + categories: { + performance: { score: 0.90 }, + seo: { score: 0.90 }, + 'best-practices': { score: 0.90 }, + accessibility: { score: 0.90 }, + }, + }, + }, + }); + assert.strictEqual(logInfo, 'Saving successful audit for domain www.testdomain.com saved successfully'); + }); + + it('should save the error audit to dynamodb and log success', async () => { + const DBDocMock = await esmock('../src/db.js', { + '@aws-sdk/lib-dynamodb': { + DynamoDBDocumentClient: DBDocClientMock, + }, + }); + const db = new DBDocMock(mockContext); + await db.saveAuditError(testSite, { + error: { + message: 'Could not run audit for www.testdomain.com', + }, + }); + assert.strictEqual(logInfo, 'Saving error audit for domain www.testdomain.com saved successfully'); + }); + + it('should get the site from dynamodb', async () => { + const DBDocMock = await esmock('../src/db.js', { + '@aws-sdk/lib-dynamodb': { + DynamoDBDocumentClient: DBDocClientMock, + }, + }); + const db = new DBDocMock(mockContext); + const site = await db.getSite(testSite.domain, testSite.path); + assert.strictEqual(logInfo, `Item retrieved successfully: ${testSite}`); + assert.deepStrictEqual(site, testSite); + }); +}); diff --git a/test/dynamo-db-doc-client-mock.js b/test/dynamo-db-doc-client-mock.js new file mode 100644 index 00000000..79f6440c --- /dev/null +++ b/test/dynamo-db-doc-client-mock.js @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb'; + +export default class DBDocClientMock { + constructor(config) { + this.config = config; + } + + static from() { + return new DBDocClientMock(); + } + + send(command) { + if (command instanceof PutCommand) { + return Promise.resolve({ }); + } else if (command instanceof GetCommand) { + return Promise.resolve({ + Item: { + domain: 'www.testdomain.com', + path: '/testpath', + }, + }); + } else { + return Promise.reject(new Error('Unknown command in mock')); + } + } +} diff --git a/test/index.test.js b/test/index.test.js index 71b0f0ad..47f6a436 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -16,8 +16,53 @@ import assert from 'assert'; import { main } from '../src/index.js'; describe('Index Tests', () => { - it('index function is present', async () => { - const result = await main(); - assert.strictEqual(result, 'Hello, world.'); + let mockContext; + beforeEach(() => { + mockContext = { + runtime: { region: 'test-region' }, + env: { + AUDIT_RESULTS_QUEUE_URL: 'queue-url', + }, + attributes: { + }, + log: { + info: () => {}, + error: () => {}, + }, + }; + }); + + it('index function returns an error if not triggered by an event', async () => { + const response = await main({}, mockContext); + assert.strictEqual(response.headers.get('x-error'), 'Action was not triggered by an event'); + }); + + it('index function returns an error if event does not contain a message', async () => { + const eventMockContext = { + ...mockContext, + ...{ invocation: { event: { Records: [{ body: {} }] } } }, + }; + const response = await main({}, eventMockContext); + assert.strictEqual(response.headers.get('x-error'), 'Event does not contain a message body'); + }); + + it('index function returns an error if event message does not contain a domain', async () => { + const eventMockContext = { + ...mockContext, + ...{ invocation: { event: { Records: [{ body: { message: '{ "text": "foo" }' } }] } } }, + }; + const response = await main({}, eventMockContext); + assert.strictEqual(response.headers.get('x-error'), 'Event message does not contain a domain'); + }); + + it('index function returns SUCCESS if trigerred by an event that contain a domain', async () => { + const eventMockContext = { + ...mockContext, + ...{ invocation: { event: { Records: [{ body: { message: '{ "domain": "adobe.com" }' } }] } } }, + }; + const response = await main({}, eventMockContext); + const reader = response.body.getReader(); + const { value } = await reader.read(); + assert.strictEqual(String.fromCharCode(...value), 'SUCCESS'); }); }); diff --git a/test/psi-client.test.js b/test/psi-client.test.js new file mode 100644 index 00000000..5de222ea --- /dev/null +++ b/test/psi-client.test.js @@ -0,0 +1,240 @@ +/* + * Copyright 2021 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import nock from 'nock'; +import assert from 'assert'; +import PSIClient from '../src/psi-client.js'; + +describe('PSIClient', () => { + let client; + const config = { + apiKey: 'test-api-key', + baseUrl: 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed', + }; + + beforeEach(() => { + client = PSIClient(config); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + describe('getPSIApiUrl', () => { + it('should build a correct PSI API URL', () => { + const apiUrl = client.getPSIApiUrl('example.com'); + const expectedUrl = `${config.baseUrl}?url=https%3A%2F%2Fexample.com&key=${config.apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo`; + assert.strictEqual(apiUrl, expectedUrl); + }); + + it('should use mobile strategy by default', () => { + const apiUrl = client.getPSIApiUrl('example.com'); + const expectedUrl = `${config.baseUrl}?url=https%3A%2F%2Fexample.com&key=${config.apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo`; + assert.strictEqual(apiUrl, expectedUrl); + }); + + it('should use mobile strategy when specified', () => { + const apiUrl = client.getPSIApiUrl('example.com', 'mobile'); + const expectedUrl = `${config.baseUrl}?url=https%3A%2F%2Fexample.com&key=${config.apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo`; + assert.strictEqual(apiUrl, expectedUrl); + }); + + it('should use desktop strategy when specified', () => { + const apiUrl = client.getPSIApiUrl('example.com', 'desktop'); + const expectedUrl = `${config.baseUrl}?url=https%3A%2F%2Fexample.com&key=${config.apiKey}&strategy=desktop&category=performance&category=accessibility&category=best-practices&category=seo`; + assert.strictEqual(apiUrl, expectedUrl); + }); + + it('should default to mobile strategy for invalid strategy', () => { + const apiUrl = client.getPSIApiUrl('example.com', 'invalid-strategy'); + const expectedUrl = `${config.baseUrl}?url=https%3A%2F%2Fexample.com&key=${config.apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo`; + assert.strictEqual(apiUrl, expectedUrl); + }); + + // Input edge cases for getPSIApiUrl + it('should handle empty domain input gracefully', () => { + const apiUrl = client.getPSIApiUrl(''); + assert.strictEqual(apiUrl, `${config.baseUrl}?url=https%3A%2F%2F&key=${config.apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo`); + }); + + it('should encode special characters in domain', () => { + const apiUrl = client.getPSIApiUrl('example.com/some path'); + assert.strictEqual(apiUrl, `${config.baseUrl}?url=https%3A%2F%2Fexample.com%2Fsome+path&key=${config.apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo`); + }); + }); + + describe('runAudit', () => { + afterEach(() => { + nock.cleanAll(); + }); + + it('should run and desktop strategy audit', async () => { + const mockResponse = { data: 'some mobile data' }; + nock('https://www.googleapis.com') + .get('/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2FsomeUrl&key=test-api-key&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo') + .reply(200, mockResponse); + nock('https://www.googleapis.com') + .get('/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2FsomeUrl&key=test-api-key&strategy=desktop&category=performance&category=accessibility&category=best-practices&category=seo') + .reply(200, mockResponse); + + const audit = await client.runAudit('someUrl'); + + // Ensure the response structure is correct + assert.deepStrictEqual(audit.result.mobile, { + audits: { + 'third-party-summary': undefined, + 'total-blocking-time': undefined, + }, + categories: undefined, + configSettings: undefined, + environment: undefined, + fetchTime: undefined, + finalDisplayedUrl: undefined, + finalUrl: undefined, + lighthouseVersion: undefined, + mainDocumentUrl: undefined, + requestedUrl: undefined, + runWarnings: undefined, + timing: undefined, + userAgent: undefined, + }); + assert.deepStrictEqual(audit.result.desktop, { + audits: { + 'third-party-summary': undefined, + 'total-blocking-time': undefined, + }, + categories: undefined, + configSettings: undefined, + environment: undefined, + fetchTime: undefined, + finalDisplayedUrl: undefined, + finalUrl: undefined, + lighthouseVersion: undefined, + mainDocumentUrl: undefined, + requestedUrl: undefined, + runWarnings: undefined, + timing: undefined, + userAgent: undefined, + }); + }); + + it('should throw an error if the audit fails', async () => { + nock('https://www.googleapis.com') + .get('/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2FsomeUrl&key=test-api-key&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo') + .replyWithError('Failed to fetch PSI'); + + try { + await client.runAudit('someUrl'); + assert.fail('Expected runAudit to throw an error'); + } catch (error) { + assert.strictEqual(error.message, 'Failed to fetch PSI'); + } + }); + }); + + describe('performPSICheck', () => { + const expectedResult = { + audits: { + 'third-party-summary': undefined, + 'total-blocking-time': undefined, + }, + categories: undefined, + configSettings: undefined, + environment: undefined, + fetchTime: undefined, + finalDisplayedUrl: undefined, + finalUrl: undefined, + lighthouseVersion: undefined, + mainDocumentUrl: undefined, + requestedUrl: undefined, + runWarnings: undefined, + timing: undefined, + userAgent: undefined, + }; + + it('should perform a PSI check and process data', async () => { + nock('https://www.googleapis.com') + .get('/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2Fexample.com&key=test-api-key&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo') + .reply(200, { data: {} }); + const data = await client.performPSICheck('example.com'); + assert.deepEqual(data, expectedResult); + }); + it('should handle empty domain input gracefully', async () => { + nock('https://www.googleapis.com') + .get('/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2F&key=test-api-key&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo') + .reply(200, { data: {} }); + const data = await client.performPSICheck(''); + assert.deepEqual(data, expectedResult); + }); + + it('should handle domain with special characters', async () => { + nock('https://www.googleapis.com') + .get('/pagespeedonline/v5/runPagespeed?url=https%3A%2F%2Fexample.com/some%20path&key=test-api-key&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo') + .reply(200, { data: {} }); + const data = await client.performPSICheck('example.com/some path'); + assert.deepEqual(data, expectedResult); + }); + }); + + describe('processAuditData', () => { + it('should replace dots with underscores in keys', () => { + const inputData = { + 'key.with.dot': 'value', + 'another.key.with.dot': { + 'nested.key': 'nestedValue', + }, + }; + const processedData = client.processAuditData(inputData); + assert.deepEqual(processedData, { + key_with_dot: 'value', + another_key_with_dot: { + nested_key: 'nestedValue', + }, + }); + }); + + // Input edge cases for processAuditData + it('should handle empty object input gracefully', () => { + const processedData = client.processAuditData({}); + assert.deepEqual(processedData, {}); + }); + + it('should handle null input gracefully', () => { + const processedData = client.processAuditData(null); + assert.strictEqual(processedData, null); + }); + + it('should leave keys without dots unchanged', () => { + const inputData = { + keyWithoutDot: 'value', + anotherKey: { + nestedKey: 'nestedValue', + }, + }; + const processedData = client.processAuditData(inputData); + assert.deepEqual(processedData, inputData); + }); + }); + + describe('formatURL', () => { + it('should replace http:// prefix with https://', () => { + const formattedUrl = client.formatURL('http://example.com'); + assert.strictEqual(formattedUrl, 'https://example.com'); + }); + it('should add https:// prefix to a URL without http/https prefix', () => { + const formattedUrl = client.formatURL('example.com'); + assert.strictEqual(formattedUrl, 'https://example.com'); + }); + }); +}); diff --git a/test/queue-wrapper.test.js b/test/queue-wrapper.test.js new file mode 100644 index 00000000..0c50a787 --- /dev/null +++ b/test/queue-wrapper.test.js @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import assert from 'assert'; +import queueWrapper from '../src/queue-wrapper.js'; +import SQSQueue from '../src/sqs-queue.js'; + +describe('Queue Wrapper Tests', () => { + let mockFunc; + let mockRequest; + let mockContext; + + beforeEach(() => { + mockFunc = async () => {}; + mockRequest = {}; + mockContext = { + env: { + AUDIT_RESULTS_QUEUE_URL: 'queue-url', + }, + attributes: {}, + region: 'test-region', + log: { + info: () => {}, + error: () => {}, + }, + }; + }); + + it('should throw error if queue url is not provided', async () => { + mockContext.env.AUDIT_RESULTS_QUEUE_URL = null; + + try { + await queueWrapper(mockFunc)(mockRequest, mockContext); + assert.fail('Expected error to be thrown'); + } catch (error) { + assert.strictEqual(error.message, 'AUDIT_RESULTS_QUEUE_URL env variable is empty/not provided'); + } + }); + + it('should set queue url in context attributes', async () => { + await queueWrapper(mockFunc)(mockRequest, mockContext); + assert.strictEqual(mockContext.attributes.queueUrl, 'queue-url'); + }); + + it('should create queue if not present in context', async () => { + await queueWrapper(mockFunc)(mockRequest, mockContext); + assert(mockContext.queue instanceof SQSQueue, 'context.queue was not correctly instantiated.'); + }); + + it('should not re-initialize queue if already present in context', async () => { + const mockQueue = new SQSQueue(mockContext); + mockContext.queue = mockQueue; + + await queueWrapper(mockFunc)(mockRequest, mockContext); + + assert.strictEqual(mockContext.queue, mockQueue, 'context.queue was re-initialized.'); + }); +}); diff --git a/test/sqs-client-mock.js b/test/sqs-client-mock.js new file mode 100644 index 00000000..a522f2d2 --- /dev/null +++ b/test/sqs-client-mock.js @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { SendMessageCommand } from '@aws-sdk/client-sqs'; + +export default class SqsClientMock { + constructor(config) { + this.config = config; + } + + // eslint-disable-next-line class-methods-use-this + send(command) { + if (command instanceof SendMessageCommand) { + if (JSON.parse(command.input.MessageBody).message.includes('error')) { + return Promise.reject(new Error('SQSClient.send encountered an error')); + } + return Promise.resolve({ MessageId: 'testMessageId' }); + } else { + return Promise.reject(new Error('Unknown command in mock')); + } + } +} diff --git a/test/sqs-queue.test.js b/test/sqs-queue.test.js new file mode 100644 index 00000000..d4c19273 --- /dev/null +++ b/test/sqs-queue.test.js @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import assert from 'assert'; +import esmock from 'esmock'; +import SqsClientMock from './sqs-client-mock.js'; +import SQSQueue from '../src/sqs-queue.js'; + +describe('SQSQueue Tests', () => { + const region = 'test-region'; + const queueUrl = 'https://queue-url'; + const messageId = 'testMessageId'; + + let mockContext; + let logInfo = ''; + let logError = ''; + + beforeEach(() => { + mockContext = { + attributes: { + queueUrl, + }, + region, + log: { + info: (message) => { + logInfo = message; + }, + error: (message) => { + logError = message; + }, + }, + }; + }); + + it('should initialize with provided context', () => { + const queue = new SQSQueue(mockContext); + assert.strictEqual(queue.queueUrl, mockContext.attributes.queueUrl); + assert.strictEqual(queue.log, mockContext.log); + assert.strictEqual(logInfo, `Creating SQS client in region ${region}`); + }); + + it('should send a message to the queue and log success', async () => { + const SQSQueueMock = await esmock('../src/sqs-queue.js', { + '@aws-sdk/client-sqs': { + SQSClient: SqsClientMock, + }, + }); + const queue = new SQSQueueMock(mockContext); + await queue.sendAuditResult('test message'); + assert.strictEqual(logInfo, `Success, message sent. MessageID: ${messageId}`); + }); + + it('should throw an error, when sending a message to the queue fails', async () => { + const SQSQueueMock = await esmock('../src/sqs-queue.js', { + '@aws-sdk/client-sqs': { + SQSClient: SqsClientMock, + }, + }); + const queue = new SQSQueueMock(mockContext); + try { + await queue.sendAuditResult('error test message'); + assert.fail('Expected SQLClient to throw an error'); + } catch (error) { + assert.strictEqual(logError, 'Error: SQSClient.send encountered an error'); + } + }); +});