diff --git a/package-lock.json b/package-lock.json index a67ae903b..413ef8c63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@aws-sdk/client-s3": "^3.920.0", "@azure/storage-blob": "^12.26.0", "@babel/cli": "^7.15.4", "@babel/core": "^7.10.2", @@ -66,6 +65,7 @@ "ws": "^8.17.1" }, "devDependencies": { + "@aws-sdk/client-s3": "^3.940.0", "@babel/eslint-parser": "^7.15.0", "@types/bson": "^4.2.4", "@types/compression": "^1.7.5", @@ -249,6 +249,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -263,6 +264,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -274,6 +276,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", @@ -288,6 +291,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -300,6 +304,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -313,6 +318,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -326,6 +332,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", @@ -341,6 +348,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -353,6 +361,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -366,6 +375,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -379,6 +389,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -393,6 +404,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -402,6 +414,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", @@ -413,6 +426,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -425,6 +439,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -438,6 +453,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -448,32 +464,33 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.937.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.937.0.tgz", - "integrity": "sha512-ioeNe6HSc7PxjsUQY7foSHmgesxM5KwAeUtPhIHgKx99nrM+7xYCfW4FMvHypUzz7ZOvqlCdH7CEAZ8ParBvVg==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.940.0.tgz", + "integrity": "sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-node": "3.936.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-bucket-endpoint": "3.936.0", "@aws-sdk/middleware-expect-continue": "3.936.0", - "@aws-sdk/middleware-flexible-checksums": "3.936.0", + "@aws-sdk/middleware-flexible-checksums": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-location-constraint": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-sdk-s3": "3.936.0", + "@aws-sdk/middleware-sdk-s3": "3.940.0", "@aws-sdk/middleware-ssec": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/signature-v4-multi-region": "3.936.0", + "@aws-sdk/signature-v4-multi-region": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/eventstream-serde-browser": "^4.2.5", @@ -514,23 +531,24 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", - "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.940.0.tgz", + "integrity": "sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", @@ -563,9 +581,10 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", - "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.940.0.tgz", + "integrity": "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -587,12 +606,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", - "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.940.0.tgz", + "integrity": "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", @@ -603,12 +623,13 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", - "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.940.0.tgz", + "integrity": "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", @@ -624,19 +645,20 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", - "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-login": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.940.0.tgz", + "integrity": "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/credential-provider-env": "3.940.0", + "@aws-sdk/credential-provider-http": "3.940.0", + "@aws-sdk/credential-provider-login": "3.940.0", + "@aws-sdk/credential-provider-process": "3.940.0", + "@aws-sdk/credential-provider-sso": "3.940.0", + "@aws-sdk/credential-provider-web-identity": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", @@ -649,13 +671,14 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.936.0.tgz", - "integrity": "sha512-8DVrdRqPyUU66gfV7VZNToh56ZuO5D6agWrkLQE/xbLJOm2RbeRgh6buz7CqV8ipRd6m+zCl9mM4F3osQLZn8Q==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.940.0.tgz", + "integrity": "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", @@ -668,17 +691,18 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", - "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.940.0.tgz", + "integrity": "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-ini": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/credential-provider-env": "3.940.0", + "@aws-sdk/credential-provider-http": "3.940.0", + "@aws-sdk/credential-provider-ini": "3.940.0", + "@aws-sdk/credential-provider-process": "3.940.0", + "@aws-sdk/credential-provider-sso": "3.940.0", + "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", @@ -691,12 +715,13 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", - "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.940.0.tgz", + "integrity": "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", @@ -708,14 +733,15 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", - "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.940.0.tgz", + "integrity": "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.936.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/token-providers": "3.936.0", + "@aws-sdk/client-sso": "3.940.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/token-providers": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", @@ -727,13 +753,14 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", - "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.940.0.tgz", + "integrity": "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", @@ -748,6 +775,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.936.0.tgz", "integrity": "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -766,6 +794,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.936.0.tgz", "integrity": "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -778,15 +807,16 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.936.0.tgz", - "integrity": "sha512-l3GG6CrSQtMCM6fWY7foV3JQv0WJWT+3G6PSP3Ceb/KEE/5Lz5PrYFXTBf+bVoYL1b0bGjGajcgAXpstBmtHtQ==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.940.0.tgz", + "integrity": "sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.5", @@ -805,6 +835,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -820,6 +851,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.936.0.tgz", "integrity": "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -834,6 +866,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -848,6 +881,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -861,12 +895,13 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.936.0.tgz", - "integrity": "sha512-UQs/pVq4cOygsnKON0pOdSKIWkfgY0dzq4h+fR+xHi/Ng3XzxPJhWeAE6tDsKrcyQc1X8UdSbS70XkfGYr5hng==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.940.0.tgz", + "integrity": "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.5", @@ -889,6 +924,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.936.0.tgz", "integrity": "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -900,12 +936,13 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", - "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.940.0.tgz", + "integrity": "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.5", @@ -918,23 +955,24 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", - "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.940.0.tgz", + "integrity": "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", + "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", @@ -970,6 +1008,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -983,12 +1022,13 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.936.0.tgz", - "integrity": "sha512-8qS0GFUqkmwO7JZ0P8tdluBmt1UTfYUah8qJXGzNh9n1Pcb0AIeT117cCSiCUtwk+gDbJvd4hhRIhJCNr5wgjg==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.940.0.tgz", + "integrity": "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.936.0", + "@aws-sdk/middleware-sdk-s3": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", @@ -1000,13 +1040,14 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", - "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.940.0.tgz", + "integrity": "sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", @@ -1021,6 +1062,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -1034,6 +1076,7 @@ "version": "3.893.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1046,6 +1089,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -1062,6 +1106,7 @@ "version": "3.893.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1074,6 +1119,7 @@ "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.936.0", @@ -1083,12 +1129,13 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", - "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", + "version": "3.940.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.940.0.tgz", + "integrity": "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", @@ -1110,6 +1157,7 @@ "version": "3.930.0", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -1124,6 +1172,7 @@ "version": "5.2.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, "funding": [ { "type": "github", @@ -1142,6 +1191,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -4103,6 +4153,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4116,6 +4167,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4128,6 +4180,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-base64": "^4.3.0", @@ -4141,6 +4194,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.5", @@ -4158,6 +4212,7 @@ "version": "3.18.5", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.2.6", @@ -4179,6 +4234,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.5", @@ -4195,6 +4251,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.5.tgz", "integrity": "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -4210,6 +4267,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.5.tgz", "integrity": "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", @@ -4224,6 +4282,7 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.5.tgz", "integrity": "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4237,6 +4296,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.5.tgz", "integrity": "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", @@ -4251,6 +4311,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.5.tgz", "integrity": "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-codec": "^4.2.5", @@ -4265,6 +4326,7 @@ "version": "5.3.6", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.5", @@ -4281,6 +4343,7 @@ "version": "4.2.6", "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.6.tgz", "integrity": "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", @@ -4296,6 +4359,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4311,6 +4375,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.5.tgz", "integrity": "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4325,6 +4390,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4338,6 +4404,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4350,6 +4417,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.5.tgz", "integrity": "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4364,6 +4432,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.5", @@ -4378,6 +4447,7 @@ "version": "4.3.12", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.18.5", @@ -4397,6 +4467,7 @@ "version": "4.4.12", "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.5", @@ -4417,6 +4488,7 @@ "version": "4.2.6", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.5", @@ -4431,6 +4503,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4444,6 +4517,7 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.5", @@ -4459,6 +4533,7 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.2.5", @@ -4475,6 +4550,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4488,6 +4564,7 @@ "version": "5.3.5", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4501,6 +4578,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4515,6 +4593,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4528,6 +4607,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0" @@ -4540,6 +4620,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4553,6 +4634,7 @@ "version": "5.3.5", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", @@ -4572,6 +4654,7 @@ "version": "4.9.8", "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.18.5", @@ -4590,6 +4673,7 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4602,6 +4686,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^4.2.5", @@ -4616,6 +4701,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.2.0", @@ -4630,6 +4716,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4642,6 +4729,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4654,6 +4742,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", @@ -4667,6 +4756,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4679,6 +4769,7 @@ "version": "4.3.11", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.5", @@ -4694,6 +4785,7 @@ "version": "4.2.14", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.4.3", @@ -4712,6 +4804,7 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.5", @@ -4726,6 +4819,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4738,6 +4832,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.9.0", @@ -4751,6 +4846,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^4.2.5", @@ -4765,6 +4861,7 @@ "version": "4.5.6", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", @@ -4784,6 +4881,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4796,6 +4894,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.2.0", @@ -4809,6 +4908,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.2.5", @@ -4823,6 +4923,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5899,9 +6000,10 @@ "license": "ISC" }, "node_modules/bowser": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", - "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { diff --git a/package.json b/package.json index 83782685e..601e50aee 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "supertest": "^6.3.4" }, "dependencies": { - "@aws-sdk/client-s3": "^3.920.0", + "@aws-sdk/client-s3": "^3.940.0", "@azure/storage-blob": "^12.26.0", "@babel/cli": "^7.15.4", "@babel/core": "^7.10.2", diff --git a/src/__tests__/integration/bmPaidLaborCostRoutes.integration.test.js b/src/__tests__/integration/bmPaidLaborCostRoutes.integration.test.js new file mode 100644 index 000000000..51720758d --- /dev/null +++ b/src/__tests__/integration/bmPaidLaborCostRoutes.integration.test.js @@ -0,0 +1,113 @@ +/** + * Integration Tests for Paid Labor Cost Routes + * + * This test suite covers: + * 1. Authorization tests (401 responses without auth token) + * 2. Route functionality tests (method validation, route existence) + * + * Note: These tests require the full app to be loaded, which may require + * additional dependencies (e.g., @aws-sdk/client-s3) to be installed. + */ + +const request = require('supertest'); +const { app } = require('../../app'); + +const agent = request.agent(app); + +describe('bmPaidLaborCostRoutes tests', () => { + // Global timeout for the entire test suite + jest.setTimeout(60000); // 1 minute + + beforeAll(async () => { + console.log('=== Starting Paid Labor Cost Integration Test Setup ==='); + console.log('✓ Test setup completed'); + console.log('=== Paid Labor Cost Integration Test Setup Complete ==='); + }, 60000); // 1 minute timeout for beforeAll + + /** + * Authorization Tests + * Verifies that routes require authentication (return 401 without auth header) + */ + describe('Authorization Tests', () => { + it('should return 401 if authorization header is not present for GET /api/labor-cost', async () => { + console.log('Testing 401 unauthorized access for GET /api/labor-cost...'); + + try { + await agent.get('/api/labor-cost').expect(401); + console.log('✓ GET /api/labor-cost 401 test passed'); + } catch (error) { + console.error('❌ GET /api/labor-cost 401 test failed:', error.message); + throw error; + } + }, 30000); + + it('should return 401 if authorization header is not present for GET with query params', async () => { + console.log('Testing 401 unauthorized access for GET /api/labor-cost with query params...'); + + try { + await agent.get('/api/labor-cost?projects=["A"]').expect(401); + console.log('✓ GET /api/labor-cost with query params 401 test passed'); + } catch (error) { + console.error('❌ GET /api/labor-cost with query params 401 test failed:', error.message); + throw error; + } + }, 30000); + }); + + /** + * Route Functionality Tests + * Verifies route configuration and method handling + * Note: Full functionality tests require database connection. + * If database is not available, these become smoke tests. + */ + describe('Route Functionality Tests', () => { + it('should return 404 or method not allowed for POST method', async () => { + console.log('Testing POST method (should not be allowed)...'); + + try { + const response = await agent.post('/api/labor-cost').send({}); + // POST should return 404 (route doesn't exist), 405 (method not allowed), or 401 (auth required) + const { status } = response; + expect([404, 405, 401]).toContain(status); + console.log(`✓ POST /api/labor-cost returned ${status} as expected`); + } catch (error) { + // If it throws, check the status code + const status = error.status || error.response?.status; + if ([404, 405, 401].includes(status)) { + console.log(`✓ POST /api/labor-cost returned ${status} as expected`); + } else { + console.error( + `❌ POST /api/labor-cost test failed with status ${status}:`, + error.message, + ); + throw error; + } + } + }, 30000); + + // Note: The following test requires authentication token + // It will fail with 401 if no token is provided, which is expected + // To fully test route existence, a valid token would be needed + it('should have route configured (returns 401 without auth, not 404)', async () => { + console.log('Testing route existence...'); + + try { + const response = await agent.get('/api/labor-cost'); + // Route exists if we get 401 (unauthorized) rather than 404 (not found) + expect(response.status).toBe(401); + console.log('✓ Route exists (returned 401, not 404)'); + } catch (error) { + // If error status is 401, that's good - route exists + if (error.status === 401 || error.response?.status === 401) { + console.log('✓ Route exists (returned 401, not 404)'); + } else if (error.status === 404 || error.response?.status === 404) { + console.error('❌ Route does not exist (returned 404)'); + throw new Error('Route /api/labor-cost not found'); + } else { + console.error('❌ Route test failed:', error.message); + throw error; + } + } + }, 30000); + }); +}); diff --git a/src/controllers/bmdashboard/__tests__/bmPaidLaborCostController.test.js b/src/controllers/bmdashboard/__tests__/bmPaidLaborCostController.test.js new file mode 100644 index 000000000..db43c9db0 --- /dev/null +++ b/src/controllers/bmdashboard/__tests__/bmPaidLaborCostController.test.js @@ -0,0 +1,1216 @@ +/** + * Unit Tests for Paid Labor Cost Controller + * + * This test suite covers: + * 1. Helper function unit tests (looksLikeJson, parseArrayParam, parseDateRangeParam, isValidDateValue) + * 2. Controller logic tests (parameter validation, query building, response formatting, error handling) + * 3. Edge case tests (boundary conditions, request object edge cases) + * + * Coverage Target: >90% (currently ~97%) + */ + +jest.mock('../../../models/laborCost'); +jest.mock('../../../startup/logger'); + +const LaborCost = require('../../../models/laborCost'); +const logger = require('../../../startup/logger'); +const bmPaidLaborCostController = require('../bmPaidLaborCostController'); + +/** + * Helper Functions Test Suite + * Tests private helper functions exported via testExports for direct unit testing + */ +describe('bmPaidLaborCostController - Helper Functions', () => { + // Get test exports for helper functions + const { looksLikeJson, parseArrayParam, parseDateRangeParam, isValidDateValue } = + bmPaidLaborCostController.testExports; + + /** + * Tests for looksLikeJson() helper function + * Validates detection of JSON-like strings (objects and arrays) + */ + describe('looksLikeJson()', () => { + it('should return true for valid JSON object string', () => { + expect(looksLikeJson('{"key":"value"}')).toBe(true); + }); + + it('should return true for nested JSON object', () => { + expect(looksLikeJson('{"nested":{"key":"val"}}')).toBe(true); + }); + + it('should return true for JSON array string', () => { + expect(looksLikeJson('["item1","item2"]')).toBe(true); + }); + + it('should return true for JSON with leading whitespace', () => { + expect(looksLikeJson(' {"key":"value"}')).toBe(true); + }); + + it('should return false for plain string', () => { + expect(looksLikeJson('plain text')).toBe(false); + }); + + it('should return false for comma-separated string', () => { + expect(looksLikeJson('Project A, Project B')).toBe(false); + }); + + it('should return false for null input', () => { + expect(looksLikeJson(null)).toBe(false); + }); + + it('should return false for undefined input', () => { + expect(looksLikeJson(undefined)).toBe(false); + }); + + it('should return false for number input', () => { + expect(looksLikeJson(123)).toBe(false); + }); + + it('should return false for object (not string)', () => { + expect(looksLikeJson({})).toBe(false); + }); + + it('should return false for array (not string)', () => { + expect(looksLikeJson([])).toBe(false); + }); + + it('should return false for empty string', () => { + expect(looksLikeJson('')).toBe(false); + }); + }); + + /** + * Tests for parseArrayParam() helper function + * Validates parsing of array parameters from query strings + * Handles JSON arrays, comma-separated strings, and error cases + */ + describe('parseArrayParam()', () => { + it('should return empty array for null input', () => { + expect(parseArrayParam(null)).toEqual([]); + }); + + it('should return empty array for undefined input', () => { + expect(parseArrayParam(undefined)).toEqual([]); + }); + + it('should return array as-is when already an array', () => { + const input = ['Project A', 'Project B']; + expect(parseArrayParam(input)).toEqual(input); + }); + + it('should parse valid JSON array string', () => { + expect(parseArrayParam('["Project A","Project B"]')).toEqual(['Project A', 'Project B']); + }); + + it('should parse single item JSON array', () => { + expect(parseArrayParam('["Single"]')).toEqual(['Single']); + }); + + it('should wrap JSON string in array', () => { + expect(parseArrayParam('"Project A"')).toEqual(['Project A']); + }); + + it('should return error object for JSON object (invalid)', () => { + const result = parseArrayParam('{"key":"value"}'); + expect(result).toHaveProperty('__invalidFormat', true); + expect(result).toHaveProperty('__error'); + expect(result.__error).toContain('JSON object provided instead of array'); + }); + + it('should parse comma-separated string', () => { + expect(parseArrayParam('Project A,Project B')).toEqual(['Project A', 'Project B']); + }); + + it('should parse comma-separated string with spaces', () => { + expect(parseArrayParam('Project A, Project B')).toEqual(['Project A', 'Project B']); + }); + + it('should return single value array for string without comma', () => { + expect(parseArrayParam('Single Project')).toEqual(['Single Project']); + }); + + it('should return error object for mixed JSON and plain string', () => { + const result = parseArrayParam('{"json":true},Plain'); + expect(result).toHaveProperty('__invalidFormat', true); + expect(result).toHaveProperty('__error'); + expect(result.__error).toContain('Cannot mix JSON'); + }); + + it('should return error object for invalid JSON that looks like JSON', () => { + // '[incomplete' starts with '[' so looksLikeJson returns true, triggering error + const result = parseArrayParam('[incomplete'); + expect(result).toHaveProperty('__invalidFormat', true); + expect(result).toHaveProperty('__error'); + expect(result.__error).toContain('Cannot mix JSON'); + }); + + it('should return empty array for empty string', () => { + expect(parseArrayParam('')).toEqual([]); + }); + + it('should return empty array for string with only commas and spaces', () => { + expect(parseArrayParam(' , , ')).toEqual([]); + }); + + it('should return empty array for number input', () => { + expect(parseArrayParam(123)).toEqual([]); + }); + }); + + /** + * Tests for parseDateRangeParam() helper function + * Validates parsing of date_range parameter from query strings + */ + describe('parseDateRangeParam()', () => { + it('should return null for null input', () => { + expect(parseDateRangeParam(null)).toBeNull(); + }); + + it('should return null for undefined input', () => { + expect(parseDateRangeParam(undefined)).toBeNull(); + }); + + it('should return object as-is when already an object', () => { + const input = { start_date: '2025-04-01', end_date: '2025-04-30' }; + expect(parseDateRangeParam(input)).toEqual(input); + }); + + it('should parse valid JSON string', () => { + const result = parseDateRangeParam('{"start_date":"2025-04-01","end_date":"2025-04-30"}'); + expect(result).toEqual({ + start_date: '2025-04-01', + end_date: '2025-04-30', + }); + }); + + it('should parse partial object JSON string', () => { + const result = parseDateRangeParam('{"start_date":"2025-04-01"}'); + expect(result).toEqual({ start_date: '2025-04-01' }); + }); + + it('should parse empty object JSON string', () => { + expect(parseDateRangeParam('{}')).toEqual({}); + }); + + it('should return null for invalid JSON string', () => { + expect(parseDateRangeParam('invalid json')).toBeNull(); + }); + + it('should return null for malformed JSON string', () => { + expect(parseDateRangeParam('{malformed')).toBeNull(); + }); + + it('should return null for number input', () => { + expect(parseDateRangeParam(123)).toBeNull(); + }); + + it('should parse string "null" to null', () => { + expect(parseDateRangeParam('null')).toBeNull(); + }); + }); + + /** + * Tests for isValidDateValue() helper function + * Validates ISO 8601 date format and catches invalid dates (e.g., invalid months/days) + */ + describe('isValidDateValue()', () => { + it('should return true for null (optional)', () => { + expect(isValidDateValue(null)).toBe(true); + }); + + it('should return true for undefined (optional)', () => { + expect(isValidDateValue(undefined)).toBe(true); + }); + + it('should return true for empty string (falsy)', () => { + expect(isValidDateValue('')).toBe(true); + }); + + it('should return true for valid date-only format', () => { + expect(isValidDateValue('2025-04-01')).toBe(true); + }); + + it('should return true for another valid date-only format', () => { + expect(isValidDateValue('2025-04-30')).toBe(true); + }); + + it('should return true for end of year date', () => { + expect(isValidDateValue('2025-12-31')).toBe(true); + }); + + it('should return true for start of year date', () => { + expect(isValidDateValue('2025-01-01')).toBe(true); + }); + + it('should return true for valid datetime with Z', () => { + expect(isValidDateValue('2025-04-01T00:00:00Z')).toBe(true); + }); + + it('should return true for valid datetime with milliseconds', () => { + expect(isValidDateValue('2025-04-01T12:30:45.123Z')).toBe(true); + }); + + it('should return true for valid datetime with timezone offset', () => { + expect(isValidDateValue('2025-04-01T00:00:00+05:30')).toBe(true); + }); + + it('should return false for invalid month (13)', () => { + expect(isValidDateValue('2025-13-01')).toBe(false); + }); + + it('should return false for invalid month (0)', () => { + expect(isValidDateValue('2025-00-01')).toBe(false); + }); + + it('should return false for invalid day (32)', () => { + expect(isValidDateValue('2025-04-32')).toBe(false); + }); + + it('should return false for invalid day (0)', () => { + expect(isValidDateValue('2025-04-00')).toBe(false); + }); + + it('should return false for invalid day for February', () => { + expect(isValidDateValue('2025-02-30')).toBe(false); + }); + + it('should return false for Feb 29 in non-leap year', () => { + expect(isValidDateValue('2025-02-29')).toBe(false); + }); + + it('should return true for Feb 29 in leap year (2024)', () => { + expect(isValidDateValue('2024-02-29')).toBe(true); + }); + + it('should return false for unparseable string', () => { + expect(isValidDateValue('not-a-date')).toBe(false); + }); + + it('should return true for date with slashes (JavaScript Date can parse it)', () => { + // JavaScript Date constructor can parse '2025/04/01' format + expect(isValidDateValue('2025/04/01')).toBe(true); + }); + + it('should return false for number input', () => { + expect(isValidDateValue(123)).toBe(false); + }); + + it('should return false for object input', () => { + expect(isValidDateValue({})).toBe(false); + }); + + it('should handle single digit month/day format', () => { + // JavaScript Date can parse this, but our validation checks component matching + // Single digit format like "2025-4-1" may or may not pass depending on Date parsing + // Testing actual behavior + const result = isValidDateValue('2025-4-1'); + expect(typeof result).toBe('boolean'); + }); + }); + + /** + * Controller Logic Test Suite + * Tests the main getLaborCost() function with mocked dependencies + */ + describe('bmPaidLaborCostController - getLaborCost()', () => { + let controller; + let mockReq; + let mockRes; + let mockSort; + let mockLean; + let mockExec; + + /** + * Setup mocks before each test + * Creates chainable mock for LaborCost.find().sort().lean().exec() + */ + beforeEach(() => { + jest.clearAllMocks(); + + // Set up mock chain for LaborCost.find().sort().lean().exec() + // The chain is: find() -> sort() -> lean() -> exec() -> Promise + mockExec = jest.fn().mockResolvedValue([]); + mockLean = jest.fn().mockReturnValue({ exec: mockExec }); + mockSort = jest.fn().mockReturnValue({ lean: mockLean }); + + // Set up LaborCost.find to return an object with sort method + // This creates the chain: find() returns {sort: fn}, sort() returns {lean: fn}, etc. + LaborCost.find = jest.fn().mockReturnValue({ sort: mockSort }); + + // Mock logger + logger.logException = jest.fn(); + + // Set up mock request/response + mockReq = { + query: {}, + method: 'GET', + originalUrl: '/api/labor-cost', + url: '/api/labor-cost', + }; + + mockRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + // Initialize controller AFTER mocks are set up + controller = bmPaidLaborCostController(); + }); + + /** + * Parameter Validation Tests - Projects + * Tests validation and parsing of projects query parameter + */ + describe('Parameter Validation - Projects', () => { + it('should succeed when projects param is undefined', async () => { + mockReq.query = {}; + // Reset mockExec for this test + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + expect(mockSort).toHaveBeenCalledWith({ date: 1 }); + expect(mockLean).toHaveBeenCalled(); + expect(mockExec).toHaveBeenCalled(); + }); + + it('should succeed with empty array projects', async () => { + mockReq.query = { projects: '[]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should succeed with valid JSON array projects', async () => { + mockReq.query = { projects: '["Project A"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ project_name: { $in: ['Project A'] } }); + }); + + it('should succeed with multiple projects', async () => { + mockReq.query = { projects: '["Project A","Project B"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + project_name: { $in: ['Project A', 'Project B'] }, + }); + }); + + it('should succeed with comma-separated projects', async () => { + mockReq.query = { projects: 'Project A,Project B' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + project_name: { $in: ['Project A', 'Project B'] }, + }); + }); + + it('should return 400 for JSON object projects (invalid)', async () => { + mockReq.query = { projects: '{"name":"Project A"}' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: expect.stringContaining('JSON object provided instead of array'), + }); + }); + + it('should return 400 for non-string array elements', async () => { + mockReq.query = { projects: '[1, 2, 3]' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: 'All project names must be strings', + }); + }); + + it('should return 400 for mixed valid/invalid project elements', async () => { + mockReq.query = { projects: '["valid", 123]' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: 'All project names must be strings', + }); + }); + + it('should return 400 for mixed format projects', async () => { + mockReq.query = { projects: '{"json":true},Plain' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: expect.stringContaining('Cannot mix JSON'), + }); + }); + }); + + /** + * Parameter Validation Tests - Tasks + * Tests validation and parsing of tasks query parameter + * Similar pattern to projects validation + */ + describe('Parameter Validation - Tasks', () => { + it('should succeed when tasks param is undefined', async () => { + mockReq.query = {}; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should succeed with empty array tasks', async () => { + mockReq.query = { tasks: '[]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should succeed with valid JSON array tasks', async () => { + mockReq.query = { tasks: '["Task 1"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ task: { $in: ['Task 1'] } }); + }); + + it('should succeed with multiple tasks', async () => { + mockReq.query = { tasks: '["Task 1","Task 2"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ task: { $in: ['Task 1', 'Task 2'] } }); + }); + + it('should succeed with comma-separated tasks', async () => { + mockReq.query = { tasks: 'Task 1,Task 2' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ task: { $in: ['Task 1', 'Task 2'] } }); + }); + + it('should return 400 for JSON object tasks (invalid)', async () => { + mockReq.query = { tasks: '{"name":"Task 1"}' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: expect.stringContaining('JSON object provided instead of array'), + }); + }); + + it('should return 400 for non-string array elements in tasks', async () => { + mockReq.query = { tasks: '[1, 2, 3]' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: 'All task names must be strings', + }); + }); + }); + + /** + * Parameter Validation Tests - Date Range + * Tests validation and parsing of date_range query parameter + * Includes partial date ranges and date format validation + */ + describe('Parameter Validation - Date Range', () => { + it('should succeed when date_range is undefined', async () => { + mockReq.query = {}; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should succeed with null string date_range', async () => { + mockReq.query = { date_range: 'null' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should succeed with valid date_range with both dates', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-01","end_date":"2025-04-30"}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + date: { + $gte: expect.any(Date), + $lte: expect.any(Date), + }, + }); + }); + + it('should succeed with only start_date', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-01","end_date":null}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + date: { + $gte: expect.any(Date), + }, + }); + }); + + it('should succeed with only end_date', async () => { + mockReq.query = { + date_range: '{"start_date":null,"end_date":"2025-04-30"}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + date: { + $lte: expect.any(Date), + }, + }); + }); + + it('should succeed with empty date_range object', async () => { + mockReq.query = { date_range: '{}' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should return 400 for invalid JSON date_range', async () => { + mockReq.query = { date_range: '{malformed' }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_PARAMETER', + error: expect.stringContaining('date_range must be a valid JSON object'), + }); + }); + + it('should return 422 for invalid start month', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-13-01","end_date":"2025-04-30"}', + }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(422); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_DATE_FORMAT', + error: expect.stringContaining('start_date must be a valid ISO 8601'), + }); + }); + + it('should return 422 for invalid end day', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-01","end_date":"2025-04-32"}', + }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(422); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_DATE_FORMAT', + error: expect.stringContaining('end_date must be a valid ISO 8601'), + }); + }); + + it('should return 400 when start_date > end_date', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-30","end_date":"2025-04-01"}', + }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_DATE_RANGE', + error: 'start_date must be before or equal to end_date', + }); + }); + + it('should return 422 for non-string start_date', async () => { + mockReq.query = { + date_range: '{"start_date":12345,"end_date":"2025-04-30"}', + }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(422); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_DATE_FORMAT', + error: expect.stringContaining('start_date must be a string'), + }); + }); + + it('should return 422 for non-string end_date', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-01","end_date":12345}', + }; + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(422); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INVALID_DATE_FORMAT', + error: expect.stringContaining('end_date must be a string'), + }); + }); + }); + + /** + * Query Building Tests + * Verifies MongoDB query construction with various filter combinations + */ + describe('Query Building and Database Call', () => { + it('should build empty query filter when no filters provided', async () => { + mockReq.query = {}; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({}); + expect(mockSort).toHaveBeenCalledWith({ date: 1 }); + expect(mockLean).toHaveBeenCalled(); + expect(mockExec).toHaveBeenCalled(); + }); + + it('should build query filter with projects only', async () => { + mockReq.query = { projects: '["A"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({ project_name: { $in: ['A'] } }); + }); + + it('should build query filter with tasks only', async () => { + mockReq.query = { tasks: '["T1"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({ task: { $in: ['T1'] } }); + }); + + it('should build query filter with date range only', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-01","end_date":"2025-04-30"}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({ + date: { + $gte: expect.any(Date), + $lte: expect.any(Date), + }, + }); + }); + + it('should build combined query filter with all filters', async () => { + mockReq.query = { + projects: '["A"]', + tasks: '["T1"]', + date_range: '{"start_date":"2025-04-01","end_date":"2025-04-30"}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({ + project_name: { $in: ['A'] }, + task: { $in: ['T1'] }, + date: { + $gte: expect.any(Date), + $lte: expect.any(Date), + }, + }); + }); + + it('should build query filter with start date only', async () => { + mockReq.query = { + date_range: '{"start_date":"2025-04-01","end_date":null}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({ + date: { + $gte: expect.any(Date), + }, + }); + }); + + it('should build query filter with end date only', async () => { + mockReq.query = { + date_range: '{"start_date":null,"end_date":"2025-04-30"}', + }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(LaborCost.find).toHaveBeenCalledWith({ + date: { + $lte: expect.any(Date), + }, + }); + }); + }); + + /** + * Response Formatting Tests + * Verifies data transformation and totalCost calculation + */ + describe('Response Formatting', () => { + it('should return empty results with totalCost 0', async () => { + mockReq.query = {}; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(mockRes.json).toHaveBeenCalledWith({ + totalCost: 0, + data: [], + }); + }); + + it('should format single record correctly', async () => { + const testDate = new Date('2025-04-01'); + mockReq.query = {}; + mockExec.mockResolvedValue([ + { + project_name: 'A', + task: 'T1', + date: testDate, + cost: 100, + }, + ]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(mockRes.json).toHaveBeenCalledWith({ + totalCost: 100, + data: [ + { + project: 'A', + task: 'T1', + date: testDate.toISOString(), + cost: 100, + }, + ], + }); + }); + + it('should format multiple records and calculate totalCost', async () => { + const testDate1 = new Date('2025-04-01'); + const testDate2 = new Date('2025-04-02'); + mockReq.query = {}; + mockExec.mockResolvedValue([ + { + project_name: 'A', + task: 'T1', + date: testDate1, + cost: 100, + }, + { + project_name: 'B', + task: 'T2', + date: testDate2, + cost: 200, + }, + ]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(mockRes.json).toHaveBeenCalledWith({ + totalCost: 300, + data: [ + { + project: 'A', + task: 'T1', + date: testDate1.toISOString(), + cost: 100, + }, + { + project: 'B', + task: 'T2', + date: testDate2.toISOString(), + cost: 200, + }, + ], + }); + }); + + it('should convert string cost to number', async () => { + const testDate = new Date('2025-04-01'); + mockReq.query = {}; + mockExec.mockResolvedValue([ + { + project_name: 'A', + task: 'T1', + date: testDate, + cost: '100', + }, + ]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.json).toHaveBeenCalledWith({ + totalCost: 100, + data: [ + { + project: 'A', + task: 'T1', + date: testDate.toISOString(), + cost: 100, + }, + ], + }); + }); + + it('should handle null date in response', async () => { + mockReq.query = {}; + mockExec.mockResolvedValue([ + { + project_name: 'A', + task: 'T1', + date: null, + cost: 100, + }, + ]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.json).toHaveBeenCalledWith({ + totalCost: 100, + data: [ + { + project: 'A', + task: 'T1', + date: null, + cost: 100, + }, + ], + }); + }); + }); + + /** + * Error Handling Tests + * Verifies proper error handling for database errors and unexpected errors + */ + describe('Error Handling', () => { + it('should handle MongoError and return 500', async () => { + mockReq.query = {}; + const error = new Error('Database connection failed'); + error.name = 'MongoError'; + mockExec.mockRejectedValue(error); + + await controller.getLaborCost(mockReq, mockRes); + + expect(logger.logException).toHaveBeenCalledWith( + error, + 'getLaborCost - Database Error - Paid Labor Cost Controller', + { + query: {}, + method: 'GET', + url: '/api/labor-cost', + }, + ); + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'DATABASE_ERROR', + error: + 'A database error occurred while fetching labor cost data. Please try again later.', + }); + }); + + it('should handle MongooseError and return 500', async () => { + mockReq.query = {}; + const error = new Error('Mongoose error'); + error.name = 'MongooseError'; + mockExec.mockRejectedValue(error); + + await controller.getLaborCost(mockReq, mockRes); + + expect(logger.logException).toHaveBeenCalled(); + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'DATABASE_ERROR', + error: + 'A database error occurred while fetching labor cost data. Please try again later.', + }); + }); + + it('should handle CastError and return 500', async () => { + mockReq.query = {}; + const error = new Error('Cast error'); + error.name = 'CastError'; + mockExec.mockRejectedValue(error); + + await controller.getLaborCost(mockReq, mockRes); + + expect(logger.logException).toHaveBeenCalled(); + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'DATABASE_ERROR', + error: + 'A database error occurred while fetching labor cost data. Please try again later.', + }); + }); + + it('should handle ValidationError and return 500', async () => { + mockReq.query = {}; + const error = new Error('Validation error'); + error.name = 'ValidationError'; + mockExec.mockRejectedValue(error); + + await controller.getLaborCost(mockReq, mockRes); + + expect(logger.logException).toHaveBeenCalled(); + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'DATABASE_ERROR', + error: + 'A database error occurred while fetching labor cost data. Please try again later.', + }); + }); + + it('should handle connection error and return 500', async () => { + mockReq.query = {}; + const error = new Error('Mongo connection failed'); + mockExec.mockRejectedValue(error); + + await controller.getLaborCost(mockReq, mockRes); + + expect(logger.logException).toHaveBeenCalled(); + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'DATABASE_ERROR', + error: + 'A database error occurred while fetching labor cost data. Please try again later.', + }); + }); + + it('should handle generic error and return 500', async () => { + mockReq.query = {}; + const error = new Error('Unknown error'); + mockExec.mockRejectedValue(error); + + await controller.getLaborCost(mockReq, mockRes); + + expect(logger.logException).toHaveBeenCalledWith( + error, + 'getLaborCost - Unexpected Error - Paid Labor Cost Controller', + { + query: {}, + method: 'GET', + url: '/api/labor-cost', + }, + ); + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + Code: 'INTERNAL_SERVER_ERROR', + error: + 'An unexpected error occurred while fetching labor cost data. Please try again later.', + }); + }); + }); + + /** + * Edge Cases - Boundary Conditions + * Tests extreme values and unusual inputs (long strings, Unicode, special chars, etc.) + */ + describe('Edge Cases - Boundary Conditions', () => { + it('should handle empty string projects parameter', async () => { + mockReq.query = { projects: '' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({}); + }); + + it('should handle very long project name', async () => { + const longProjectName = 'A'.repeat(1000); + mockReq.query = { projects: `["${longProjectName}"]` }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + project_name: { $in: [longProjectName] }, + }); + }); + + it('should handle Unicode characters in project names', async () => { + mockReq.query = { projects: '["日本語"]' }; + mockExec.mockResolvedValue([]); + + await controller.getLaborCost(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(LaborCost.find).toHaveBeenCalledWith({ + project_name: { $in: ['日本語'] }, + }); + }); + + it('should handle special characters in project names', async () => { + mockReq.query = { projects: '["Project