From 7fa51a5f72dbb57ae168516bf835070ef7120ca0 Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Sun, 20 Apr 2025 22:08:16 -0500 Subject: [PATCH 01/11] Upgrades node versoin for the initial DB lambda. --- package.json | 8 +- .../initializeHalfsiesDatabase.test.ts | 33 ++++++ .../halfsies/initializeHalfsiesDatabase.ts | 18 +++ src/stacks/halfsies.ts | 43 +++---- yarn.lock | 110 +++++++++--------- 5 files changed, 135 insertions(+), 77 deletions(-) create mode 100755 src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts create mode 100755 src/lambdas/halfsies/initializeHalfsiesDatabase.ts diff --git a/package.json b/package.json index 7124a95..fe37759 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "troyblank-api", "version": "1.0.0", - "packageManager": "yarn@1.22.21", + "packageManager": "yarn@1.22.22", "bin": { "halfsies": "bin/halfsies.js" }, @@ -15,7 +15,7 @@ "deploy": "cdk deploy --all" }, "dependencies": { - "aws-sdk": "2.1606.0", + "aws-sdk": "2.1692.0", "constructs": "10.3.0", "source-map-support": "0.5.21" }, @@ -25,8 +25,8 @@ "@types/aws-lambda": "8.10.137", "@types/jest": "29.5.12", "@types/node": "20.12.7", - "aws-cdk": "2.139.0", - "aws-cdk-lib": "2.139.0", + "aws-cdk": "2.1010.0", + "aws-cdk-lib": "2.190.0", "aws-lambda": "1.0.7", "chance": "1.1.11", "esbuild": "0.20.2", diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts new file mode 100755 index 0000000..589038b --- /dev/null +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts @@ -0,0 +1,33 @@ +import { Chance } from 'chance' +import { DynamoDB } from 'aws-sdk' + +const putItemMock = jest.fn().mockReturnValue({ + promise: jest.fn().mockResolvedValue({}), +}) + +DynamoDB.prototype.putItem = putItemMock + +import { handler } from './initializeHalfsiesDatabase' +import { generateNewBalanceDbItem } from '../../utils' + +describe('Lambda - Initialize Halfsies Database', () => { + const chance = new Chance() + + it('Should not initialize the Halfises database anytime when we are not creating the database.', async () => { + await handler({ RequestType: chance.word({ syllables: 4 }) } as any) + + expect(putItemMock).not.toHaveBeenCalled() + }) + + it('Should initialize the Halfises database with a balance of zero.', async () => { + const tableName = chance.word() + + process.env.balanceTableName = tableName + await handler({ RequestType: 'Create' } as any) + + expect(putItemMock).toHaveBeenCalledWith({ + TableName: tableName, + Item: generateNewBalanceDbItem(0), + }) + }) +}) diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts new file mode 100755 index 0000000..fe832d8 --- /dev/null +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -0,0 +1,18 @@ +import { CloudFormationCustomResourceEvent } from 'aws-lambda' +import { DynamoDB } from 'aws-sdk' +import { generateNewBalanceDbItem } from '../../utils' + +export const handler = async (event: CloudFormationCustomResourceEvent) => { + const { balanceTableName = '' } = process.env + + if (event.RequestType === 'Create') { + const dynamoDb = new DynamoDB() + + const params = { + TableName: balanceTableName, + Item: generateNewBalanceDbItem(0), + } + + await dynamoDb.putItem(params).promise() + } +} diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index d0225ee..513f912 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -1,7 +1,7 @@ /* istanbul ignore file */ import { join } from 'path' import { Construct } from 'constructs' -import { Stack } from 'aws-cdk-lib' +import { CustomResource, Stack } from 'aws-cdk-lib' import { CognitoUserPoolsAuthorizer, LambdaIntegration, @@ -14,10 +14,9 @@ import { import { Certificate, ICertificate } from 'aws-cdk-lib/aws-certificatemanager' import { AttributeType, Table } from 'aws-cdk-lib/aws-dynamodb' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' -import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources' import { HALFSIES_NODE_VERSION } from '../../config' import { HalfsiesStackProps } from '../types' -import { createTable, generateNewBalanceDbItem, requiresAuthorization } from '../utils' +import { createTable, requiresAuthorization } from '../utils' // ---------------------------------------------------------------------------------------- // WHEN DELETING THIS IN CLOUD FORMATION @@ -67,26 +66,9 @@ export class HalfsiesStack extends Stack { stack: this, type: AttributeType.NUMBER, }) - - // Initial DB data - new AwsCustomResource(this, `halfsiesBalanceInitData${resourcePostFix}`, { - onCreate: { - service: 'DynamoDB', - action: 'putItem', - parameters: { - TableName: balanceDb.tableName, - Item: generateNewBalanceDbItem(0), - }, - physicalResourceId: PhysicalResourceId.of('initDBData'), - }, - policy: AwsCustomResourcePolicy.fromSdkCalls({ - resources: AwsCustomResourcePolicy.ANY_RESOURCE, - }), - }) - // ---------------------------------------------------------------------------------------- // LAMBDAS - // ---------------------------------------------------------------------------------------- + // ---------------------------------------------------------------------------------------- const createHalfsie: NodejsFunction = new NodejsFunction(this, 'createHalfsie', { functionName: `halfsiesCreateHalfsie${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'createHalfsie.ts'), @@ -120,6 +102,15 @@ export class HalfsiesStack extends Stack { halfsiesLogTableName: logDb.tableName, }, }) + + const initializeHalfsiesDatabase: NodejsFunction = new NodejsFunction(this, 'initializeHalfsiesDatabase', { + functionName: `initializeHalfsiesDatabase${resourcePostFix}`, + handler: 'handler', + runtime: HALFSIES_NODE_VERSION, + environment: { + balanceTableName: balanceDb.tableName, + }, + }) // ---------------------------------------------------------------------------------------- // AUTHORIZATION // ---------------------------------------------------------------------------------------- @@ -155,5 +146,15 @@ export class HalfsiesStack extends Stack { getLogLambdaResource.addMethod('GET', getLogLambdaIntegration, requiresAuthorization(authorizer)) logDb.grantReadData(getLog) + + // initialize halfsies database + balanceDb.grantWriteData(initializeHalfsiesDatabase) + + // ---------------------------------------------------------------------------------------- + // CUSTOM RESOURCES + // --------------------------------------------------------------------------------------- + new CustomResource(this, `initializeHalfsiesDatabase${resourcePostFix}`, { + serviceToken: initializeHalfsiesDatabase.functionArn, + }) } } diff --git a/yarn.lock b/yarn.lock index 021acfa..35438fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,20 +15,23 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@aws-cdk/asset-awscli-v1@^2.2.202": - version "2.2.202" - resolved "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz" - integrity sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg== +"@aws-cdk/asset-awscli-v1@^2.2.229": + version "2.2.232" + resolved "https://registry.yarnpkg.com/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.232.tgz#afae709ee662c2ab99f5e5ef848d513fde065652" + integrity sha512-x9aFQG9gA+RgGj9bGB+WC6y1Nq2/Y8R2yXFoKWoQZOet8PRFJ8M5/FeXoh9XmdWI4weJVctLU4WTIve6rOvPtA== -"@aws-cdk/asset-kubectl-v20@^2.1.2": - version "2.1.2" - resolved "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz" - integrity sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg== +"@aws-cdk/asset-node-proxy-agent-v6@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz#6d3c7860354d4856a7e75375f2f0ecab313b4989" + integrity sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A== -"@aws-cdk/asset-node-proxy-agent-v6@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.3.tgz#9b5d213b5ce5ad4461f6a4720195ff8de72e6523" - integrity sha512-twhuEG+JPOYCYPx/xy5uH2+VUsIEhPTzDY0F1KuB+ocjWWB/KEDiOVL19nHvbPCB6fhWnkykXEMJ4HHcKvjtvg== +"@aws-cdk/cloud-assembly-schema@^41.0.0": + version "41.2.0" + resolved "https://registry.yarnpkg.com/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz#c1ef513e1cc0528dbc05948ae39d5631306af423" + integrity sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw== + dependencies: + jsonschema "~1.4.1" + semver "^7.7.1" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": version "7.21.4" @@ -1296,30 +1299,30 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -aws-cdk-lib@2.139.0: - version "2.139.0" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.139.0.tgz#bee393c979d74cf58c087850ce896df145b04776" - integrity sha512-G9yoc+VFwF10kpgf4omtrAVmUNPeAP708oF5fc7XlRTzoTXMmAdUJW9cRGOMtAkFY83SxiJP0wm8n5Z9tjAdUA== +aws-cdk-lib@2.190.0: + version "2.190.0" + resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.190.0.tgz#a439b4c356ef63b3bb5e4ecb945dc7be3ab914b9" + integrity sha512-D6BGf0Gg4s3XCnNiXnCgH1NHXYjngizs676HeytI4ekrUMtsw1ZmH9dlFBattH1x9gYX/9A+UxMkid+P4bNZKA== dependencies: - "@aws-cdk/asset-awscli-v1" "^2.2.202" - "@aws-cdk/asset-kubectl-v20" "^2.1.2" - "@aws-cdk/asset-node-proxy-agent-v6" "^2.0.3" + "@aws-cdk/asset-awscli-v1" "^2.2.229" + "@aws-cdk/asset-node-proxy-agent-v6" "^2.1.0" + "@aws-cdk/cloud-assembly-schema" "^41.0.0" "@balena/dockerignore" "^1.0.2" case "1.6.3" - fs-extra "^11.2.0" - ignore "^5.3.1" - jsonschema "^1.4.1" + fs-extra "^11.3.0" + ignore "^5.3.2" + jsonschema "^1.5.0" mime-types "^2.1.35" minimatch "^3.1.2" punycode "^2.3.1" - semver "^7.6.0" - table "^6.8.2" + semver "^7.7.1" + table "^6.9.0" yaml "1.10.2" -aws-cdk@2.139.0: - version "2.139.0" - resolved "https://registry.yarnpkg.com/aws-cdk/-/aws-cdk-2.139.0.tgz#439e20bd08fba9fe4ee627a85641f28f219e3330" - integrity sha512-MjMsySQbhR5yTWCphnuVQuS15UdGMV6v4XIM+C8SN7/eUOfv7BFr7QgYMUm5WXCG/f66RnY0zjJbOLRxvcjCrQ== +aws-cdk@2.1010.0: + version "2.1010.0" + resolved "https://registry.yarnpkg.com/aws-cdk/-/aws-cdk-2.1010.0.tgz#1797a62d63567273a4b08e7e43952af6babaf768" + integrity sha512-kYNzBXVUZoRrTuYxRRA2Loz/Uvay0MqHobg8KPZaWylIbw/meUDgtoATRNt+stOdJ9PHODTjWmlDKI+2/KoF+w== optionalDependencies: fsevents "2.3.2" @@ -1333,10 +1336,10 @@ aws-lambda@1.0.7: js-yaml "^3.14.1" watchpack "^2.0.0-beta.10" -aws-sdk@2.1606.0: - version "2.1606.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1606.0.tgz#617b2afc5955aca4ac42c6ef6f5b649cbd36b3b1" - integrity sha512-Sg1y0p93XI0ce86D9mi402sKMiH7LDoq6k3YzMja9yA0i3i0Pg3py4jjZ7p3HlKNOsDUF0t+9DLdCjtbTbuM6w== +aws-sdk@2.1692.0: + version "2.1692.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1692.0.tgz#9dac5f7bfcc5ab45825cc8591b12753aa7d2902c" + integrity sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw== dependencies: buffer "4.9.2" events "1.1.1" @@ -2377,10 +2380,10 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -fs-extra@^11.2.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" - integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== +fs-extra@^11.3.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -2652,10 +2655,10 @@ ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== -ignore@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +ignore@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== import-fresh@^3.2.1: version "3.3.0" @@ -3475,9 +3478,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonschema@^1.4.1: +jsonschema@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.5.0.tgz#f6aceb1ab9123563dd901d05f81f9d4883d3b7d8" + integrity sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw== + +jsonschema@~1.4.1: version "1.4.1" - resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: @@ -4143,12 +4151,10 @@ semver@^7.5.3, semver@^7.5.4: dependencies: lru-cache "^6.0.0" -semver@^7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" +semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== set-function-length@^1.1.1: version "1.2.0" @@ -4393,10 +4399,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -table@^6.8.2: - version "6.8.2" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" - integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== +table@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" + integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" From b575a442030160f4562de27b464053553c52964f Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Mon, 21 Apr 2025 18:36:39 -0500 Subject: [PATCH 02/11] Fixed deploy issues with global fetch. --- package.json | 10 ++-- .../initializeHalfsiesDatabase.test.ts | 46 ++++++++++++++++- .../halfsies/initializeHalfsiesDatabase.ts | 18 ++++++- src/stacks/halfsies.ts | 5 +- src/utils/index.ts | 1 + src/utils/lambdas.ts | 50 +++++++++++++++++++ yarn.lock | 50 +++++++++---------- 7 files changed, 144 insertions(+), 36 deletions(-) create mode 100755 src/utils/lambdas.ts diff --git a/package.json b/package.json index fe37759..52dcea4 100755 --- a/package.json +++ b/package.json @@ -16,19 +16,19 @@ }, "dependencies": { "aws-sdk": "2.1692.0", - "constructs": "10.3.0", + "constructs": "10.4.2", "source-map-support": "0.5.21" }, "devDependencies": { "@troyblank/eslint-config-troyblank": "2.4.0", "@types/chance": "1.1.6", - "@types/aws-lambda": "8.10.137", - "@types/jest": "29.5.12", - "@types/node": "20.12.7", + "@types/aws-lambda": "8.10.149", + "@types/jest": "29.5.14", + "@types/node": "22.14.1", "aws-cdk": "2.1010.0", "aws-cdk-lib": "2.190.0", "aws-lambda": "1.0.7", - "chance": "1.1.11", + "chance": "1.1.12", "esbuild": "0.20.2", "jest": "29.7.0", "ts-jest": "29.1.2", diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts index 589038b..3bb34d1 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts @@ -5,20 +5,61 @@ const putItemMock = jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue({}), }) -DynamoDB.prototype.putItem = putItemMock +const alreadyHasDataPutItemMock = jest.fn().mockReturnValue({ + promise: jest.fn().mockRejectedValue({ code: 'ConditionalCheckFailedException' }), +}) + +const errorPutItemMock = jest.fn().mockReturnValue({ + promise: jest.fn().mockRejectedValue({ code: '' }), +}) import { handler } from './initializeHalfsiesDatabase' import { generateNewBalanceDbItem } from '../../utils' +jest.mock('../../utils', () => ({ + ...jest.requireActual('../../utils'), + sendCustomResourceLambdaResponse: jest.fn(), +})) + + describe('Lambda - Initialize Halfsies Database', () => { const chance = new Chance() - it('Should not initialize the Halfises database anytime when we are not creating the database.', async () => { + beforeEach(() => { + DynamoDB.prototype.putItem = putItemMock + }) + + it('Should not initialize the Halfises database anytime when we are not creating the lambda.', async () => { await handler({ RequestType: chance.word({ syllables: 4 }) } as any) expect(putItemMock).not.toHaveBeenCalled() }) + it('Should not initialize the Halfises database anytime when there is already data in the database.', async () => { + DynamoDB.prototype.putItem = alreadyHasDataPutItemMock + + jest.spyOn(console, 'warn').mockImplementation(() => {}) + + const tableName = chance.word() + + process.env.balanceTableName = tableName + + await handler({ RequestType: 'Create' } as any) + + // eslint-disable-next-line no-console + expect(console.warn).toHaveBeenCalledWith('Halfsies balance amount already exists, skipping initialization.') + }) + + it('Should throw an error if there is any issues putting the value in the database.', async () => { + DynamoDB.prototype.putItem = errorPutItemMock + + const tableName = chance.word() + + process.env.balanceTableName = tableName + + expect(async() => await handler({ RequestType: 'Create' } as any)).toThrowError() + }) + it('Should initialize the Halfises database with a balance of zero.', async () => { const tableName = chance.word() @@ -26,6 +67,7 @@ describe('Lambda - Initialize Halfsies Database', () => { await handler({ RequestType: 'Create' } as any) expect(putItemMock).toHaveBeenCalledWith({ + ConditionExpression: 'attribute_not_exists(id)', TableName: tableName, Item: generateNewBalanceDbItem(0), }) diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts index fe832d8..4c3c84b 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -1,6 +1,6 @@ import { CloudFormationCustomResourceEvent } from 'aws-lambda' import { DynamoDB } from 'aws-sdk' -import { generateNewBalanceDbItem } from '../../utils' +import { generateNewBalanceDbItem, sendCustomResourceLambdaResponse } from '../../utils' export const handler = async (event: CloudFormationCustomResourceEvent) => { const { balanceTableName = '' } = process.env @@ -11,8 +11,22 @@ export const handler = async (event: CloudFormationCustomResourceEvent) => { const params = { TableName: balanceTableName, Item: generateNewBalanceDbItem(0), + // Ensures the value is only inserted if it doesn't already exist. + ConditionExpression: 'attribute_not_exists(id)', } - await dynamoDb.putItem(params).promise() + try { + await dynamoDb.putItem(params).promise() + } catch (error: any) { + if (error.code === 'ConditionalCheckFailedException') { + // This error is expected on any stack update where the balance db has data already. + // eslint-disable-next-line no-console + console.warn('Halfsies balance amount already exists, skipping initialization.') + } else { + throw new Error(error) + } + } + + await sendCustomResourceLambdaResponse(event, true) } } diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index 513f912..2134e6f 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -21,7 +21,7 @@ import { createTable, requiresAuthorization } from '../utils' // ---------------------------------------------------------------------------------------- // WHEN DELETING THIS IN CLOUD FORMATION // ---------------------------------------------------------------------------------------- -// 1. Be sure to remove any DNS records made under your custom domain name on your your domain provider's. (You will have to re-add this if re-deploy - your cert is on us-east-1). +// 1. Be sure to remove the api DNS records made under your custom domain name on your your domain provider's. (You will have to re-add this if re-deploy - your cert is on us-east-1). // 2. Be sure to remove all DB tables from DynamoDB associated with this stack. export class HalfsiesStack extends Stack { @@ -104,7 +104,8 @@ export class HalfsiesStack extends Stack { }) const initializeHalfsiesDatabase: NodejsFunction = new NodejsFunction(this, 'initializeHalfsiesDatabase', { - functionName: `initializeHalfsiesDatabase${resourcePostFix}`, + functionName: `halfsiesInitializeHalfsiesDatabase${resourcePostFix}`, + entry: join(__dirname, '../lambdas', 'halfsies', 'initializeHalfsiesDatabase.ts'), handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { diff --git a/src/utils/index.ts b/src/utils/index.ts index 3a1211d..fddcd2a 100755 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,4 +2,5 @@ export * from './auth' export * from './halfsies' export * from './jwt' +export * from './lambdas' export * from './tables' diff --git a/src/utils/lambdas.ts b/src/utils/lambdas.ts new file mode 100755 index 0000000..ae3bc5b --- /dev/null +++ b/src/utils/lambdas.ts @@ -0,0 +1,50 @@ +// THIS NEEDS UNIT TESTING +import { CloudFormationCustomResourceEvent } from 'aws-lambda' +import { request } from 'https' + +// When using lambdas as custom resources in CloudFormation, you must return a response to the custom resource request. +// This is done by sending a response to the URL provided in the event object. +// The following util function handles allows for this. +export const sendCustomResourceLambdaResponse = async ( + event: CloudFormationCustomResourceEvent, + isSuccess: boolean, + errorMessage?: string, +) => { + const physicalResourceId = 'PhysicalResourceId' in event + ? event.PhysicalResourceId + : event.LogicalResourceId + + const data: { error?: string } = errorMessage ? { error: errorMessage } : {} + + const responseBody = JSON.stringify({ + Status: isSuccess ? 'SUCCESS' : 'FAILED', + Reason: !isSuccess ? JSON.stringify(data) : 'See CloudWatch Logs', + PhysicalResourceId: physicalResourceId, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + Data: data, + }) + + await new Promise((resolve, reject) => { + const { hostname, pathname, search } = new URL(event.ResponseURL) + + const req = request( + { + hostname: hostname, + path: `${pathname}${search}`, + method: 'PUT', + headers: { + 'Content-Type': '', // CloudFormation expects this to be an empty string + 'Content-Length': Buffer.byteLength(responseBody).toString(), + }, + }, ({ on }: any) => { + on('end', resolve) + }, + ) + + req.on('error', reject) + req.write(responseBody) + req.end() + }) +} diff --git a/yarn.lock b/yarn.lock index 35438fe..8448b0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -846,10 +846,10 @@ resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz" integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== -"@types/aws-lambda@8.10.137": - version "8.10.137" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.137.tgz#c9998a944541afdd6df0d159e9ec9c23dfe5fb40" - integrity sha512-YNFwzVarXAOXkjuFxONyDw1vgRNzyH8AuyN19s0bM+ChSu/bzxb5XPxYFLXoqoM+tvgzwR3k7fXcEOW125yJxg== +"@types/aws-lambda@8.10.149": + version "8.10.149" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.149.tgz#77c7bde809425546d03626e51bab8181bc5d24c9" + integrity sha512-NXSZIhfJjnXqJgtS7IwutqIF/SOy1Wz5Px4gUY1RWITp3AYTyuJS4xaXr/bIJY1v15XMzrJ5soGnPM+7uigZjA== "@types/babel__core@^7.1.14": version "7.20.0" @@ -915,10 +915,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@29.5.12": - version "29.5.12" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" - integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== +"@types/jest@29.5.14": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -943,12 +943,12 @@ resolved "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz" integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg== -"@types/node@20.12.7": - version "20.12.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" - integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== +"@types/node@22.14.1": + version "22.14.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.14.1.tgz#53b54585cec81c21eee3697521e31312d6ca1e6f" + integrity sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw== dependencies: - undici-types "~5.26.4" + undici-types "~6.21.0" "@types/semver@^7.3.12": version "7.3.13" @@ -1562,10 +1562,10 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chance@1.1.11: - version "1.1.11" - resolved "https://registry.npmjs.org/chance/-/chance-1.1.11.tgz" - integrity sha512-kqTg3WWywappJPqtgrdvbA380VoXO2eu9VCV895JgbyHsaErXdyHK9LOZ911OvAk6L0obK7kDk9CGs8+oBawVA== +chance@1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.12.tgz#6a263cf241674af50a1b903357f9d328a6f252fb" + integrity sha512-vVBIGQVnwtUG+SYe0ge+3MvF78cvSpuCOEUJr7sVEk2vSBuMW6OXNJjSzdtzrlxNUEaoqH2GBd5Y/+18BEB01Q== char-regex@^1.0.2: version "1.0.2" @@ -1635,10 +1635,10 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -constructs@10.3.0: - version "10.3.0" - resolved "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz" - integrity sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ== +constructs@10.4.2: + version "10.4.2" + resolved "https://registry.yarnpkg.com/constructs/-/constructs-10.4.2.tgz#e875a78bef932cca12ea63965969873a25c1c132" + integrity sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA== convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" @@ -4558,10 +4558,10 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== universalify@^2.0.0: version "2.0.1" From fe260c21bd0ea64cc2d99ca6070f7d53e62e0b4e Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Mon, 21 Apr 2025 22:21:23 -0500 Subject: [PATCH 03/11] Adds CORS preflight. --- src/stacks/halfsies.ts | 5 ++++- src/utils/apiGateway.ts | 34 ++++++++++++++++++++++++++++++++++ src/utils/index.ts | 1 + 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100755 src/utils/apiGateway.ts diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index 2134e6f..e6e2caa 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -16,7 +16,7 @@ import { AttributeType, Table } from 'aws-cdk-lib/aws-dynamodb' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import { HALFSIES_NODE_VERSION } from '../../config' import { HalfsiesStackProps } from '../types' -import { createTable, requiresAuthorization } from '../utils' +import { addCorsOptions, createTable, requiresAuthorization } from '../utils' // ---------------------------------------------------------------------------------------- // WHEN DELETING THIS IN CLOUD FORMATION @@ -133,6 +133,7 @@ export class HalfsiesStack extends Stack { createHalfsieLambdaResource.addMethod('POST', createHalfsieLambdaIntegration, requiresAuthorization(authorizer)) balanceDb.grantReadWriteData(createHalfsie) logDb.grantReadWriteData(createHalfsie) + addCorsOptions(createHalfsieLambdaResource, accessControlAllowOrigin) // getBalance const getBalanceLambdaIntegration: LambdaIntegration = new LambdaIntegration(getBalance) @@ -140,6 +141,7 @@ export class HalfsiesStack extends Stack { getBalanceLambdaResource.addMethod('GET', getBalanceLambdaIntegration, requiresAuthorization(authorizer)) balanceDb.grantReadData(getBalance) + addCorsOptions(getBalanceLambdaResource, accessControlAllowOrigin) // getLog const getLogLambdaIntegration: LambdaIntegration = new LambdaIntegration(getLog) @@ -147,6 +149,7 @@ export class HalfsiesStack extends Stack { getLogLambdaResource.addMethod('GET', getLogLambdaIntegration, requiresAuthorization(authorizer)) logDb.grantReadData(getLog) + addCorsOptions(getLogLambdaResource, accessControlAllowOrigin) // initialize halfsies database balanceDb.grantWriteData(initializeHalfsiesDatabase) diff --git a/src/utils/apiGateway.ts b/src/utils/apiGateway.ts new file mode 100755 index 0000000..712efac --- /dev/null +++ b/src/utils/apiGateway.ts @@ -0,0 +1,34 @@ +import { Resource, MockIntegration } from 'aws-cdk-lib/aws-apigateway' + +export function addCorsOptions(resource: Resource, accessControlAllowOrigin: string): void { + resource.addMethod( + 'OPTIONS', + new MockIntegration({ + integrationResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': `'Content-Type,X-Amz-Date,Authorization,X-Api-Key'`, + 'method.response.header.Access-Control-Allow-Origin': `'${accessControlAllowOrigin}'`, + 'method.response.header.Access-Control-Allow-Methods': `'GET,POST,OPTIONS'`, + }, + }, + ], + requestTemplates: { + 'application/json': '{"statusCode": 200}', + }, + }), + { + methodResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': true, + 'method.response.header.Access-Control-Allow-Origin': true, + 'method.response.header.Access-Control-Allow-Methods': true, + }, + }, + ], + }, + ) +} diff --git a/src/utils/index.ts b/src/utils/index.ts index fddcd2a..e9dcb6d 100755 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,5 @@ /* istanbul ignore file */ +export * from './apiGateway' export * from './auth' export * from './halfsies' export * from './jwt' From 5380d944de1bddc165eee77a5854fe600527576d Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 25 Apr 2025 17:07:48 -0500 Subject: [PATCH 04/11] moved to v3 of aws cdk --- package.json | 6 +- src/lambdas/halfsies/getLog.ts | 5 +- src/lambdas/halfsies/utils/log.ts | 108 ++-- src/stacks/halfsies.ts | 20 + src/types/lambdas/utils.ts | 2 +- src/utils/halfsies/log.ts | 7 +- yarn.lock | 907 +++++++++++++++++++++++++++++- 7 files changed, 994 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 52dcea4..5c52b5b 100755 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "deploy": "cdk deploy --all" }, "dependencies": { + "@aws-sdk/client-dynamodb": "3.797.0", + "@aws-sdk/lib-dynamodb": "3.797.0", "aws-sdk": "2.1692.0", "constructs": "10.4.2", "source-map-support": "0.5.21" @@ -25,8 +27,8 @@ "@types/aws-lambda": "8.10.149", "@types/jest": "29.5.14", "@types/node": "22.14.1", - "aws-cdk": "2.1010.0", - "aws-cdk-lib": "2.190.0", + "aws-cdk": "2.1012.0", + "aws-cdk-lib": "2.192.0", "aws-lambda": "1.0.7", "chance": "1.1.12", "esbuild": "0.20.2", diff --git a/src/lambdas/halfsies/getLog.ts b/src/lambdas/halfsies/getLog.ts index ffee492..f41f66e 100755 --- a/src/lambdas/halfsies/getLog.ts +++ b/src/lambdas/halfsies/getLog.ts @@ -1,5 +1,4 @@ -import { AWSError } from 'aws-sdk' -import { APIGatewayProxyResult } from 'aws-lambda' +import { type APIGatewayProxyResult } from 'aws-lambda' import { type DatabaseResponse } from '../../types' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' import { sortLogs } from '../../utils' @@ -23,7 +22,7 @@ export const handler = (): Promise => new Promise((resolv } result.body = JSON.stringify({ message: errorMessage, log: log && sortLogs(log) }) - }).catch((error: AWSError) => { + }).catch((error) => { result.statusCode = RESPONSE_CODE_SERVER_ERROR result.body = JSON.stringify({ message: error.toString() }) }).finally(() => { diff --git a/src/lambdas/halfsies/utils/log.ts b/src/lambdas/halfsies/utils/log.ts index 83ca110..33979a2 100755 --- a/src/lambdas/halfsies/utils/log.ts +++ b/src/lambdas/halfsies/utils/log.ts @@ -1,64 +1,86 @@ -import { AWSError, DynamoDB } from 'aws-sdk' -import { type ScanOutput } from 'aws-sdk/clients/dynamodb' +import { + DeleteItemCommand, + DeleteItemCommandInput, + DynamoDBClient, +} from '@aws-sdk/client-dynamodb' +import { + DynamoDBDocumentClient, + PutCommand, + ScanCommand, + type ScanCommandOutput, +} from "@aws-sdk/lib-dynamodb" +import { marshall } from '@aws-sdk/util-dynamodb' import { type DatabaseResponse, type NewLog } from '../../../types' -export const getLog = async (): Promise => new Promise((resolve) => { +const dynamoDbClient = new DynamoDBClient() + +export const getLog = async (): Promise => { + const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) const { halfsiesLogTableName = '' } = process.env - const dynamoDbClient = new DynamoDB.DocumentClient() - const handleDbReturn = (error: AWSError, data: ScanOutput) => { - resolve({ + try { + const data: ScanCommandOutput = await dynamoDocumentClient.send(new ScanCommand({ + TableName: halfsiesLogTableName, + })) + + return { + isError: false, data: data.Items, - errorMessage: error?.message, - isError: Boolean(error), - }) + } + } catch (error: unknown) { + return { + isError: true, + errorMessage: error instanceof Error ? error.message : "Unknown error occurred", + } } +} - dynamoDbClient.scan({ TableName: halfsiesLogTableName }, handleDbReturn) -}) - -export const saveLog = async (log: NewLog, userName: string): Promise => new Promise((resolve) => { +export const saveLog = async (log: NewLog, userName: string): Promise => { + const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) const { halfsiesLogTableName = '' } = process.env const { amount, description } = log - const dynamoDbClient = new DynamoDB.DocumentClient() const now: Date = new Date() - const dbQueryParams: DynamoDB.DocumentClient.PutItemInput = { - TableName: halfsiesLogTableName, - Item: { - amount, - date: now.toISOString(), - description, - id: now.getTime(), - user: userName, - }, - } + try { + await dynamoDocumentClient.send(new PutCommand({ + TableName: halfsiesLogTableName, + Item: { + amount, + date: now.toISOString(), + description, + id: now.getTime(), + user: userName, + }, + })) - const handleDbReturn = (error: AWSError) => { - resolve({ - errorMessage: error?.message, - isError: Boolean(error), - }) + return { + isError: false, + } + } catch (error: unknown) { + return { + isError: true, + errorMessage: error instanceof Error ? error.message : "Unknown error occurred", + } } +} - dynamoDbClient.put(dbQueryParams, handleDbReturn) -}) - -export const deleteLog = async (id: number): Promise => new Promise((resolve) => { +export const deleteLog = async (id: number): Promise => { const { halfsiesLogTableName = '' } = process.env - const dynamoDbClient = new DynamoDB.DocumentClient() - const dbQueryParams: DynamoDB.DocumentClient.DeleteItemInput = { + const dbQueryParams: DeleteItemCommandInput = { TableName: halfsiesLogTableName, - Key: { id }, + Key: marshall({ id }), } - const handleDbReturn = (error: AWSError) => { - resolve({ + try { + await dynamoDbClient.send(new DeleteItemCommand(dbQueryParams)) + return { + isError: false, + } + } catch (error: any) { + return { errorMessage: error?.message, - isError: Boolean(error), - }) + isError: true, + } } - - dynamoDbClient.delete(dbQueryParams, handleDbReturn) -}) +} diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index e6e2caa..2b3ff6e 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -72,6 +72,11 @@ export class HalfsiesStack extends Stack { const createHalfsie: NodejsFunction = new NodejsFunction(this, 'createHalfsie', { functionName: `halfsiesCreateHalfsie${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'createHalfsie.ts'), + bundling: { + externalModules: ['aws-sdk'], + minify: true, + sourceMap: false, + }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { @@ -84,6 +89,11 @@ export class HalfsiesStack extends Stack { const getBalance: NodejsFunction = new NodejsFunction(this, 'getBalance', { functionName: `halfsiesGetBalance${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getBalance.ts'), + bundling: { + externalModules: ['aws-sdk'], + minify: true, + sourceMap: false, + }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { @@ -95,6 +105,11 @@ export class HalfsiesStack extends Stack { const getLog: NodejsFunction = new NodejsFunction(this, 'getLog', { functionName: `halfsiesGetLog${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getLog.ts'), + bundling: { + externalModules: ['aws-sdk'], + minify: true, + sourceMap: false, + }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { @@ -106,6 +121,11 @@ export class HalfsiesStack extends Stack { const initializeHalfsiesDatabase: NodejsFunction = new NodejsFunction(this, 'initializeHalfsiesDatabase', { functionName: `halfsiesInitializeHalfsiesDatabase${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'initializeHalfsiesDatabase.ts'), + bundling: { + externalModules: ['aws-sdk'], + minify: true, + sourceMap: false, + }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { diff --git a/src/types/lambdas/utils.ts b/src/types/lambdas/utils.ts index 64c5e09..2da32ac 100755 --- a/src/types/lambdas/utils.ts +++ b/src/types/lambdas/utils.ts @@ -1,5 +1,5 @@ export type DatabaseResponse = { data?: any, - errorMessage: string | undefined, + errorMessage?: string, isError: boolean, } diff --git a/src/utils/halfsies/log.ts b/src/utils/halfsies/log.ts index 0908021..896a0f8 100755 --- a/src/utils/halfsies/log.ts +++ b/src/utils/halfsies/log.ts @@ -1,4 +1,3 @@ -import { AWSError } from 'aws-sdk' import { HALFSIES_MAX_LOGS } from '../../../config' import { type DatabaseResponse, type HalfsieLog } from '../../types' import { deleteLog, getLog } from '../../lambdas/halfsies/utils' @@ -50,15 +49,15 @@ export const pruneLogs = async (): Promise => new Promise((res isError: false, errorMessage: '', }) - }).catch((error: AWSError) => { + }).catch((error) => { resolve({ errorMessage: error?.message, isError: true, }) }) - }).catch((error: AWSError) => { + }).catch((error) => { resolve({ - errorMessage: error?.message, + errorMessage: error instanceof Error ? error.message : "Unknown error occurred", isError: true, }) }) diff --git a/yarn.lock b/yarn.lock index 8448b0d..7ea50d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,6 +33,451 @@ jsonschema "~1.4.1" semver "^7.7.1" +"@aws-crypto/sha256-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" + integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + dependencies: + "@aws-crypto/sha256-js" "^5.2.0" + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" + integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/supports-web-crypto@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" + integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + dependencies: + tslib "^2.6.2" + +"@aws-crypto/util@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" + integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + dependencies: + "@aws-sdk/types" "^3.222.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-dynamodb@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-dynamodb/-/client-dynamodb-3.797.0.tgz#f5a6dddf6d007449c6bed5eb999684ea5c812bff" + integrity sha512-vIJAG7ReBfDcnFmsAEM4sNBwDkVA7Bmzi7+xRWIbh9+jWHjy85aC1WAAFriof1JGhpS0+1WnS7hqe9fi6Es5aA== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.796.0" + "@aws-sdk/credential-provider-node" "3.797.0" + "@aws-sdk/middleware-endpoint-discovery" "3.775.0" + "@aws-sdk/middleware-host-header" "3.775.0" + "@aws-sdk/middleware-logger" "3.775.0" + "@aws-sdk/middleware-recursion-detection" "3.775.0" + "@aws-sdk/middleware-user-agent" "3.796.0" + "@aws-sdk/region-config-resolver" "3.775.0" + "@aws-sdk/types" "3.775.0" + "@aws-sdk/util-endpoints" "3.787.0" + "@aws-sdk/util-user-agent-browser" "3.775.0" + "@aws-sdk/util-user-agent-node" "3.796.0" + "@smithy/config-resolver" "^4.1.0" + "@smithy/core" "^3.2.0" + "@smithy/fetch-http-handler" "^5.0.2" + "@smithy/hash-node" "^4.0.2" + "@smithy/invalid-dependency" "^4.0.2" + "@smithy/middleware-content-length" "^4.0.2" + "@smithy/middleware-endpoint" "^4.1.0" + "@smithy/middleware-retry" "^4.1.0" + "@smithy/middleware-serde" "^4.0.3" + "@smithy/middleware-stack" "^4.0.2" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/node-http-handler" "^4.0.4" + "@smithy/protocol-http" "^5.1.0" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + "@smithy/url-parser" "^4.0.2" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.8" + "@smithy/util-defaults-mode-node" "^4.0.8" + "@smithy/util-endpoints" "^3.0.2" + "@smithy/util-middleware" "^4.0.2" + "@smithy/util-retry" "^4.0.2" + "@smithy/util-utf8" "^4.0.0" + "@smithy/util-waiter" "^4.0.3" + "@types/uuid" "^9.0.1" + tslib "^2.6.2" + uuid "^9.0.1" + +"@aws-sdk/client-sso@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.797.0.tgz#94d123568830788980cc8712a94d3b20de735e4c" + integrity sha512-9xuR918p7tShR67ZL+AOSbydpJxSHAOdXcQswxxWR/hKCF7tULX7tyL3gNo3l/ETp0CDcStvorOdH/nCbzEOjw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.796.0" + "@aws-sdk/middleware-host-header" "3.775.0" + "@aws-sdk/middleware-logger" "3.775.0" + "@aws-sdk/middleware-recursion-detection" "3.775.0" + "@aws-sdk/middleware-user-agent" "3.796.0" + "@aws-sdk/region-config-resolver" "3.775.0" + "@aws-sdk/types" "3.775.0" + "@aws-sdk/util-endpoints" "3.787.0" + "@aws-sdk/util-user-agent-browser" "3.775.0" + "@aws-sdk/util-user-agent-node" "3.796.0" + "@smithy/config-resolver" "^4.1.0" + "@smithy/core" "^3.2.0" + "@smithy/fetch-http-handler" "^5.0.2" + "@smithy/hash-node" "^4.0.2" + "@smithy/invalid-dependency" "^4.0.2" + "@smithy/middleware-content-length" "^4.0.2" + "@smithy/middleware-endpoint" "^4.1.0" + "@smithy/middleware-retry" "^4.1.0" + "@smithy/middleware-serde" "^4.0.3" + "@smithy/middleware-stack" "^4.0.2" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/node-http-handler" "^4.0.4" + "@smithy/protocol-http" "^5.1.0" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + "@smithy/url-parser" "^4.0.2" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.8" + "@smithy/util-defaults-mode-node" "^4.0.8" + "@smithy/util-endpoints" "^3.0.2" + "@smithy/util-middleware" "^4.0.2" + "@smithy/util-retry" "^4.0.2" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@aws-sdk/core@3.796.0": + version "3.796.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.796.0.tgz#6076b78772c1eb97ec6ea9064c85ce500e0aa889" + integrity sha512-tH8Sp7lCxISVoLnkyv4AouuXs2CDlMhTuesWa0lq2NX1f+DXsMwSBtN37ttZdpFMw3F8mWdsJt27X9h2Oq868A== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/core" "^3.2.0" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/property-provider" "^4.0.2" + "@smithy/protocol-http" "^5.1.0" + "@smithy/signature-v4" "^5.1.0" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + "@smithy/util-middleware" "^4.0.2" + fast-xml-parser "4.4.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-env@3.796.0": + version "3.796.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.796.0.tgz#4ed6903814868b0f9daa8c8db449b1f1adcda041" + integrity sha512-kQzGKm4IOYYO6vUrai2JocNwhJm4Aml2BsAV+tBhFhhkutE7khf9PUucoVjB78b0J48nF+kdSacqzY+gB81/Uw== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/types" "3.775.0" + "@smithy/property-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-http@3.796.0": + version "3.796.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.796.0.tgz#7f36074021b2605dba4b758b4b0ca98fb5b965ad" + integrity sha512-wWOT6VAHIKOuHdKFGm1iyKvx7f6+Kc/YTzFWJPuT+l+CPlXR6ylP1UMIDsHHLKpMzsrh3CH77QDsjkhQrnKkfg== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/types" "3.775.0" + "@smithy/fetch-http-handler" "^5.0.2" + "@smithy/node-http-handler" "^4.0.4" + "@smithy/property-provider" "^4.0.2" + "@smithy/protocol-http" "^5.1.0" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + "@smithy/util-stream" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-ini@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.797.0.tgz#e617edac6dbd761e13826d49092b85a337bf29a2" + integrity sha512-Zpj6pJ2hnebrhLDr+x61ArMUkjHG6mfJRfamHxeVTgZkhLcwHjC5aM4u9pWTVugIaPY+VBtgkKPbi3TRbHlt2g== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/credential-provider-env" "3.796.0" + "@aws-sdk/credential-provider-http" "3.796.0" + "@aws-sdk/credential-provider-process" "3.796.0" + "@aws-sdk/credential-provider-sso" "3.797.0" + "@aws-sdk/credential-provider-web-identity" "3.797.0" + "@aws-sdk/nested-clients" "3.797.0" + "@aws-sdk/types" "3.775.0" + "@smithy/credential-provider-imds" "^4.0.2" + "@smithy/property-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-node@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.797.0.tgz#bf82e239b14c8fcc7da660c2bc1ec042123638d9" + integrity sha512-xJSWvvnmzEfHbqbpN4F3E3mI9+zJ/VWLGiKOjzX1Inbspa5WqNn2GoMamolZR2TvvZS4F3Hp73TD1WoBzkIjuw== + dependencies: + "@aws-sdk/credential-provider-env" "3.796.0" + "@aws-sdk/credential-provider-http" "3.796.0" + "@aws-sdk/credential-provider-ini" "3.797.0" + "@aws-sdk/credential-provider-process" "3.796.0" + "@aws-sdk/credential-provider-sso" "3.797.0" + "@aws-sdk/credential-provider-web-identity" "3.797.0" + "@aws-sdk/types" "3.775.0" + "@smithy/credential-provider-imds" "^4.0.2" + "@smithy/property-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-process@3.796.0": + version "3.796.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.796.0.tgz#d22d8adf73985fb218ff74365cd86b71bbd64513" + integrity sha512-r4e8/4AdKn/qQbRVocW7oXkpoiuXdTv0qty8AASNLnbQnT1vjD1bvmP6kp4fbHPWgwY8I9h0Dqjp49uy9Bqyuw== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/types" "3.775.0" + "@smithy/property-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-sso@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.797.0.tgz#ea22714498157c937acd3170660b10ae0348366a" + integrity sha512-VlyWnjTsTnBXqXcEW0nw3S7nj00n9fYwF6uU6HPO9t860yIySG01lNPAWTvAt3DfVL5SRS0GANriCZF6ohcMcQ== + dependencies: + "@aws-sdk/client-sso" "3.797.0" + "@aws-sdk/core" "3.796.0" + "@aws-sdk/token-providers" "3.797.0" + "@aws-sdk/types" "3.775.0" + "@smithy/property-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-web-identity@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.797.0.tgz#e52b5a054a71f83ffafe3461b41851d5008d84de" + integrity sha512-DIb05FEmdOX7bNsqSVEAB3UkaDgrYHonQ2+gcBLqZ7LoDNnovHIlvC5jii93usgEStxITZstnzw+49keNEgVWw== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/nested-clients" "3.797.0" + "@aws-sdk/types" "3.775.0" + "@smithy/property-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/endpoint-cache@3.723.0": + version "3.723.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/endpoint-cache/-/endpoint-cache-3.723.0.tgz#6c5984698d3cffca4d55f5c1b14350776ee008ac" + integrity sha512-2+a4WXRc+07uiPR+zJiPGKSOWaNJQNqitkks+6Hhm/haTLJqNVTgY2OWDh2PXvwMNpKB+AlGdhE65Oy6NzUgXg== + dependencies: + mnemonist "0.38.3" + tslib "^2.6.2" + +"@aws-sdk/lib-dynamodb@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.797.0.tgz#b758265ebb1423c4e671dbc40d50f1eedd51c67b" + integrity sha512-T0UxEE4Lj5HjQbHSp3JFHSD5x/BdH0Da8XnTz0WeI67J3JvZhHKji3sh0MydoMtU1aOeWPEsqqR3POWVxT9wng== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/util-dynamodb" "3.797.0" + "@smithy/core" "^3.2.0" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-endpoint-discovery@3.775.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.775.0.tgz#e4fa26718b09559a30004c728dd98cf596fc5916" + integrity sha512-L0PmjSg7t+wovRo/Lin1kpei3e7wBhrENWb1Bbccu3PWUIfxolGeWplOmNhSlXjuQe9GXjf3z8kJRYOGBMFOvw== + dependencies: + "@aws-sdk/endpoint-cache" "3.723.0" + "@aws-sdk/types" "3.775.0" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-host-header@3.775.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz#1bf8160b8f4f96ba30c19f9baa030a6c9bd5f94d" + integrity sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-logger@3.775.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz#df1909d441cd4bade8d6c7d24c41532808db0e81" + integrity sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-recursion-detection@3.775.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz#36a40f467754d7c86424d12ef45c05e96ce3475b" + integrity sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-user-agent@3.796.0": + version "3.796.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.796.0.tgz#80149d7f9034d41d35de88d96a6b73c1cb06413b" + integrity sha512-IeNg+3jNWT37J45opi5Jx89hGF0lOnZjiNwlMp3rKq7PlOqy8kWq5J1Gxk0W3tIkPpuf68CtBs/QFrRXWOjsZw== + dependencies: + "@aws-sdk/core" "3.796.0" + "@aws-sdk/types" "3.775.0" + "@aws-sdk/util-endpoints" "3.787.0" + "@smithy/core" "^3.2.0" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/nested-clients@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.797.0.tgz#59699196a9b3fa43b021bdf1715e531d74885f75" + integrity sha512-xCsRKdsv0GAg9E28fvYBdC3JR2xdtZ2o41MVknOs+pSFtMsZm3SsgxObN35p1OTMk/o/V0LORGVLnFQMlc5QiA== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.796.0" + "@aws-sdk/middleware-host-header" "3.775.0" + "@aws-sdk/middleware-logger" "3.775.0" + "@aws-sdk/middleware-recursion-detection" "3.775.0" + "@aws-sdk/middleware-user-agent" "3.796.0" + "@aws-sdk/region-config-resolver" "3.775.0" + "@aws-sdk/types" "3.775.0" + "@aws-sdk/util-endpoints" "3.787.0" + "@aws-sdk/util-user-agent-browser" "3.775.0" + "@aws-sdk/util-user-agent-node" "3.796.0" + "@smithy/config-resolver" "^4.1.0" + "@smithy/core" "^3.2.0" + "@smithy/fetch-http-handler" "^5.0.2" + "@smithy/hash-node" "^4.0.2" + "@smithy/invalid-dependency" "^4.0.2" + "@smithy/middleware-content-length" "^4.0.2" + "@smithy/middleware-endpoint" "^4.1.0" + "@smithy/middleware-retry" "^4.1.0" + "@smithy/middleware-serde" "^4.0.3" + "@smithy/middleware-stack" "^4.0.2" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/node-http-handler" "^4.0.4" + "@smithy/protocol-http" "^5.1.0" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + "@smithy/url-parser" "^4.0.2" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.8" + "@smithy/util-defaults-mode-node" "^4.0.8" + "@smithy/util-endpoints" "^3.0.2" + "@smithy/util-middleware" "^4.0.2" + "@smithy/util-retry" "^4.0.2" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@aws-sdk/region-config-resolver@3.775.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz#592b52498e68501fe46480be3dfb185e949d1eab" + integrity sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + "@smithy/util-config-provider" "^4.0.0" + "@smithy/util-middleware" "^4.0.2" + tslib "^2.6.2" + +"@aws-sdk/token-providers@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.797.0.tgz#9f8c316f1adfda592c8dc6e69082879896720696" + integrity sha512-TLFkP4BBdkH2zCXhG3JjaYrRft25MMZ+6/YDz1C/ikq2Zk8krUbVoSmhtYMVz10JtxAPiQ++w0vI/qbz2JSDXg== + dependencies: + "@aws-sdk/nested-clients" "3.797.0" + "@aws-sdk/types" "3.775.0" + "@smithy/property-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/types@3.775.0", "@aws-sdk/types@^3.222.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.775.0.tgz#09863a9e68c080947db7c3d226d1c56b8f0f5150" + integrity sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/util-dynamodb@3.797.0": + version "3.797.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-dynamodb/-/util-dynamodb-3.797.0.tgz#7063267363b758fc80aac97b64e1151d6f79aecc" + integrity sha512-fzXXK263JdX4QGKyfxi+hdB49wkRhEhIRYyiXk+mwtDKSQCWCjCpw7bhwJcRCD5JxFwFn8TWkkD9tEZG6f49wA== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-endpoints@3.787.0": + version "3.787.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz#1398f0bd87f19e615ae920c73e16d9d5e5cb76d1" + integrity sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/types" "^4.2.0" + "@smithy/util-endpoints" "^3.0.2" + tslib "^2.6.2" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.723.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz#174551bfdd2eb36d3c16e7023fd7e7ee96ad0fa9" + integrity sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-browser@3.775.0": + version "3.775.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz#b69a1a5548ccc6db1acb3ec115967593ece927a1" + integrity sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A== + dependencies: + "@aws-sdk/types" "3.775.0" + "@smithy/types" "^4.2.0" + bowser "^2.11.0" + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-node@3.796.0": + version "3.796.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.796.0.tgz#72c2ea1f32d2eb1200e111f48fd8a3c005f5202c" + integrity sha512-9fQpNcHgVFitf1tbTT8V1xGRoRHSmOAWjrhevo6Tc0WoINMAKz+4JNqfVGWRE5Tmtpq0oHKo1RmvxXQQtJYciA== + dependencies: + "@aws-sdk/middleware-user-agent" "3.796.0" + "@aws-sdk/types" "3.775.0" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": version "7.21.4" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz" @@ -811,6 +1256,408 @@ dependencies: "@sinonjs/commons" "^2.0.0" +"@smithy/abort-controller@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-4.0.2.tgz#36a23e8cc65fc03cacb6afa35dfbfd319c560c6b" + integrity sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/config-resolver@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-4.1.0.tgz#de1043cbd75f05d99798b0fbcfdaf4b89b0f2f41" + integrity sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A== + dependencies: + "@smithy/node-config-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + "@smithy/util-config-provider" "^4.0.0" + "@smithy/util-middleware" "^4.0.2" + tslib "^2.6.2" + +"@smithy/core@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.2.0.tgz#613b15f76eab9a6be396b1d5453b6bc8f22ba99c" + integrity sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q== + dependencies: + "@smithy/middleware-serde" "^4.0.3" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-middleware" "^4.0.2" + "@smithy/util-stream" "^4.2.0" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@smithy/credential-provider-imds@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz#1ec34a04842fa69996b151a695b027f0486c69a8" + integrity sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w== + dependencies: + "@smithy/node-config-provider" "^4.0.2" + "@smithy/property-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + "@smithy/url-parser" "^4.0.2" + tslib "^2.6.2" + +"@smithy/fetch-http-handler@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz#9d3cacf044aa9573ab933f445ab95cddb284813d" + integrity sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ== + dependencies: + "@smithy/protocol-http" "^5.1.0" + "@smithy/querystring-builder" "^4.0.2" + "@smithy/types" "^4.2.0" + "@smithy/util-base64" "^4.0.0" + tslib "^2.6.2" + +"@smithy/hash-node@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-4.0.2.tgz#a34fe5a33b067d754ca63302b9791778f003e437" + integrity sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg== + dependencies: + "@smithy/types" "^4.2.0" + "@smithy/util-buffer-from" "^4.0.0" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@smithy/invalid-dependency@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz#e9b1c5e407d795f10a03afba90e37bccdc3e38f7" + integrity sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/is-array-buffer@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" + integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + dependencies: + tslib "^2.6.2" + +"@smithy/is-array-buffer@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz#55a939029321fec462bcc574890075cd63e94206" + integrity sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw== + dependencies: + tslib "^2.6.2" + +"@smithy/middleware-content-length@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz#ff78658e8047ad7038f58478cf8713ee2f6ef647" + integrity sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A== + dependencies: + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/middleware-endpoint@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz#cbfe47c5632942c960dbcf71fb02fd0d9985444d" + integrity sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA== + dependencies: + "@smithy/core" "^3.2.0" + "@smithy/middleware-serde" "^4.0.3" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + "@smithy/url-parser" "^4.0.2" + "@smithy/util-middleware" "^4.0.2" + tslib "^2.6.2" + +"@smithy/middleware-retry@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz#338ac1e025bbc6fd7b008152c4efa8bc0591acc9" + integrity sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg== + dependencies: + "@smithy/node-config-provider" "^4.0.2" + "@smithy/protocol-http" "^5.1.0" + "@smithy/service-error-classification" "^4.0.2" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + "@smithy/util-middleware" "^4.0.2" + "@smithy/util-retry" "^4.0.2" + tslib "^2.6.2" + uuid "^9.0.1" + +"@smithy/middleware-serde@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz#b90ef1065ad9dc0b54c561fae73c8a5792d145e3" + integrity sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/middleware-stack@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz#ca7bc3eedc7c1349e2cf94e0dc92a68d681bef18" + integrity sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/node-config-provider@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz#017ba626828bced0fa588e795246e5468632f3ef" + integrity sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw== + dependencies: + "@smithy/property-provider" "^4.0.2" + "@smithy/shared-ini-file-loader" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/node-http-handler@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz#aa583d201c1ee968170b65a07f06d633c214b7a1" + integrity sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g== + dependencies: + "@smithy/abort-controller" "^4.0.2" + "@smithy/protocol-http" "^5.1.0" + "@smithy/querystring-builder" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/property-provider@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.0.2.tgz#4572c10415c9d4215f3df1530ba61b0319b17b55" + integrity sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/protocol-http@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.1.0.tgz#ad34e336a95944785185234bebe2ec8dbe266936" + integrity sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/querystring-builder@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz#834cea95bf413ab417bf9c166d60fd80d2cb3016" + integrity sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q== + dependencies: + "@smithy/types" "^4.2.0" + "@smithy/util-uri-escape" "^4.0.0" + tslib "^2.6.2" + +"@smithy/querystring-parser@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz#d80c5afb740e12ad8b4d4f58415e402c69712479" + integrity sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/service-error-classification@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz#96740ed8be7ac5ad7d6f296d4ddf3f66444b8dcc" + integrity sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ== + dependencies: + "@smithy/types" "^4.2.0" + +"@smithy/shared-ini-file-loader@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz#15043f0516fe09ff4b22982bc5f644dc701ebae5" + integrity sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/signature-v4@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-5.1.0.tgz#2c56e5b278482b04383d84ea2c07b7f0a8eb8f63" + integrity sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w== + dependencies: + "@smithy/is-array-buffer" "^4.0.0" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + "@smithy/util-hex-encoding" "^4.0.0" + "@smithy/util-middleware" "^4.0.2" + "@smithy/util-uri-escape" "^4.0.0" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@smithy/smithy-client@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.2.0.tgz#0c64cae4fb5bb4f26386e9b2c33fc9a3c24c9df3" + integrity sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw== + dependencies: + "@smithy/core" "^3.2.0" + "@smithy/middleware-endpoint" "^4.1.0" + "@smithy/middleware-stack" "^4.0.2" + "@smithy/protocol-http" "^5.1.0" + "@smithy/types" "^4.2.0" + "@smithy/util-stream" "^4.2.0" + tslib "^2.6.2" + +"@smithy/types@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.2.0.tgz#e7998984cc54b1acbc32e6d4cf982c712e3d26b6" + integrity sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-4.0.2.tgz#a316f7d8593ffab796348bc5df96237833880713" + integrity sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ== + dependencies: + "@smithy/querystring-parser" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-base64@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-4.0.0.tgz#8345f1b837e5f636e5f8470c4d1706ae0c6d0358" + integrity sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg== + dependencies: + "@smithy/util-buffer-from" "^4.0.0" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@smithy/util-body-length-browser@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz#965d19109a4b1e5fe7a43f813522cce718036ded" + integrity sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-body-length-node@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz#3db245f6844a9b1e218e30c93305bfe2ffa473b3" + integrity sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-buffer-from@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" + integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + dependencies: + "@smithy/is-array-buffer" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-buffer-from@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz#b23b7deb4f3923e84ef50c8b2c5863d0dbf6c0b9" + integrity sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug== + dependencies: + "@smithy/is-array-buffer" "^4.0.0" + tslib "^2.6.2" + +"@smithy/util-config-provider@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz#e0c7c8124c7fba0b696f78f0bd0ccb060997d45e" + integrity sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w== + dependencies: + tslib "^2.6.2" + +"@smithy/util-defaults-mode-browser@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz#77bc4590cdc928901b80f3482e79607a2cbcb150" + integrity sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ== + dependencies: + "@smithy/property-provider" "^4.0.2" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + bowser "^2.11.0" + tslib "^2.6.2" + +"@smithy/util-defaults-mode-node@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz#123b517efe6434977139b341d1f64b5f1e743aac" + integrity sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA== + dependencies: + "@smithy/config-resolver" "^4.1.0" + "@smithy/credential-provider-imds" "^4.0.2" + "@smithy/node-config-provider" "^4.0.2" + "@smithy/property-provider" "^4.0.2" + "@smithy/smithy-client" "^4.2.0" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-endpoints@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz#6933a0d6d4a349523ef71ca9540c9c0b222b559e" + integrity sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ== + dependencies: + "@smithy/node-config-provider" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-hex-encoding@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz#dd449a6452cffb37c5b1807ec2525bb4be551e8d" + integrity sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.0.2.tgz#272f1249664e27068ef0d5f967a233bf7b77962c" + integrity sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ== + dependencies: + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-retry@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-4.0.2.tgz#9b64cf460d63555884e641721d19e3c0abff8ee6" + integrity sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg== + dependencies: + "@smithy/service-error-classification" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-stream@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.2.0.tgz#85f85516b0042726162bf619caa3358332195652" + integrity sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ== + dependencies: + "@smithy/fetch-http-handler" "^5.0.2" + "@smithy/node-http-handler" "^4.0.4" + "@smithy/types" "^4.2.0" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-buffer-from" "^4.0.0" + "@smithy/util-hex-encoding" "^4.0.0" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + +"@smithy/util-uri-escape@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz#a96c160c76f3552458a44d8081fade519d214737" + integrity sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-utf8@^2.0.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" + integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-utf8@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-4.0.0.tgz#09ca2d9965e5849e72e347c130f2a29d5c0c863c" + integrity sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow== + dependencies: + "@smithy/util-buffer-from" "^4.0.0" + tslib "^2.6.2" + +"@smithy/util-waiter@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-4.0.3.tgz#ec5605ec123493259ccbf1c0b5c1951b3360f43b" + integrity sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA== + dependencies: + "@smithy/abort-controller" "^4.0.2" + "@smithy/types" "^4.2.0" + tslib "^2.6.2" + "@testing-library/dom@^8.11.1": version "8.20.0" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz" @@ -965,6 +1812,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@^9.0.1": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" @@ -1299,10 +2151,10 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -aws-cdk-lib@2.190.0: - version "2.190.0" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.190.0.tgz#a439b4c356ef63b3bb5e4ecb945dc7be3ab914b9" - integrity sha512-D6BGf0Gg4s3XCnNiXnCgH1NHXYjngizs676HeytI4ekrUMtsw1ZmH9dlFBattH1x9gYX/9A+UxMkid+P4bNZKA== +aws-cdk-lib@2.192.0: + version "2.192.0" + resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.192.0.tgz#e985c168aef99463cc41bf698fec3b5824a6d783" + integrity sha512-Y9BAlr9a4QsEsamKc2cOGzX8DpVSOh94wsrMSGRXT0bZaqmixhhmT7WYCrT1KX4MU3gYk3OiwY2BbNyWaNE8Fg== dependencies: "@aws-cdk/asset-awscli-v1" "^2.2.229" "@aws-cdk/asset-node-proxy-agent-v6" "^2.1.0" @@ -1319,10 +2171,10 @@ aws-cdk-lib@2.190.0: table "^6.9.0" yaml "1.10.2" -aws-cdk@2.1010.0: - version "2.1010.0" - resolved "https://registry.yarnpkg.com/aws-cdk/-/aws-cdk-2.1010.0.tgz#1797a62d63567273a4b08e7e43952af6babaf768" - integrity sha512-kYNzBXVUZoRrTuYxRRA2Loz/Uvay0MqHobg8KPZaWylIbw/meUDgtoATRNt+stOdJ9PHODTjWmlDKI+2/KoF+w== +aws-cdk@2.1012.0: + version "2.1012.0" + resolved "https://registry.yarnpkg.com/aws-cdk/-/aws-cdk-2.1012.0.tgz#9771c991d5c9585c2128486343518ea94cec1677" + integrity sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A== optionalDependencies: fsevents "2.3.2" @@ -1450,6 +2302,11 @@ base64-js@^1.0.2: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -2316,6 +3173,13 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-xml-parser@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== + dependencies: + strnum "^1.0.5" + fastq@^1.6.0: version "1.15.0" resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" @@ -3657,6 +4521,13 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +mnemonist@0.38.3: + version "0.38.3" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.3.tgz#35ec79c1c1f4357cfda2fe264659c2775ccd7d9d" + integrity sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw== + dependencies: + obliterator "^1.6.1" + ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" @@ -3777,6 +4648,11 @@ object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" +obliterator@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" + integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig== + once@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" @@ -4373,6 +5249,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strnum@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.1.2.tgz#57bca4fbaa6f271081715dbc9ed7cee5493e28e4" + integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" @@ -4475,6 +5356,11 @@ tslib@^1.8.1: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.6.2: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" @@ -4607,6 +5493,11 @@ uuid@8.0.0: resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz" integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-to-istanbul@^9.0.1: version "9.1.0" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz" From c3ab182d78f7ab5366692f442362d83fd5babf54 Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 25 Apr 2025 17:30:45 -0500 Subject: [PATCH 05/11] moved to v3 of aws cdk --- package.json | 1 - src/lambdas/halfsies/createHalfsie.ts | 7 +- src/lambdas/halfsies/getBalance.ts | 3 +- .../halfsies/initializeHalfsiesDatabase.ts | 23 +++--- src/lambdas/halfsies/utils/balance.ts | 70 ++++++++++++------- src/lambdas/halfsies/utils/user.ts | 2 +- src/stacks/halfsies.ts | 4 -- yarn.lock | 16 ----- 8 files changed, 62 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index 5c52b5b..1df657e 100755 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "dependencies": { "@aws-sdk/client-dynamodb": "3.797.0", "@aws-sdk/lib-dynamodb": "3.797.0", - "aws-sdk": "2.1692.0", "constructs": "10.4.2", "source-map-support": "0.5.21" }, diff --git a/src/lambdas/halfsies/createHalfsie.ts b/src/lambdas/halfsies/createHalfsie.ts index 1da8bd1..e907bc8 100755 --- a/src/lambdas/halfsies/createHalfsie.ts +++ b/src/lambdas/halfsies/createHalfsie.ts @@ -1,4 +1,3 @@ -import { AWSError } from 'aws-sdk' import { type APIGatewayProxyEvent, type APIGatewayProxyResult } from 'aws-lambda' import { type DatabaseResponse, type NewLog } from '../../types' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' @@ -53,20 +52,20 @@ export const handler = ({ body, headers }: APIGatewayProxyEvent): Promise { + }).catch((error) => { result.statusCode = RESPONSE_CODE_SERVER_ERROR result.body = JSON.stringify({ message: error.toString() }) }).finally(() => { resolve(result) }) } - }).catch((error: AWSError) => { + }).catch((error) => { result.statusCode = RESPONSE_CODE_SERVER_ERROR result.body = JSON.stringify({ message: error.toString() }) resolve(result) }) - }).catch((error: AWSError) => { + }).catch((error) => { result.statusCode = RESPONSE_CODE_SERVER_ERROR result.body = JSON.stringify({ message: error.toString() }) diff --git a/src/lambdas/halfsies/getBalance.ts b/src/lambdas/halfsies/getBalance.ts index 43e3563..4b7e262 100755 --- a/src/lambdas/halfsies/getBalance.ts +++ b/src/lambdas/halfsies/getBalance.ts @@ -1,4 +1,3 @@ -import { AWSError } from 'aws-sdk' import { APIGatewayProxyResult } from 'aws-lambda' import { type DatabaseResponse } from '../../types' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' @@ -22,7 +21,7 @@ export const handler = (): Promise => new Promise((resolv } result.body = JSON.stringify({ message: errorMessage, balance }) - }).catch((error: AWSError) => { + }).catch((error) => { result.statusCode = RESPONSE_CODE_SERVER_ERROR result.body = JSON.stringify({ message: error.toString() }) }).finally(() => { diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts index 4c3c84b..7c36206 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -1,29 +1,32 @@ import { CloudFormationCustomResourceEvent } from 'aws-lambda' -import { DynamoDB } from 'aws-sdk' +import { + DynamoDBClient, + PutItemCommand, + PutItemCommandInput, + ConditionalCheckFailedException, +} from '@aws-sdk/client-dynamodb' import { generateNewBalanceDbItem, sendCustomResourceLambdaResponse } from '../../utils' +const dynamoDbClient = new DynamoDBClient() + export const handler = async (event: CloudFormationCustomResourceEvent) => { const { balanceTableName = '' } = process.env if (event.RequestType === 'Create') { - const dynamoDb = new DynamoDB() - - const params = { + const params: PutItemCommandInput = { TableName: balanceTableName, Item: generateNewBalanceDbItem(0), - // Ensures the value is only inserted if it doesn't already exist. ConditionExpression: 'attribute_not_exists(id)', } try { - await dynamoDb.putItem(params).promise() - } catch (error: any) { - if (error.code === 'ConditionalCheckFailedException') { - // This error is expected on any stack update where the balance db has data already. + await dynamoDbClient.send(new PutItemCommand(params)) + } catch (error) { + if (error instanceof ConditionalCheckFailedException) { // eslint-disable-next-line no-console console.warn('Halfsies balance amount already exists, skipping initialization.') } else { - throw new Error(error) + throw new Error((error as Error).message) } } diff --git a/src/lambdas/halfsies/utils/balance.ts b/src/lambdas/halfsies/utils/balance.ts index 8062f77..cbe265a 100755 --- a/src/lambdas/halfsies/utils/balance.ts +++ b/src/lambdas/halfsies/utils/balance.ts @@ -1,31 +1,44 @@ -import { AWSError, DynamoDB } from 'aws-sdk' -import { type GetItemOutput, type DocumentClient } from 'aws-sdk/clients/dynamodb' +import { DynamoDBClient } from '@aws-sdk/client-dynamodb' +import { + DynamoDBDocumentClient, + GetCommand, + UpdateCommand, + type GetCommandInput, + type UpdateCommandInput, +} from "@aws-sdk/lib-dynamodb" import { type DatabaseResponse } from '../../../types' -export const getBalance = async (): Promise => new Promise((resolve) => { +const dynamoDbClient = new DynamoDBClient() + +export const getBalance = async (): Promise => { + const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) const { balanceTableName = '' } = process.env - const dynamoDbClient = new DynamoDB.DocumentClient() - const dbQueryParams: DynamoDB.DocumentClient.GetItemInput = { + + const params: GetCommandInput = { TableName: balanceTableName, Key: { id: 0 }, } - const handleDbReturn = (error: AWSError, data: GetItemOutput) => { - resolve({ - data: data.Item?.balance, - errorMessage: error?.message, - isError: Boolean(error), - }) - } + try { + const result = await dynamoDocumentClient.send(new GetCommand(params)) - dynamoDbClient.get(dbQueryParams, handleDbReturn) -}) + return { + isError: false, + data: result.Item?.balance, + } + } catch (error) { + return { + isError: true, + errorMessage: (error as Error).message, + } + } +} -export const updateBalance = async (newBalance: number): Promise => new Promise((resolve) => { +export const updateBalance = async (newBalance: number): Promise => { + const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) const { balanceTableName = '' } = process.env - const dynamoDbClient = new DynamoDB.DocumentClient() - const dbQueryParams = { + const params: UpdateCommandInput = { TableName: balanceTableName, Key: { id: 0 }, UpdateExpression: 'set balance = :num', @@ -35,13 +48,18 @@ export const updateBalance = async (newBalance: number): Promise { - resolve({ - data: data.Attributes?.balance, - errorMessage: error?.message, - isError: Boolean(error), - }) - } + try { + const result = await dynamoDocumentClient.send(new UpdateCommand(params)) - dynamoDbClient.update(dbQueryParams, handleDbReturn) -}) + return { + isError: false, + data: result.Attributes?.balance, + errorMessage: undefined, + } + } catch (error) { + return { + isError: true, + errorMessage: (error as Error).message, + } + } +} diff --git a/src/lambdas/halfsies/utils/user.ts b/src/lambdas/halfsies/utils/user.ts index 402c4e9..c14e2ec 100755 --- a/src/lambdas/halfsies/utils/user.ts +++ b/src/lambdas/halfsies/utils/user.ts @@ -1,4 +1,4 @@ -import { APIGatewayProxyEventHeaders } from 'aws-lambda' +import { type APIGatewayProxyEventHeaders } from 'aws-lambda' import { parseJwt } from '../../../utils' export const getUserName = (headers: APIGatewayProxyEventHeaders): string => parseJwt(headers?.Authorization?.split(' ')[1])['cognito:username'] diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index 2b3ff6e..ce6b595 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -73,7 +73,6 @@ export class HalfsiesStack extends Stack { functionName: `halfsiesCreateHalfsie${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'createHalfsie.ts'), bundling: { - externalModules: ['aws-sdk'], minify: true, sourceMap: false, }, @@ -90,7 +89,6 @@ export class HalfsiesStack extends Stack { functionName: `halfsiesGetBalance${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getBalance.ts'), bundling: { - externalModules: ['aws-sdk'], minify: true, sourceMap: false, }, @@ -106,7 +104,6 @@ export class HalfsiesStack extends Stack { functionName: `halfsiesGetLog${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getLog.ts'), bundling: { - externalModules: ['aws-sdk'], minify: true, sourceMap: false, }, @@ -122,7 +119,6 @@ export class HalfsiesStack extends Stack { functionName: `halfsiesInitializeHalfsiesDatabase${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'initializeHalfsiesDatabase.ts'), bundling: { - externalModules: ['aws-sdk'], minify: true, sourceMap: false, }, diff --git a/yarn.lock b/yarn.lock index 7ea50d5..d3223ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2188,22 +2188,6 @@ aws-lambda@1.0.7: js-yaml "^3.14.1" watchpack "^2.0.0-beta.10" -aws-sdk@2.1692.0: - version "2.1692.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1692.0.tgz#9dac5f7bfcc5ab45825cc8591b12753aa7d2902c" - integrity sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - util "^0.12.4" - uuid "8.0.0" - xml2js "0.6.2" - aws-sdk@^2.814.0: version "2.1543.0" resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1543.0.tgz" From b05f0ab5a774dcefd1bbda03fa350588a05055d7 Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 25 Apr 2025 17:38:03 -0500 Subject: [PATCH 06/11] moved to v3 of aws cdk --- src/lambdas/halfsies/getBalance.ts | 2 +- .../halfsies/initializeHalfsiesDatabase.ts | 2 +- src/stacks/halfsies.ts | 32 +++++++++---------- src/utils/lambdas.ts | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lambdas/halfsies/getBalance.ts b/src/lambdas/halfsies/getBalance.ts index 4b7e262..531c669 100755 --- a/src/lambdas/halfsies/getBalance.ts +++ b/src/lambdas/halfsies/getBalance.ts @@ -1,4 +1,4 @@ -import { APIGatewayProxyResult } from 'aws-lambda' +import { type APIGatewayProxyResult } from 'aws-lambda' import { type DatabaseResponse } from '../../types' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' import { getBalance } from './utils' diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts index 7c36206..3bed8bf 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -1,4 +1,4 @@ -import { CloudFormationCustomResourceEvent } from 'aws-lambda' +import { type CloudFormationCustomResourceEvent } from 'aws-lambda' import { DynamoDBClient, PutItemCommand, diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index ce6b595..a2666cf 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -72,10 +72,10 @@ export class HalfsiesStack extends Stack { const createHalfsie: NodejsFunction = new NodejsFunction(this, 'createHalfsie', { functionName: `halfsiesCreateHalfsie${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'createHalfsie.ts'), - bundling: { - minify: true, - sourceMap: false, - }, + // bundling: { + // minify: true, + // sourceMap: false, + // }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { @@ -88,10 +88,10 @@ export class HalfsiesStack extends Stack { const getBalance: NodejsFunction = new NodejsFunction(this, 'getBalance', { functionName: `halfsiesGetBalance${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getBalance.ts'), - bundling: { - minify: true, - sourceMap: false, - }, + // bundling: { + // minify: true, + // sourceMap: false, + // }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { @@ -103,10 +103,10 @@ export class HalfsiesStack extends Stack { const getLog: NodejsFunction = new NodejsFunction(this, 'getLog', { functionName: `halfsiesGetLog${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getLog.ts'), - bundling: { - minify: true, - sourceMap: false, - }, + // bundling: { + // minify: true, + // sourceMap: false, + // }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { @@ -118,10 +118,10 @@ export class HalfsiesStack extends Stack { const initializeHalfsiesDatabase: NodejsFunction = new NodejsFunction(this, 'initializeHalfsiesDatabase', { functionName: `halfsiesInitializeHalfsiesDatabase${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'initializeHalfsiesDatabase.ts'), - bundling: { - minify: true, - sourceMap: false, - }, + // bundling: { + // minify: true, + // sourceMap: false, + // }, handler: 'handler', runtime: HALFSIES_NODE_VERSION, environment: { diff --git a/src/utils/lambdas.ts b/src/utils/lambdas.ts index ae3bc5b..8f20ceb 100755 --- a/src/utils/lambdas.ts +++ b/src/utils/lambdas.ts @@ -1,5 +1,5 @@ // THIS NEEDS UNIT TESTING -import { CloudFormationCustomResourceEvent } from 'aws-lambda' +import { type CloudFormationCustomResourceEvent } from 'aws-lambda' import { request } from 'https' // When using lambdas as custom resources in CloudFormation, you must return a response to the custom resource request. From 6d34b4edbdbfcf73d3208d5bab31c1850b45f27d Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Sat, 26 Apr 2025 16:27:03 -0500 Subject: [PATCH 07/11] Reduced the size of the lambdas by a lot --- config.ts | 3 -- src/lambdas/halfsies/createHalfsie.ts | 3 +- src/lambdas/halfsies/getLog.ts | 4 +- .../halfsies/initializeHalfsiesDatabase.ts | 3 +- src/lambdas/halfsies/utils/user.ts | 2 +- src/stacks/halfsies.ts | 40 ++++++++++--------- src/utils/auth/auth.test.ts | 8 +--- src/utils/auth/auth.ts | 3 +- src/utils/halfsies/auth.test.ts | 12 ++++++ src/utils/halfsies/auth.ts | 3 ++ src/utils/halfsies/index.ts | 1 + src/utils/halfsies/log.ts | 2 +- 12 files changed, 48 insertions(+), 36 deletions(-) create mode 100755 src/utils/halfsies/auth.test.ts create mode 100755 src/utils/halfsies/auth.ts diff --git a/config.ts b/config.ts index 03948c8..c2e30ee 100755 --- a/config.ts +++ b/config.ts @@ -1,5 +1,2 @@ -import { Runtime } from 'aws-cdk-lib/aws-lambda' - export const MAIN_USER_NAME: string = 'troy' export const HALFSIES_MAX_LOGS: number = 300 -export const HALFSIES_NODE_VERSION = Runtime.NODEJS_20_X diff --git a/src/lambdas/halfsies/createHalfsie.ts b/src/lambdas/halfsies/createHalfsie.ts index e907bc8..5dcf762 100755 --- a/src/lambdas/halfsies/createHalfsie.ts +++ b/src/lambdas/halfsies/createHalfsie.ts @@ -1,7 +1,8 @@ import { type APIGatewayProxyEvent, type APIGatewayProxyResult } from 'aws-lambda' import { type DatabaseResponse, type NewLog } from '../../types' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' -import { isALog, isUserNameTheMainUserName, pruneLogs } from '../../utils' +import { isUserNameTheMainUserName } from '../../utils/halfsies/auth' +import { isALog, pruneLogs } from '../../utils/halfsies/log' import { getBalance, getUserName, diff --git a/src/lambdas/halfsies/getLog.ts b/src/lambdas/halfsies/getLog.ts index f41f66e..a765c13 100755 --- a/src/lambdas/halfsies/getLog.ts +++ b/src/lambdas/halfsies/getLog.ts @@ -1,8 +1,8 @@ import { type APIGatewayProxyResult } from 'aws-lambda' import { type DatabaseResponse } from '../../types' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' -import { sortLogs } from '../../utils' -import { getLog } from './utils' +import { sortLogs } from '../../utils/halfsies/log' +import { getLog } from './utils/log' export const handler = (): Promise => new Promise((resolve) => { const { accessControlAllowOrigin = '' } = process.env diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts index 3bed8bf..a77a153 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -5,7 +5,8 @@ import { PutItemCommandInput, ConditionalCheckFailedException, } from '@aws-sdk/client-dynamodb' -import { generateNewBalanceDbItem, sendCustomResourceLambdaResponse } from '../../utils' +import { sendCustomResourceLambdaResponse } from '../../utils/lambdas' +import { generateNewBalanceDbItem} from '../../utils/halfsies' const dynamoDbClient = new DynamoDBClient() diff --git a/src/lambdas/halfsies/utils/user.ts b/src/lambdas/halfsies/utils/user.ts index c14e2ec..7a22f87 100755 --- a/src/lambdas/halfsies/utils/user.ts +++ b/src/lambdas/halfsies/utils/user.ts @@ -1,4 +1,4 @@ import { type APIGatewayProxyEventHeaders } from 'aws-lambda' -import { parseJwt } from '../../../utils' +import { parseJwt } from '../../../utils/jwt' export const getUserName = (headers: APIGatewayProxyEventHeaders): string => parseJwt(headers?.Authorization?.split(' ')[1])['cognito:username'] diff --git a/src/stacks/halfsies.ts b/src/stacks/halfsies.ts index a2666cf..c6dced7 100755 --- a/src/stacks/halfsies.ts +++ b/src/stacks/halfsies.ts @@ -13,10 +13,14 @@ import { } from 'aws-cdk-lib/aws-apigateway' import { Certificate, ICertificate } from 'aws-cdk-lib/aws-certificatemanager' import { AttributeType, Table } from 'aws-cdk-lib/aws-dynamodb' +import { Runtime } from 'aws-cdk-lib/aws-lambda' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' -import { HALFSIES_NODE_VERSION } from '../../config' import { HalfsiesStackProps } from '../types' -import { addCorsOptions, createTable, requiresAuthorization } from '../utils' +import { requiresAuthorization } from '../utils/auth' +import { createTable } from '../utils/tables' +import { addCorsOptions } from '../utils/apiGateway' + +const NODE_VERSION = Runtime.NODEJS_20_X // ---------------------------------------------------------------------------------------- // WHEN DELETING THIS IN CLOUD FORMATION @@ -72,12 +76,12 @@ export class HalfsiesStack extends Stack { const createHalfsie: NodejsFunction = new NodejsFunction(this, 'createHalfsie', { functionName: `halfsiesCreateHalfsie${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'createHalfsie.ts'), - // bundling: { - // minify: true, - // sourceMap: false, - // }, + bundling: { + minify: true, + sourceMap: false, + }, handler: 'handler', - runtime: HALFSIES_NODE_VERSION, + runtime: NODE_VERSION, environment: { accessControlAllowOrigin, balanceTableName: balanceDb.tableName, @@ -88,12 +92,12 @@ export class HalfsiesStack extends Stack { const getBalance: NodejsFunction = new NodejsFunction(this, 'getBalance', { functionName: `halfsiesGetBalance${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getBalance.ts'), - // bundling: { - // minify: true, - // sourceMap: false, - // }, + bundling: { + minify: true, + sourceMap: false, + }, handler: 'handler', - runtime: HALFSIES_NODE_VERSION, + runtime: NODE_VERSION, environment: { accessControlAllowOrigin, balanceTableName: balanceDb.tableName, @@ -103,12 +107,12 @@ export class HalfsiesStack extends Stack { const getLog: NodejsFunction = new NodejsFunction(this, 'getLog', { functionName: `halfsiesGetLog${resourcePostFix}`, entry: join(__dirname, '../lambdas', 'halfsies', 'getLog.ts'), - // bundling: { - // minify: true, - // sourceMap: false, - // }, + bundling: { + minify: true, + sourceMap: false, + }, handler: 'handler', - runtime: HALFSIES_NODE_VERSION, + runtime: NODE_VERSION, environment: { accessControlAllowOrigin, halfsiesLogTableName: logDb.tableName, @@ -123,7 +127,7 @@ export class HalfsiesStack extends Stack { // sourceMap: false, // }, handler: 'handler', - runtime: HALFSIES_NODE_VERSION, + runtime: NODE_VERSION, environment: { balanceTableName: balanceDb.tableName, }, diff --git a/src/utils/auth/auth.test.ts b/src/utils/auth/auth.test.ts index 7346241..b04f01f 100755 --- a/src/utils/auth/auth.test.ts +++ b/src/utils/auth/auth.test.ts @@ -1,7 +1,6 @@ import { Chance } from 'chance' import { AuthorizationType, MethodOptions } from 'aws-cdk-lib/aws-apigateway' -import { MAIN_USER_NAME } from '../../../config' -import { isUserNameTheMainUserName, requiresAuthorization } from './auth' +import { requiresAuthorization } from './auth' describe('Auth Util', () => { const chance = new Chance() @@ -15,9 +14,4 @@ describe('Auth Util', () => { authorizer, }) }) - - it('should be able to determine if the userName is the main userName', async () => { - expect(isUserNameTheMainUserName(MAIN_USER_NAME)).toBe(true) - expect(isUserNameTheMainUserName(chance.word({ syllables: 4 }))).toBe(false) - }) }) diff --git a/src/utils/auth/auth.ts b/src/utils/auth/auth.ts index 7c7336a..6a6f33d 100755 --- a/src/utils/auth/auth.ts +++ b/src/utils/auth/auth.ts @@ -1,9 +1,8 @@ import { AuthorizationType, type Authorizer, type MethodOptions } from 'aws-cdk-lib/aws-apigateway' -import { MAIN_USER_NAME } from '../../../config' export const requiresAuthorization = (authorizer: Authorizer): MethodOptions => ({ authorizationType: AuthorizationType.COGNITO, authorizer, }) -export const isUserNameTheMainUserName = (userName: string): Boolean => userName === MAIN_USER_NAME + diff --git a/src/utils/halfsies/auth.test.ts b/src/utils/halfsies/auth.test.ts new file mode 100755 index 0000000..2b8e16e --- /dev/null +++ b/src/utils/halfsies/auth.test.ts @@ -0,0 +1,12 @@ +import { Chance } from 'chance' +import { MAIN_USER_NAME } from '../../../config' +import { isUserNameTheMainUserName } from './auth' + +describe('Auth Util', () => { + const chance = new Chance() + + it('should be able to determine if the userName is the main userName', async () => { + expect(isUserNameTheMainUserName(MAIN_USER_NAME)).toBe(true) + expect(isUserNameTheMainUserName(chance.word({ syllables: 4 }))).toBe(false) + }) +}) diff --git a/src/utils/halfsies/auth.ts b/src/utils/halfsies/auth.ts new file mode 100755 index 0000000..5526fc7 --- /dev/null +++ b/src/utils/halfsies/auth.ts @@ -0,0 +1,3 @@ +import { MAIN_USER_NAME } from '../../../config' + +export const isUserNameTheMainUserName = (userName: string): Boolean => userName === MAIN_USER_NAME \ No newline at end of file diff --git a/src/utils/halfsies/index.ts b/src/utils/halfsies/index.ts index b088a39..5c485f2 100755 --- a/src/utils/halfsies/index.ts +++ b/src/utils/halfsies/index.ts @@ -1,3 +1,4 @@ /* istanbul ignore file */ +export * from './auth' export * from './log' export * from './tables' diff --git a/src/utils/halfsies/log.ts b/src/utils/halfsies/log.ts index 896a0f8..d3c3ac6 100755 --- a/src/utils/halfsies/log.ts +++ b/src/utils/halfsies/log.ts @@ -1,6 +1,6 @@ import { HALFSIES_MAX_LOGS } from '../../../config' import { type DatabaseResponse, type HalfsieLog } from '../../types' -import { deleteLog, getLog } from '../../lambdas/halfsies/utils' +import { deleteLog, getLog } from '../../lambdas/halfsies/utils/log' export const isALog = (log: any): boolean => { return typeof log.amount === 'number' && typeof log.description === 'string' From d8623cd36bf0cacbeac5412f221cca75c41cfc86 Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 5 Sep 2025 15:16:19 -0500 Subject: [PATCH 08/11] Fixing unit tests... --- .../initializeHalfsiesDatabase.test.ts | 91 +++++++++++-------- .../halfsies/initializeHalfsiesDatabase.ts | 3 +- .../halfsies/utils/balanceError.test.ts | 15 ++- 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts index 3bb34d1..bb71e41 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts @@ -1,23 +1,18 @@ import { Chance } from 'chance' -import { DynamoDB } from 'aws-sdk' - -const putItemMock = jest.fn().mockReturnValue({ - promise: jest.fn().mockResolvedValue({}), -}) - -const alreadyHasDataPutItemMock = jest.fn().mockReturnValue({ - promise: jest.fn().mockRejectedValue({ code: 'ConditionalCheckFailedException' }), -}) - -const errorPutItemMock = jest.fn().mockReturnValue({ - promise: jest.fn().mockRejectedValue({ code: '' }), -}) - +import { ConditionalCheckFailedException, DynamoDBClient } from '@aws-sdk/client-dynamodb' +import { sendCustomResourceLambdaResponse } from '../../utils/lambdas' import { handler } from './initializeHalfsiesDatabase' -import { generateNewBalanceDbItem } from '../../utils' +import { generateNewBalanceDbItem} from '../../utils/halfsies' + +jest.mock('@aws-sdk/client-dynamodb', () => ({ + ...jest.requireActual('@aws-sdk/client-dynamodb'), + DynamoDBClient: jest.fn(() => ({ + send: jest.fn(), + })), +})) -jest.mock('../../utils', () => ({ - ...jest.requireActual('../../utils'), +jest.mock('../../utils/lambdas', () => ({ + ...jest.requireActual('../../utils/lambdas'), sendCustomResourceLambdaResponse: jest.fn(), })) @@ -25,51 +20,75 @@ jest.mock('../../utils', () => ({ describe('Lambda - Initialize Halfsies Database', () => { const chance = new Chance() - beforeEach(() => { - DynamoDB.prototype.putItem = putItemMock - }) - it('Should not initialize the Halfises database anytime when we are not creating the lambda.', async () => { + const putItemMock = jest.fn().mockReturnValue({ + promise: jest.fn().mockResolvedValue({}), + }) + await handler({ RequestType: chance.word({ syllables: 4 }) } as any) expect(putItemMock).not.toHaveBeenCalled() }) it('Should not initialize the Halfises database anytime when there is already data in the database.', async () => { - DynamoDB.prototype.putItem = alreadyHasDataPutItemMock + const tableName = chance.word() + const send = jest.fn().mockRejectedValue(new ConditionalCheckFailedException({} as any)) + + jest.mocked(DynamoDBClient).mockImplementation(() => ({ + send, + }) as any) jest.spyOn(console, 'warn').mockImplementation(() => {}) - - const tableName = chance.word() process.env.balanceTableName = tableName await handler({ RequestType: 'Create' } as any) // eslint-disable-next-line no-console - expect(console.warn).toHaveBeenCalledWith('Halfsies balance amount already exists, skipping initialization.') + expect(console.warn).toHaveBeenCalledWith( + 'Halfsies balance amount already exists, skipping initialization.', + ) }) - it('Should throw an error if there is any issues putting the value in the database.', async () => { - DynamoDB.prototype.putItem = errorPutItemMock - + it('Should throw an error if there are any issues putting the value in the database.', async () => { const tableName = chance.word() + const error = chance.sentence() + const send = jest.fn().mockRejectedValue(new Error(error)) + + jest.mocked(DynamoDBClient).mockImplementation(() => ({ + send, + }) as any) process.env.balanceTableName = tableName - expect(async() => await handler({ RequestType: 'Create' } as any)).toThrowError() + await expect(handler({ RequestType: 'Create' } as any)) + .rejects.toThrow(error) }) it('Should initialize the Halfises database with a balance of zero.', async () => { const tableName = chance.word() + const send = jest.fn() - process.env.balanceTableName = tableName - await handler({ RequestType: 'Create' } as any) + const event = { + RequestType: 'Create', + } - expect(putItemMock).toHaveBeenCalledWith({ - ConditionExpression: 'attribute_not_exists(id)', - TableName: tableName, - Item: generateNewBalanceDbItem(0), - }) + jest.mocked(DynamoDBClient).mockImplementation(() => ({ + send, + }) as any) + + process.env.balanceTableName = tableName + await handler({ + ...event, + } as any) + + expect(send).toHaveBeenCalledWith(expect.objectContaining({ + input: expect.objectContaining({ + TableName: tableName, + Item: generateNewBalanceDbItem(0), + ConditionExpression: 'attribute_not_exists(id)', + }), + })) + expect(sendCustomResourceLambdaResponse).toHaveBeenCalledWith(event, true) }) }) diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts index a77a153..0787576 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -8,9 +8,8 @@ import { import { sendCustomResourceLambdaResponse } from '../../utils/lambdas' import { generateNewBalanceDbItem} from '../../utils/halfsies' -const dynamoDbClient = new DynamoDBClient() - export const handler = async (event: CloudFormationCustomResourceEvent) => { + const dynamoDbClient = new DynamoDBClient() const { balanceTableName = '' } = process.env if (event.RequestType === 'Create') { diff --git a/src/lambdas/halfsies/utils/balanceError.test.ts b/src/lambdas/halfsies/utils/balanceError.test.ts index 4b34bd1..1ab9124 100755 --- a/src/lambdas/halfsies/utils/balanceError.test.ts +++ b/src/lambdas/halfsies/utils/balanceError.test.ts @@ -1,17 +1,16 @@ import { Chance } from 'chance' import { getBalance, updateBalance } from './balance' -jest.mock('aws-sdk', () => { +jest.mock('@aws-sdk/lib-dynamodb', () => { return { - DynamoDB: { - DocumentClient: jest.fn(() => ({ - get: (_: any, callback: Function) => callback({ message: 'Something bad happened.' }, { Item: {} }), - update: (_: any, callback: Function) => callback({ message: 'Something bad happened.' }, { Item: {} }), - })), + ...jest.requireActual('@aws-sdk/lib-dynamodb'), + DynamoDBDocumentClient: { + from: jest.fn().mockReturnValue({ + send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), + }), }, } }) - describe('Balance util - failure', () => { const chance = new Chance() @@ -19,7 +18,6 @@ describe('Balance util - failure', () => { const result = await getBalance() expect(result).toStrictEqual({ - data: undefined, errorMessage: 'Something bad happened.', isError: true, }) @@ -29,7 +27,6 @@ describe('Balance util - failure', () => { const result = await updateBalance(chance.natural()) expect(result).toStrictEqual({ - data: undefined, errorMessage: 'Something bad happened.', isError: true, }) From a9d263ba9e774b90758a1c6f5855c6be02b52b19 Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 5 Sep 2025 17:39:03 -0500 Subject: [PATCH 09/11] Fixing more tests --- .../initializeHalfsiesDatabase.test.ts | 1 - src/lambdas/halfsies/utils/balance.ts | 5 ++- .../halfsies/utils/balanceError.test.ts | 12 +++++++ .../halfsies/utils/balanceSuccess.test.ts | 33 ++++++++++++++----- src/lambdas/halfsies/utils/logError.test.ts | 22 +++++++++---- src/lambdas/halfsies/utils/logSuccess.test.ts | 26 ++++++++++----- 6 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts index bb71e41..e1f1d2e 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts @@ -16,7 +16,6 @@ jest.mock('../../utils/lambdas', () => ({ sendCustomResourceLambdaResponse: jest.fn(), })) - describe('Lambda - Initialize Halfsies Database', () => { const chance = new Chance() diff --git a/src/lambdas/halfsies/utils/balance.ts b/src/lambdas/halfsies/utils/balance.ts index cbe265a..53f6797 100755 --- a/src/lambdas/halfsies/utils/balance.ts +++ b/src/lambdas/halfsies/utils/balance.ts @@ -8,9 +8,8 @@ import { } from "@aws-sdk/lib-dynamodb" import { type DatabaseResponse } from '../../../types' -const dynamoDbClient = new DynamoDBClient() - export const getBalance = async (): Promise => { + const dynamoDbClient = new DynamoDBClient() const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) const { balanceTableName = '' } = process.env @@ -35,6 +34,7 @@ export const getBalance = async (): Promise => { } export const updateBalance = async (newBalance: number): Promise => { + const dynamoDbClient = new DynamoDBClient() const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) const { balanceTableName = '' } = process.env @@ -54,7 +54,6 @@ export const updateBalance = async (newBalance: number): Promise ({ + ...jest.requireActual('@aws-sdk/client-dynamodb'), + DynamoDBClient: jest.fn(() => ({ + send: jest.fn(), + })), +})) + jest.mock('@aws-sdk/lib-dynamodb', () => { return { ...jest.requireActual('@aws-sdk/lib-dynamodb'), @@ -11,9 +18,14 @@ jest.mock('@aws-sdk/lib-dynamodb', () => { }, } }) + describe('Balance util - failure', () => { const chance = new Chance() + beforeEach(() => { + process.env.balanceTableName = chance.word({ syllables: 4 }) + }) + it('should allow fail when getting a balance', async () => { const result = await getBalance() diff --git a/src/lambdas/halfsies/utils/balanceSuccess.test.ts b/src/lambdas/halfsies/utils/balanceSuccess.test.ts index 7b9ab76..cda5617 100755 --- a/src/lambdas/halfsies/utils/balanceSuccess.test.ts +++ b/src/lambdas/halfsies/utils/balanceSuccess.test.ts @@ -1,26 +1,44 @@ import { Chance } from 'chance' import { getBalance, updateBalance } from './balance' -jest.mock('aws-sdk', () => { +jest.mock('@aws-sdk/client-dynamodb', () => ({ + ...jest.requireActual('@aws-sdk/client-dynamodb'), + DynamoDBClient: jest.fn(() => ({ + send: jest.fn(), + })), +})) + +jest.mock('@aws-sdk/lib-dynamodb', () => { return { - DynamoDB: { - DocumentClient: jest.fn(() => ({ - get: (_: any, callback: Function) => callback(null, { Item: { balance: 532 } }), - update: (_: any, callback: Function) => callback(null, { Attributes: { balance: 624 } }), + ...jest.requireActual('@aws-sdk/lib-dynamodb'), + DynamoDBDocumentClient: { + from: jest.fn(() => ({ + send: jest.fn((command) => { + if (command.constructor.name === 'GetCommand') { + return Promise.resolve({ Item: { balance: 532 } }) + } + if (command.constructor.name === 'UpdateCommand') { + return Promise.resolve({ Attributes: { balance: 624 } }) + } + return Promise.resolve({}) + }), })), }, } -}) +}) describe('Balance util - success', () => { const chance = new Chance() + beforeEach(() => { + process.env.balanceTableName = chance.word({ syllables: 4 }) + }) + it('should return a balance', async () => { const result = await getBalance() expect(result).toStrictEqual({ data: 532, - errorMessage: undefined, isError: false, }) }) @@ -30,7 +48,6 @@ describe('Balance util - success', () => { expect(result).toStrictEqual({ data: 624, - errorMessage: undefined, isError: false, }) }) diff --git a/src/lambdas/halfsies/utils/logError.test.ts b/src/lambdas/halfsies/utils/logError.test.ts index 874ee71..2749a29 100755 --- a/src/lambdas/halfsies/utils/logError.test.ts +++ b/src/lambdas/halfsies/utils/logError.test.ts @@ -2,13 +2,19 @@ import { Chance } from 'chance' import { mockNewLog } from '../../../mocks' import { deleteLog, getLog, saveLog } from './log' -jest.mock('aws-sdk', () => { +jest.mock('@aws-sdk/client-dynamodb', () => ({ + DynamoDBClient: jest.fn(() => ({ + send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), + })), +})) + +jest.mock('@aws-sdk/lib-dynamodb', () => { + const originalModule = jest.requireActual('@aws-sdk/lib-dynamodb') return { - DynamoDB: { - DocumentClient: jest.fn(() => ({ - delete: (_: any, callback: Function) => callback({ message: 'Something bad happened.' }, { Item: {} }), - put: (_: any, callback: Function) => callback({ message: 'Something bad happened.' }, { Item: {} }), - scan: (_: any, callback: Function) => callback({ message: 'Something bad happened.' }, { Item: {} }), + ...originalModule, + DynamoDBDocumentClient: { + from: jest.fn(() => ({ + send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), })), }, } @@ -17,6 +23,10 @@ jest.mock('aws-sdk', () => { describe('Log util - failure', () => { const chance = new Chance() + beforeEach(() => { + process.env.halfsiesLogTableName = chance.word({ syllables: 4 }) + }) + it('should allow fail when getting logs', async () => { const result = await getLog() diff --git a/src/lambdas/halfsies/utils/logSuccess.test.ts b/src/lambdas/halfsies/utils/logSuccess.test.ts index 1816342..e0b76cc 100755 --- a/src/lambdas/halfsies/utils/logSuccess.test.ts +++ b/src/lambdas/halfsies/utils/logSuccess.test.ts @@ -1,24 +1,32 @@ import { Chance } from 'chance' -import { mockHalfsieLogs, mockNewLog } from '../../../mocks' +import { mockNewLog } from '../../../mocks' import { deleteLog, getLog, saveLog } from './log' -jest.mock('aws-sdk', () => { +jest.mock('@aws-sdk/client-dynamodb', () => ({ + DynamoDBClient: jest.fn(() => ({ + send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), + })), +})) + +jest.mock('@aws-sdk/lib-dynamodb', () => { + const originalModule = jest.requireActual('@aws-sdk/lib-dynamodb') return { - DynamoDB: { - DocumentClient: jest.fn(() => ({ - delete: (_: any, callback: Function) => callback(null), - put: (_: any, callback: Function) => callback(null), - scan: (_: any, callback: Function) => callback(null, { Items: mockHalfsieLogs() }), + ...originalModule, + DynamoDBDocumentClient: { + from: jest.fn(() => ({ + send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), })), }, } }) -jest.mock('./log') - describe('Log util - success', () => { const chance = new Chance() + beforeEach(() => { + process.env.halfsiesLogTableName = chance.word({ syllables: 4 }) + }) + it('should get logs', async () => { expect(async () => await getLog()).not.toThrow() }) From d6bd337cc756f93b31c7058993d0940e618c419e Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 12 Sep 2025 17:58:44 -0500 Subject: [PATCH 10/11] Added more coverage to test files --- src/lambdas/halfsies/createHalfsie.test.ts | 21 +-- src/lambdas/halfsies/getLog.test.ts | 10 +- .../halfsies/initializeHalfsiesDatabase.ts | 2 +- src/lambdas/halfsies/utils/balance.ts | 4 +- src/lambdas/halfsies/utils/log.ts | 33 ++--- src/lambdas/halfsies/utils/logError.test.ts | 15 ++- src/lambdas/halfsies/utils/logSuccess.test.ts | 7 +- src/utils/apiGateway.test.ts | 50 +++++++ src/utils/error.test.ts | 27 ++++ src/utils/error.ts | 6 + src/utils/halfsies/log.test.ts | 8 +- src/utils/halfsies/log.ts | 3 +- src/utils/index.ts | 1 + src/utils/lambdas.test.ts | 125 ++++++++++++++++++ src/utils/tables.test.ts | 2 +- 15 files changed, 268 insertions(+), 46 deletions(-) create mode 100755 src/utils/apiGateway.test.ts create mode 100755 src/utils/error.test.ts create mode 100755 src/utils/error.ts create mode 100755 src/utils/lambdas.test.ts diff --git a/src/lambdas/halfsies/createHalfsie.test.ts b/src/lambdas/halfsies/createHalfsie.test.ts index fdb63c8..009c1f6 100755 --- a/src/lambdas/halfsies/createHalfsie.test.ts +++ b/src/lambdas/halfsies/createHalfsie.test.ts @@ -3,7 +3,7 @@ import { MAIN_USER_NAME } from '../../../config' import { HalfsieLog } from '../../types' import { mockApiGatewayProxyEvent, mockHalfsieLogs, mockNewLog } from '../../mocks' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' -import * as utils from '../../utils' +import { pruneLogs } from '../../utils/halfsies/log' import { getBalance, getUserName, @@ -12,12 +12,10 @@ import { } from './utils' import { handler } from './createHalfsie' -jest.mock('../../utils', () => { - return { - __esModule: true, - ...jest.requireActual('../../utils'), - } -}) +jest.mock('../../utils/halfsies/log', () => ({ + ...jest.requireActual('../../utils/halfsies/log'), + pruneLogs: jest.fn(), +})) jest.mock('./utils') describe('Lambda - Create Halfsie', () => { @@ -26,6 +24,9 @@ describe('Lambda - Create Halfsie', () => { const newLogs: HalfsieLog[] = mockHalfsieLogs() beforeEach(() => { + process.env.balanceTableName = chance.word({ syllables: 4 }) + process.env.halfsiesLogTableName = chance.word({ syllables: 4 }) + jest.mocked(getBalance).mockResolvedValue({ data: currentBalance, isError: false, @@ -43,7 +44,7 @@ describe('Lambda - Create Halfsie', () => { errorMessage: undefined, }) - jest.spyOn(utils, 'pruneLogs').mockResolvedValue({ + jest.mocked(pruneLogs).mockResolvedValue({ data: newLogs, isError: false, errorMessage: undefined, @@ -244,7 +245,7 @@ describe('Lambda - Create Halfsie', () => { it('should return an error if there is a problem using the prune logs util', async () => { const errorMessage = chance.sentence() - jest.spyOn(utils, 'pruneLogs').mockRejectedValue(errorMessage) + jest.mocked(pruneLogs).mockRejectedValue(errorMessage) const result = await handler(mockApiGatewayProxyEvent( mockNewLog(), @@ -263,7 +264,7 @@ describe('Lambda - Create Halfsie', () => { it('should return an error if there is a problem pruning the logs', async () => { const errorMessage = chance.sentence() - jest.spyOn(utils, 'pruneLogs').mockResolvedValue({ + jest.mocked(pruneLogs).mockResolvedValue({ errorMessage, isError: true, }) diff --git a/src/lambdas/halfsies/getLog.test.ts b/src/lambdas/halfsies/getLog.test.ts index 73d212b..67a813a 100755 --- a/src/lambdas/halfsies/getLog.test.ts +++ b/src/lambdas/halfsies/getLog.test.ts @@ -1,15 +1,19 @@ import { Chance } from 'chance' import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants' import { mockHalfsieLog } from '../../mocks' -import { sortLogs } from '../../utils' -import { getLog } from './utils' +import { sortLogs } from '../../utils/halfsies/log' +import { getLog } from './utils/log' import { handler } from './getLog' -jest.mock('./utils') +jest.mock('./utils/log') describe('Lambda - Get Log', () => { const chance = new Chance() + beforeEach(() => { + process.env.halfsiesLogTableName = chance.word({ syllables: 4 }) + }) + it('should return a log', async () => { const log = [ mockHalfsieLog(), diff --git a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts index 0787576..687855a 100755 --- a/src/lambdas/halfsies/initializeHalfsiesDatabase.ts +++ b/src/lambdas/halfsies/initializeHalfsiesDatabase.ts @@ -10,7 +10,7 @@ import { generateNewBalanceDbItem} from '../../utils/halfsies' export const handler = async (event: CloudFormationCustomResourceEvent) => { const dynamoDbClient = new DynamoDBClient() - const { balanceTableName = '' } = process.env + const { balanceTableName } = process.env if (event.RequestType === 'Create') { const params: PutItemCommandInput = { diff --git a/src/lambdas/halfsies/utils/balance.ts b/src/lambdas/halfsies/utils/balance.ts index 53f6797..a54ec44 100755 --- a/src/lambdas/halfsies/utils/balance.ts +++ b/src/lambdas/halfsies/utils/balance.ts @@ -11,7 +11,7 @@ import { type DatabaseResponse } from '../../../types' export const getBalance = async (): Promise => { const dynamoDbClient = new DynamoDBClient() const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) - const { balanceTableName = '' } = process.env + const { balanceTableName } = process.env const params: GetCommandInput = { TableName: balanceTableName, @@ -36,7 +36,7 @@ export const getBalance = async (): Promise => { export const updateBalance = async (newBalance: number): Promise => { const dynamoDbClient = new DynamoDBClient() const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) - const { balanceTableName = '' } = process.env + const { balanceTableName } = process.env const params: UpdateCommandInput = { TableName: balanceTableName, diff --git a/src/lambdas/halfsies/utils/log.ts b/src/lambdas/halfsies/utils/log.ts index 33979a2..2c106b7 100755 --- a/src/lambdas/halfsies/utils/log.ts +++ b/src/lambdas/halfsies/utils/log.ts @@ -1,6 +1,5 @@ import { DeleteItemCommand, - DeleteItemCommandInput, DynamoDBClient, } from '@aws-sdk/client-dynamodb' import { @@ -11,12 +10,12 @@ import { } from "@aws-sdk/lib-dynamodb" import { marshall } from '@aws-sdk/util-dynamodb' import { type DatabaseResponse, type NewLog } from '../../../types' - -const dynamoDbClient = new DynamoDBClient() +import { getErrorMessage } from '../../../utils/error' export const getLog = async (): Promise => { + const dynamoDbClient = new DynamoDBClient() const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) - const { halfsiesLogTableName = '' } = process.env + const { halfsiesLogTableName } = process.env try { const data: ScanCommandOutput = await dynamoDocumentClient.send(new ScanCommand({ @@ -30,14 +29,15 @@ export const getLog = async (): Promise => { } catch (error: unknown) { return { isError: true, - errorMessage: error instanceof Error ? error.message : "Unknown error occurred", + errorMessage: getErrorMessage(error), } } } export const saveLog = async (log: NewLog, userName: string): Promise => { + const dynamoDbClient = new DynamoDBClient() const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) - const { halfsiesLogTableName = '' } = process.env + const { halfsiesLogTableName } = process.env const { amount, description } = log const now: Date = new Date() @@ -59,27 +59,30 @@ export const saveLog = async (log: NewLog, userName: string): Promise => { - const { halfsiesLogTableName = '' } = process.env - - const dbQueryParams: DeleteItemCommandInput = { - TableName: halfsiesLogTableName, - Key: marshall({ id }), - } + const dynamoDbClient = new DynamoDBClient() + const dynamoDocumentClient = DynamoDBDocumentClient.from(dynamoDbClient) + const { halfsiesLogTableName } = process.env try { - await dynamoDbClient.send(new DeleteItemCommand(dbQueryParams)) + await dynamoDocumentClient.send( + new DeleteItemCommand({ + TableName: halfsiesLogTableName, + Key: marshall({ id }), + }), + ) + return { isError: false, } } catch (error: any) { return { - errorMessage: error?.message, + errorMessage: getErrorMessage(error), isError: true, } } diff --git a/src/lambdas/halfsies/utils/logError.test.ts b/src/lambdas/halfsies/utils/logError.test.ts index 2749a29..92074d0 100755 --- a/src/lambdas/halfsies/utils/logError.test.ts +++ b/src/lambdas/halfsies/utils/logError.test.ts @@ -2,11 +2,15 @@ import { Chance } from 'chance' import { mockNewLog } from '../../../mocks' import { deleteLog, getLog, saveLog } from './log' -jest.mock('@aws-sdk/client-dynamodb', () => ({ - DynamoDBClient: jest.fn(() => ({ - send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), - })), -})) +jest.mock('@aws-sdk/client-dynamodb', () => { + const actual = jest.requireActual('@aws-sdk/client-dynamodb') + return { + ...actual, // keeps DeleteItemCommand, DeleteItemCommandInput, etc. + DynamoDBClient: jest.fn(() => ({ + send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), + })), + } +}) jest.mock('@aws-sdk/lib-dynamodb', () => { const originalModule = jest.requireActual('@aws-sdk/lib-dynamodb') @@ -31,7 +35,6 @@ describe('Log util - failure', () => { const result = await getLog() expect(result).toStrictEqual({ - data: undefined, errorMessage: 'Something bad happened.', isError: true, }) diff --git a/src/lambdas/halfsies/utils/logSuccess.test.ts b/src/lambdas/halfsies/utils/logSuccess.test.ts index e0b76cc..3ac5175 100755 --- a/src/lambdas/halfsies/utils/logSuccess.test.ts +++ b/src/lambdas/halfsies/utils/logSuccess.test.ts @@ -3,9 +3,8 @@ import { mockNewLog } from '../../../mocks' import { deleteLog, getLog, saveLog } from './log' jest.mock('@aws-sdk/client-dynamodb', () => ({ - DynamoDBClient: jest.fn(() => ({ - send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), - })), + ...jest.requireActual('@aws-sdk/client-dynamodb'), + DynamoDBClient: jest.fn(() => ({})), })) jest.mock('@aws-sdk/lib-dynamodb', () => { @@ -14,7 +13,7 @@ jest.mock('@aws-sdk/lib-dynamodb', () => { ...originalModule, DynamoDBDocumentClient: { from: jest.fn(() => ({ - send: jest.fn().mockRejectedValue(new Error('Something bad happened.')), + send: jest.fn().mockResolvedValue({ Items: [] }), })), }, } diff --git a/src/utils/apiGateway.test.ts b/src/utils/apiGateway.test.ts new file mode 100755 index 0000000..a66d25a --- /dev/null +++ b/src/utils/apiGateway.test.ts @@ -0,0 +1,50 @@ +import { MockIntegration } from 'aws-cdk-lib/aws-apigateway' +import { Chance } from 'chance' +import { addCorsOptions } from './apiGateway' + +jest.mock('aws-cdk-lib/aws-apigateway', () => { + const actual = jest.requireActual('aws-cdk-lib/aws-apigateway') + return { + ...actual, + MockIntegration: jest.fn().mockImplementation((props) => ({ __props: props })), + } +}) + +describe('API Gateway Utils', () => { + const chance = new Chance() + + it('Should add an OPTIONS method with proper CORS headers.', () => { + const addMethod = jest.fn() + const mockResource = { addMethod: addMethod } as any + const origin = chance.url() + + addCorsOptions(mockResource, origin) + + expect(addMethod).toHaveBeenCalledTimes(1) + + const [method, integration, methodOptions] = addMethod.mock.calls[0] + + expect(method).toBe('OPTIONS') + + const integrationProps = (integration as any).__props + + expect(MockIntegration).toHaveBeenCalledTimes(1) + expect(integrationProps.integrationResponses[0].statusCode).toBe('200') + expect(integrationProps.integrationResponses[0].responseParameters).toMatchObject({ + 'method.response.header.Access-Control-Allow-Headers': `'Content-Type,X-Amz-Date,Authorization,X-Api-Key'`, + 'method.response.header.Access-Control-Allow-Origin': `'${origin}'`, + 'method.response.header.Access-Control-Allow-Methods': `'GET,POST,OPTIONS'`, + }) + expect(integrationProps.requestTemplates).toMatchObject({ + 'application/json': '{"statusCode": 200}', + }) + expect(methodOptions.methodResponses[0]).toMatchObject({ + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': true, + 'method.response.header.Access-Control-Allow-Origin': true, + 'method.response.header.Access-Control-Allow-Methods': true, + }, + }) + }) +}) diff --git a/src/utils/error.test.ts b/src/utils/error.test.ts new file mode 100755 index 0000000..5b3ba2f --- /dev/null +++ b/src/utils/error.test.ts @@ -0,0 +1,27 @@ +import { getErrorMessage } from './error' + +describe('Error Utils', () => { + it('Should return the error message when given an Error instance.', () => { + const error = new Error('Something went wrong') + const result = getErrorMessage(error) + + expect(result).toBe('Something went wrong') + }) + + it('Should returns "Unknown error occurred" when given a string.', () => { + const result = getErrorMessage('not-an-error') + + expect(result).toBe('Unknown error occurred') + }) + + it('Should return "Unknown error occurred" when given null.', () => { + const result = getErrorMessage(null) + expect(result).toBe('Unknown error occurred') + }) + + it('Should return "Unknown error occurred" when given an arbitrary object.', () => { + const result = getErrorMessage({ message: 'fake' }) + + expect(result).toBe('Unknown error occurred') + }) +}) \ No newline at end of file diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100755 index 0000000..fae27ed --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,6 @@ +export const getErrorMessage = (error: unknown): string => { + if (error instanceof Error) { + return error.message + } + return "Unknown error occurred" +} \ No newline at end of file diff --git a/src/utils/halfsies/log.test.ts b/src/utils/halfsies/log.test.ts index 506523e..6f2669e 100755 --- a/src/utils/halfsies/log.test.ts +++ b/src/utils/halfsies/log.test.ts @@ -2,16 +2,18 @@ import { Chance } from 'chance' import { HALFSIES_MAX_LOGS } from '../../../config' import { type HalfsieLog } from '../../types' import { mockHalfsieLog, mockHalfsieLogs } from '../../mocks' -import { deleteLog, getLog } from '../../lambdas/halfsies/utils' +import { deleteLog, getLog } from '../../lambdas/halfsies/utils/log' import { isALog, pruneLogs, sortLogs } from './log' jest.mock('../../../config') -jest.mock('../../lambdas/halfsies/utils') +jest.mock('../../lambdas/halfsies/utils/log') describe('Log util', () => { const chance = new Chance() beforeEach(() => { + process.env.halfsiesLogTableName = chance.word({ syllables: 4 }) + jest.mocked(deleteLog).mockResolvedValue({ isError: false, errorMessage: '' }) }) @@ -72,7 +74,7 @@ describe('Log util', () => { it('Should not be able to prune logs if there is an error getting the logs.', async () => { const errorMessage: string = chance.sentence() - jest.mocked(getLog).mockRejectedValue({ message: errorMessage }) + jest.mocked(getLog).mockResolvedValue({ isError: true, errorMessage: errorMessage }) const result = await pruneLogs() diff --git a/src/utils/halfsies/log.ts b/src/utils/halfsies/log.ts index d3c3ac6..57782f8 100755 --- a/src/utils/halfsies/log.ts +++ b/src/utils/halfsies/log.ts @@ -1,6 +1,7 @@ import { HALFSIES_MAX_LOGS } from '../../../config' import { type DatabaseResponse, type HalfsieLog } from '../../types' import { deleteLog, getLog } from '../../lambdas/halfsies/utils/log' +import { getErrorMessage } from '../../utils/error' export const isALog = (log: any): boolean => { return typeof log.amount === 'number' && typeof log.description === 'string' @@ -57,7 +58,7 @@ export const pruneLogs = async (): Promise => new Promise((res }) }).catch((error) => { resolve({ - errorMessage: error instanceof Error ? error.message : "Unknown error occurred", + errorMessage: getErrorMessage(error), isError: true, }) }) diff --git a/src/utils/index.ts b/src/utils/index.ts index e9dcb6d..87d3142 100755 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ /* istanbul ignore file */ export * from './apiGateway' export * from './auth' +export * from './error' export * from './halfsies' export * from './jwt' export * from './lambdas' diff --git a/src/utils/lambdas.test.ts b/src/utils/lambdas.test.ts new file mode 100755 index 0000000..b097c7f --- /dev/null +++ b/src/utils/lambdas.test.ts @@ -0,0 +1,125 @@ +import { sendCustomResourceLambdaResponse } from './lambdas' +import { request as httpsRequest } from 'https' +import { Chance } from 'chance' +import type { ClientRequest } from 'http' + +jest.mock('https', () => ({ + request: jest.fn(), +})) + +describe('Lambdas Utils', () => { + const chance = new Chance() + const mockEnd = jest.fn() + const mockWrite = jest.fn() + + beforeEach(() => { + jest.mocked(httpsRequest).mockImplementation((_opts: any, callback?: any) => { + const res = { + on: jest.fn((event: string, handler: (...args: any[]) => void) => { + if (event === 'end') setImmediate(handler) + return res + }), + } as any + + if (callback) callback(res) + + const req: any = { + on: jest.fn(() => req), + write: mockWrite, + end: mockEnd, + } + + return req + }) + }) + + it('Should send SUCCESS response with the correct body.', async () => { + const event = { + ResponseURL: `https://${chance.domain()}/path?query=1`, + StackId: chance.guid(), + RequestId: chance.guid(), + LogicalResourceId: chance.word(), + PhysicalResourceId: chance.word(), + } + + await sendCustomResourceLambdaResponse(event as any, true) + + const [options] = (httpsRequest as jest.Mock).mock.calls[0] + + expect(options.method).toBe('PUT') + expect(options.hostname).toBe(new URL(event.ResponseURL).hostname) + + const body = mockWrite.mock.calls[0][0] + const parsed = JSON.parse(body) + + expect(parsed.Status).toBe('SUCCESS') + expect(parsed.Reason).toBe('See CloudWatch Logs') + expect(parsed.PhysicalResourceId).toBe(event.PhysicalResourceId) + expect(parsed.StackId).toBe(event.StackId) + expect(parsed.RequestId).toBe(event.RequestId) + expect(parsed.LogicalResourceId).toBe(event.LogicalResourceId) + expect(parsed.Data).toEqual({}) + }) + + it('Should send a FAILED response with error message.', async () => { + const event = { + ResponseURL: `https://${chance.domain()}/path?query=1`, + StackId: chance.guid(), + RequestId: chance.guid(), + LogicalResourceId: chance.word(), + PhysicalResourceId: chance.word(), + } + const errorMsg = chance.sentence() + + await sendCustomResourceLambdaResponse(event as any, false, errorMsg) + }) + + it('Should reject if https.request emits an error.', async () => { + const mockErrorHandler = jest.fn() + + jest.mocked(httpsRequest).mockImplementation(() => { + const req: Partial = { + on: (event: string, handler: (...args: any[]) => void) => { + if (event === 'error') { + mockErrorHandler.mockImplementation(handler) + } + return req + }, + write: mockWrite, + end: mockEnd, + } as any + + return req as ClientRequest + }) + + const event = { + ResponseURL: `https://${chance.domain()}/path?query=1`, + StackId: chance.guid(), + RequestId: chance.guid(), + LogicalResourceId: chance.word(), + PhysicalResourceId: chance.word(), + } + const promise = sendCustomResourceLambdaResponse(event as any, true) + const error = new Error('network fail') + + mockErrorHandler(error) + + await expect(promise).rejects.toThrow('network fail') + }) + + it('Uses LogicalResourceId when PhysicalResourceId is missing', async () => { + const event = { + ResponseURL: `https://${chance.domain()}/path`, + StackId: chance.guid(), + RequestId: chance.guid(), + LogicalResourceId: 'MyLogicalId', + } as any + + await sendCustomResourceLambdaResponse(event, true) + + const body = mockWrite.mock.calls[0][0] + const parsed = JSON.parse(body) + + expect(parsed.PhysicalResourceId).toBe(event.LogicalResourceId) + }) +}) diff --git a/src/utils/tables.test.ts b/src/utils/tables.test.ts index 3205b45..b463b8b 100755 --- a/src/utils/tables.test.ts +++ b/src/utils/tables.test.ts @@ -4,7 +4,7 @@ import { AttributeType } from 'aws-cdk-lib/aws-dynamodb' import { CreateTableType } from '../types' import * as tables from './tables' -describe('Create Table', () => { +describe('Table Utils', () => { const chance = new Chance() const SomeStack = new Stack() From 051544be34cc10d329d3bdea9f73c1930afe4fec Mon Sep 17 00:00:00 2001 From: Troy Blank Date: Fri, 19 Sep 2025 15:48:35 -0500 Subject: [PATCH 11/11] fixed coverage issues --- jest.config.js | 1 + jest.setup.ts | 3 +++ src/utils/halfsies/log.test.ts | 13 ++++++++++++- src/utils/halfsies/log.ts | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100755 jest.setup.ts diff --git a/jest.config.js b/jest.config.js index b0bfc57..4ae49c5 100755 --- a/jest.config.js +++ b/jest.config.js @@ -17,6 +17,7 @@ module.exports = { testEnvironment: 'node', rootDir: './', roots: ['/src'], + setupFilesAfterEnv: ['/jest.setup.ts'], transform: { '^.+\\.ts?$': 'ts-jest', }, diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100755 index 0000000..af72d1c --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1,3 @@ +afterEach(() => { + jest.clearAllMocks() +}) \ No newline at end of file diff --git a/src/utils/halfsies/log.test.ts b/src/utils/halfsies/log.test.ts index 6f2669e..0580417 100755 --- a/src/utils/halfsies/log.test.ts +++ b/src/utils/halfsies/log.test.ts @@ -3,6 +3,7 @@ import { HALFSIES_MAX_LOGS } from '../../../config' import { type HalfsieLog } from '../../types' import { mockHalfsieLog, mockHalfsieLogs } from '../../mocks' import { deleteLog, getLog } from '../../lambdas/halfsies/utils/log' +import { getErrorMessage } from '../../utils/error' import { isALog, pruneLogs, sortLogs } from './log' jest.mock('../../../config') @@ -32,7 +33,6 @@ describe('Log util', () => { }) it('Should be able to sort logs based on date.', async () => { - const logs: HalfsieLog[] = [ mockHalfsieLog({ date: new Date('1950-11-10').toISOString(), @@ -139,4 +139,15 @@ describe('Log util', () => { errorMessage: 'Failed to delete some logs.', }) }) + + it('Should not be able to prune logs if there is an syntax error getting the logs.', async () => { + jest.mocked(getLog).mockResolvedValue(Promise.reject()) + + const result = await pruneLogs() + + expect(result).toStrictEqual({ + isError: true, + errorMessage: getErrorMessage({}), + }) + }) }) diff --git a/src/utils/halfsies/log.ts b/src/utils/halfsies/log.ts index 57782f8..2e374a9 100755 --- a/src/utils/halfsies/log.ts +++ b/src/utils/halfsies/log.ts @@ -30,7 +30,7 @@ export const pruneLogs = async (): Promise => new Promise((res const logsToDelete = remainingLogs.slice(-amountOver) remainingLogs = remainingLogs.slice(0, -amountOver) - logsToDelete.forEach(async ({ id }: { id: number }) => { + logsToDelete.forEach(({ id }: { id: number }) => { logsAwaitingDeletion.push(deleteLog(id)) }) }