diff --git a/CHANGELOG.md b/CHANGELOG.md index 3046d1c..e54ea46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- matcher `toHaveReceivedCommandExactlyOnceWith` can be used to verify there are + no additional calls + +### Changed + +- Update dependencies. This bumps `@vitest/expect` dependency to `^3.0.1` + ## [5.1.0] - 2025-01-11 ### Changed diff --git a/README.md b/README.md index 3a28c39..600adff 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ You must register the new matchers explicity (think about putting this to a [set }, }); - to add the custom mat chers before each test run + to add the custom matchers before each test run */ import { expect } from "vitest"; import { @@ -57,6 +57,8 @@ import { toHaveReceivedLastCommandWith, toReceiveAnyCommand, toHaveReceivedAnyCommand, + toReceiveCommandExactlyOnceWith, + toHaveReceivedCommandExactlyOnceWith, } from "aws-sdk-client-mock-vitest"; expect.extend({ @@ -74,6 +76,8 @@ expect.extend({ toHaveReceivedLastCommandWith, toReceiveAnyCommand, toHaveReceivedAnyCommand, + toReceiveCommandExactlyOnceWith, + toHaveReceivedCommandExactlyOnceWith, }); ``` diff --git a/package-lock.json b/package-lock.json index a17818e..34fc9d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,25 +9,25 @@ "version": "5.1.0", "license": "MIT", "dependencies": { - "@vitest/expect": "^2.1.3", - "tslib": "^2.8.0" + "@vitest/expect": "^3.0.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@aws-sdk/client-s3": "^3.726.1", - "@aws-sdk/client-secrets-manager": "^3.726.1", + "@aws-sdk/client-s3": "^3.730.0", + "@aws-sdk/client-secrets-manager": "^3.730.0", "@eslint/js": "^9.18.0", - "@stylistic/eslint-plugin": "^2.12.1", + "@stylistic/eslint-plugin": "^2.13.0", "@types/eslint__js": "^8.42.3", - "@types/node": "^22.10.5", - "@vitest/coverage-v8": "^2.1.8", - "@vitest/eslint-plugin": "^1.1.24", + "@types/node": "^22.10.7", + "@vitest/coverage-v8": "^3.0.2", + "@vitest/eslint-plugin": "^1.1.25", "aws-sdk-client-mock": "^4.1.0", "eslint": "^9.18.0", "eslint-config-flat-gitignore": "^1.0.0", "eslint-plugin-perfectionist": "^4.6.0", "typescript": "^5.7.3", - "typescript-eslint": "^8.19.1", - "vitest": "^2.1.8" + "typescript-eslint": "^8.20.0", + "vitest": "^3.0.2" }, "peerDependencies": { "@smithy/types": ">=3.5.0", @@ -267,35 +267,33 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.726.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.726.1.tgz", - "integrity": "sha512-UpOGcob87DiuS2d3fW6vDZg94g57mNiOSkzvR/6GOdvBSlUgk8LLwVzGASB71FdKMl1EGEr4MeD5uKH9JsG+dw==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.730.0.tgz", + "integrity": "sha512-45RAzBWCR3E2p9Oale1TqIHPxqcbJoNmLDU+z/5kqnLml32Ntkvioe3BRMq+eaSmiFtOyviF1snfuxPUkRrUWA==", "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/client-sso-oidc": "3.726.0", - "@aws-sdk/client-sts": "3.726.1", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/credential-provider-node": "3.730.0", "@aws-sdk/middleware-bucket-endpoint": "3.726.0", "@aws-sdk/middleware-expect-continue": "3.723.0", - "@aws-sdk/middleware-flexible-checksums": "3.723.0", + "@aws-sdk/middleware-flexible-checksums": "3.730.0", "@aws-sdk/middleware-host-header": "3.723.0", "@aws-sdk/middleware-location-constraint": "3.723.0", "@aws-sdk/middleware-logger": "3.723.0", "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-sdk-s3": "3.723.0", + "@aws-sdk/middleware-sdk-s3": "3.730.0", "@aws-sdk/middleware-ssec": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/middleware-user-agent": "3.730.0", "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/signature-v4-multi-region": "3.723.0", + "@aws-sdk/signature-v4-multi-region": "3.730.0", "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-endpoints": "3.730.0", "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", + "@aws-sdk/util-user-agent-node": "3.730.0", "@aws-sdk/xml-builder": "3.723.0", "@smithy/config-resolver": "^4.0.0", "@smithy/core": "^3.0.0", @@ -337,27 +335,25 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.726.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.726.1.tgz", - "integrity": "sha512-eO9WpE8IyQrs2xWhfQCdHcVTHQTwJ56JGx3FhwhtFWWYHIS0c1bTIAvP5E3jSWAZNaK1iWdVexz3yGi3aAnGzA==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.730.0.tgz", + "integrity": "sha512-ysc8dXQrTICMCTOboJDJCHxI9pKG+r9trejHatR4lv5ayDwLVgQ3hv1VxImYlyZVHpBWzGK3fFn+cPCwmIp4mA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.726.0", - "@aws-sdk/client-sts": "3.726.1", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/credential-provider-node": "3.730.0", "@aws-sdk/middleware-host-header": "3.723.0", "@aws-sdk/middleware-logger": "3.723.0", "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/middleware-user-agent": "3.730.0", "@aws-sdk/region-config-resolver": "3.723.0", "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-endpoints": "3.730.0", "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", + "@aws-sdk/util-user-agent-node": "3.730.0", "@smithy/config-resolver": "^4.0.0", "@smithy/core": "^3.0.0", "@smithy/fetch-http-handler": "^5.0.0", @@ -392,130 +388,24 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.726.0.tgz", - "integrity": "sha512-NM5pjv2qglEc4XN3nnDqtqGsSGv1k5YTmzDo3W3pObItHmpS8grSeNfX9zSH+aVl0Q8hE4ZIgvTPNZ+GzwVlqg==", - "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.723.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@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.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.726.0.tgz", - "integrity": "sha512-5JzTX9jwev7+y2Jkzjz0pd1wobB5JQfPOQF3N2DrJ5Pao0/k6uRYwE4NqB0p0HlGrMTDm7xNq7OSPPIPG575Jw==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.730.0.tgz", + "integrity": "sha512-mI8kqkSuVlZklewEmN7jcbBMyVODBld3MsTjCKSl5ztduuPX69JD7nXLnWWPkw1PX4aGTO24AEoRMGNxntoXUg==", "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.723.0", - "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/middleware-host-header": "3.723.0", "@aws-sdk/middleware-logger": "3.723.0", "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/middleware-user-agent": "3.730.0", "@aws-sdk/region-config-resolver": "3.723.0", "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-endpoints": "3.730.0", "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@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.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.726.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.726.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.726.1.tgz", - "integrity": "sha512-qh9Q9Vu1hrM/wMBOBIaskwnE4GTFaZu26Q6WHwyWNfj7J8a40vBxpW16c2vYXHLBtwRKM1be8uRLkmDwghpiNw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.726.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-node": "3.726.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", + "@aws-sdk/util-user-agent-node": "3.730.0", "@smithy/config-resolver": "^4.0.0", "@smithy/core": "^3.0.0", "@smithy/fetch-http-handler": "^5.0.0", @@ -548,9 +438,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.723.0.tgz", - "integrity": "sha512-UraXNmvqj3vScSsTkjMwQkhei30BhXlW5WxX6JacMKVtl95c7z0qOXquTWeTalYkFfulfdirUhvSZrl+hcyqTw==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.730.0.tgz", + "integrity": "sha512-jonKyR+2GcqbZj2WDICZS0c633keLc9qwXnePu83DfAoFXMMIMyoR/7FOGf8F3OrIdGh8KzE9VvST+nZCK9EJA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -571,13 +461,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.723.0.tgz", - "integrity": "sha512-OuH2yULYUHTVDUotBoP/9AEUIJPn81GQ/YBtZLoo2QyezRJ2QiO/1epVtbJlhNZRwXrToLEDmQGA2QfC8c7pbA==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.730.0.tgz", + "integrity": "sha512-fFXgo3jBXLWqu8I07Hd96mS7RjrtpDgm3bZShm0F3lKtqDQF+hObFWq9A013SOE+RjMLVfbABhToXAYct3FcBw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/property-provider": "^4.0.0", "@smithy/types": "^4.0.0", @@ -588,13 +478,13 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.723.0.tgz", - "integrity": "sha512-DTsKC6xo/kz/ZSs1IcdbQMTgiYbpGTGEd83kngFc1bzmw7AmK92DBZKNZpumf8R/UfSpTcj9zzUUmrWz1kD0eQ==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.730.0.tgz", + "integrity": "sha512-1aF3elbCzpVhWLAuV63iFElfLOqLGGTp4fkf2VAFIDO3hjshpXUQssTgIWiBwwtJYJdOSxaFrCU7u8frjr/5aQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/fetch-http-handler": "^5.0.0", "@smithy/node-http-handler": "^4.0.0", @@ -610,18 +500,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.726.0.tgz", - "integrity": "sha512-seTtcKL2+gZX6yK1QRPr5mDJIBOatrpoyrO8D5b8plYtV/PDbDW3mtDJSWFHet29G61ZmlNElyXRqQCXn9WX+A==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.730.0.tgz", + "integrity": "sha512-zwsxkBuQuPp06o45ATAnznHzj3+ibop/EaTytNzSv0O87Q59K/jnS/bdtv1n6bhe99XCieRNTihvtS7YklzK7A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-env": "3.723.0", - "@aws-sdk/credential-provider-http": "3.723.0", - "@aws-sdk/credential-provider-process": "3.723.0", - "@aws-sdk/credential-provider-sso": "3.726.0", - "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/credential-provider-env": "3.730.0", + "@aws-sdk/credential-provider-http": "3.730.0", + "@aws-sdk/credential-provider-process": "3.730.0", + "@aws-sdk/credential-provider-sso": "3.730.0", + "@aws-sdk/credential-provider-web-identity": "3.730.0", + "@aws-sdk/nested-clients": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/credential-provider-imds": "^4.0.0", "@smithy/property-provider": "^4.0.0", @@ -631,24 +522,21 @@ }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.726.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.726.0.tgz", - "integrity": "sha512-jjsewBcw/uLi24x8JbnuDjJad4VA9ROCE94uVRbEnGmUEsds75FWOKp3fWZLQlmjLtzsIbJOZLALkZP86liPaw==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.730.0.tgz", + "integrity": "sha512-ztRjh1edY7ut2wwrj1XqHtqPY/NXEYIk5fYf04KKsp8zBi81ScVqP7C+Cst6PFKixjgLSG6RsqMx9GSAalVv0Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.723.0", - "@aws-sdk/credential-provider-http": "3.723.0", - "@aws-sdk/credential-provider-ini": "3.726.0", - "@aws-sdk/credential-provider-process": "3.723.0", - "@aws-sdk/credential-provider-sso": "3.726.0", - "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/credential-provider-env": "3.730.0", + "@aws-sdk/credential-provider-http": "3.730.0", + "@aws-sdk/credential-provider-ini": "3.730.0", + "@aws-sdk/credential-provider-process": "3.730.0", + "@aws-sdk/credential-provider-sso": "3.730.0", + "@aws-sdk/credential-provider-web-identity": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/credential-provider-imds": "^4.0.0", "@smithy/property-provider": "^4.0.0", @@ -661,13 +549,13 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.723.0.tgz", - "integrity": "sha512-fgupvUjz1+jeoCBA7GMv0L6xEk92IN6VdF4YcFhsgRHlHvNgm7ayaoKQg7pz2JAAhG/3jPX6fp0ASNy+xOhmPA==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.730.0.tgz", + "integrity": "sha512-cNKUQ81eptfZN8MlSqwUq3+5ln8u/PcY57UmLZ+npxUHanqO1akpgcpNsLpmsIkoXGbtSQrLuDUgH86lS/SWOw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/property-provider": "^4.0.0", "@smithy/shared-ini-file-loader": "^4.0.0", @@ -679,15 +567,15 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.726.0.tgz", - "integrity": "sha512-WxkN76WeB08j2yw7jUH9yCMPxmT9eBFd9ZA/aACG7yzOIlsz7gvG3P2FQ0tVg25GHM0E4PdU3p/ByTOawzcOAg==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.730.0.tgz", + "integrity": "sha512-SdI2xrTbquJLMxUh5LpSwB8zfiKq3/jso53xWRgrVfeDlrSzZuyV6QghaMs3KEEjcNzwEnTfSIjGQyRXG9VrEw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.726.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/token-providers": "3.723.0", + "@aws-sdk/client-sso": "3.730.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/token-providers": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/property-provider": "^4.0.0", "@smithy/shared-ini-file-loader": "^4.0.0", @@ -699,13 +587,14 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.723.0.tgz", - "integrity": "sha512-tl7pojbFbr3qLcOE6xWaNCf1zEfZrIdSJtOPeSXfV/thFMMAvIjgf3YN6Zo1a6cxGee8zrV/C8PgOH33n+Ev/A==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.730.0.tgz", + "integrity": "sha512-l5vdPmvF/d890pbvv5g1GZrdjaSQkyPH/Bc8dO/ZqkWxkIP8JNgl48S2zgf4DkP3ik9K2axWO828L5RsMDQzdA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/nested-clients": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/property-provider": "^4.0.0", "@smithy/types": "^4.0.0", @@ -713,9 +602,6 @@ }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.723.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { @@ -754,16 +640,16 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.723.0.tgz", - "integrity": "sha512-JY76mrUCLa0FHeMZp8X9+KK6uEuZaRZaQrlgq6zkXX/3udukH0T3YdFC+Y9uw5ddbiwZ5+KwgmlhnPpiXKfP4g==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.730.0.tgz", + "integrity": "sha512-D0YfQWJ32xo3DvFKcTuM5Aq0IxVr8N++ChVeK3ENYcdelUpxWA9xplZH3QTSbbM1iH3qs2maeAAnLyAns26aGg==", "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.723.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.0.0", @@ -841,13 +727,13 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.723.0.tgz", - "integrity": "sha512-wfjOvNJVp8LDWhq4wO5jtSMb8Vgf4tNlR7QTEQfoYc6AGU3WlK5xyUQcpfcpwytEhQTN9u0cJLQpSyXDO+qSCw==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.730.0.tgz", + "integrity": "sha512-5EJWF8cV5SWqGQfQWDd1sklawm5grUa6H/QHGAhmCdcw+isz675qg/+c970I5rBsSEky83ialjBQIirrBo7DYQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/types": "3.723.0", "@aws-sdk/util-arn-parser": "3.723.0", "@smithy/core": "^3.0.0", @@ -882,15 +768,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.726.0.tgz", - "integrity": "sha512-hZvzuE5S0JmFie1r68K2wQvJbzyxJFdzltj9skgnnwdvLe8F/tz7MqLkm28uV0m4jeHk0LpiBo6eZaPkQiwsZQ==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.730.0.tgz", + "integrity": "sha512-aPMZvNmf2a42B41au3bA3ODU4HfHka2nYT/SAIhhVXH1ENYfAmZo7FraFPxetKepFMCtL7j4QE6/LDucK6liIw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", + "@aws-sdk/core": "3.730.0", "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-endpoints": "3.730.0", "@smithy/core": "^3.0.0", "@smithy/protocol-http": "^5.0.0", "@smithy/types": "^4.0.0", @@ -900,6 +786,56 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.730.0.tgz", + "integrity": "sha512-vilIgf1/7kre8DdE5zAQkDOwHFb/TahMn/6j2RZwFLlK7cDk91r19deSiVYnKQkupDMtOfNceNqnorM4I3PDzw==", + "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.730.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.730.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.730.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.730.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@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.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.723.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.723.0.tgz", @@ -919,13 +855,13 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.723.0.tgz", - "integrity": "sha512-lJlVAa5Sl589qO8lwMLVUtnlF1Q7I+6k1Iomv2goY9d1bRl4q2N5Pit2qJVr2AMW0sceQXeh23i2a/CKOqVAdg==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.730.0.tgz", + "integrity": "sha512-/a6ndc+oNnFwolBdB4QgAK7iO4cge0vm9hEp7ZeFQCviX64UmsWH/UdIhsi7wvKEXdIo1XR9v3O4o6UwcQngXg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.723.0", + "@aws-sdk/middleware-sdk-s3": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/protocol-http": "^5.0.0", "@smithy/signature-v4": "^5.0.0", @@ -937,12 +873,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.723.0.tgz", - "integrity": "sha512-hniWi1x4JHVwKElANh9afKIMUhAutHVBRD8zo6usr0PAoj+Waf220+1ULS74GXtLXAPCiNXl5Og+PHA7xT8ElQ==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.730.0.tgz", + "integrity": "sha512-BSPssGj54B/AABWXARIPOT/1ybFahM1ldlfmXy9gRmZi/afe9geWJGlFYCCt3PmqR+1Ny5XIjSfue+kMd//drQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@aws-sdk/nested-clients": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/property-provider": "^4.0.0", "@smithy/shared-ini-file-loader": "^4.0.0", @@ -951,9 +888,6 @@ }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.723.0" } }, "node_modules/@aws-sdk/types": { @@ -984,9 +918,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.726.0.tgz", - "integrity": "sha512-sLd30ASsPMoPn3XBK50oe/bkpJ4N8Bpb7SbhoxcY3Lk+fSASaWxbbXE81nbvCnkxrZCvkPOiDHzJCp1E2im71A==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.730.0.tgz", + "integrity": "sha512-1KTFuVnk+YtLgWr6TwDiggcDqtPpOY2Cszt3r2lkXfaEAX6kHyOZi1vdvxXjPU5LsOBJem8HZ7KlkmrEi+xowg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1026,13 +960,13 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.726.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.726.0.tgz", - "integrity": "sha512-iEj6KX9o6IQf23oziorveRqyzyclWai95oZHDJtYav3fvLJKStwSjygO4xSF7ycHcTYeCHSLO1FFOHgGVs4Viw==", + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.730.0.tgz", + "integrity": "sha512-yBvkOAjqsDEl1va4eHNOhnFBk0iCY/DBFNyhvtTMqPF4NO+MITWpFs3J9JtZKzJlQ6x0Yb9TLQ8NhDjEISz5Ug==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/middleware-user-agent": "3.730.0", "@aws-sdk/types": "3.723.0", "@smithy/node-config-provider": "^4.0.0", "@smithy/types": "^4.0.0", @@ -1115,16 +1049,19 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], @@ -1135,13 +1072,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], @@ -1152,13 +1089,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], @@ -1169,13 +1106,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], @@ -1186,13 +1123,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], @@ -1203,13 +1140,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], @@ -1220,13 +1157,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], @@ -1237,13 +1174,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], @@ -1254,13 +1191,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], @@ -1271,13 +1208,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], @@ -1288,13 +1225,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], @@ -1305,13 +1242,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], @@ -1322,13 +1259,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], @@ -1339,13 +1276,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], @@ -1356,13 +1293,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], @@ -1373,13 +1310,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], @@ -1390,13 +1327,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -1407,13 +1344,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "cpu": [ "x64" ], @@ -1424,13 +1378,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], @@ -1441,13 +1412,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], @@ -1458,13 +1429,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], @@ -1475,13 +1446,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], @@ -1492,13 +1463,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], @@ -1509,7 +1480,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -2276,9 +2247,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.0.tgz", - "integrity": "sha512-swFv0wQiK7TGHeuAp6lfF5Kw1dHWsTrCuc+yh4Kh05gEShjsE2RUxHucEerR9ih9JITNtaHcSpUThn5Y/vDw0A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.1.tgz", + "integrity": "sha512-hhUZlBWYuh9t6ycAcN90XOyG76C1AzwxZZgaCVPMYpWqqk9uMFo7HGG5Zu2cEhCJn7DdOi5krBmlibWWWPgdsw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2287,7 +2258,7 @@ "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.0.1", + "@smithy/util-stream": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -2509,13 +2480,13 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.1.tgz", - "integrity": "sha512-hCCOPu9+sRI7Wj0rZKKnGylKXBEd9cQJetzjQqe8cT4PWvtQAbvNVa6cgAONiZg9m8LaXtP9/waxm3C3eO4hiw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.2.tgz", + "integrity": "sha512-Z9m67CXizGpj8CF/AW/7uHqYNh1VXXOn9Ap54fenWsCa0HnT4cJuE61zqG3cBkTZJDCy0wHJphilI41co/PE5g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.1.0", + "@smithy/core": "^3.1.1", "@smithy/middleware-serde": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", @@ -2529,16 +2500,16 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.1.tgz", - "integrity": "sha512-n3g2zZFgOWaz2ZYCy8+4wxSmq+HSTD8QKkRhFDv+nkxY1o7gzyp4PDz/+tOdcNPMPZ/A6Mt4aVECYNjQNiaHJw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.3.tgz", + "integrity": "sha512-TiKwwQTwUDeDtwWW8UWURTqu7s6F3wN2pmziLU215u7bqpVT9Mk2oEvURjpRLA+5XeQhM68R5BpAGzVtomsqgA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.0", + "@smithy/smithy-client": "^4.1.2", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", @@ -2594,9 +2565,9 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.1.tgz", - "integrity": "sha512-ddQc7tvXiVLC5c3QKraGWde761KSk+mboCheZoWtuqnXh5l0WKyFy3NfDIM/dsKrI9HlLVH/21pi9wWK2gUFFA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.2.tgz", + "integrity": "sha512-X66H9aah9hisLLSnGuzRYba6vckuFtGE+a5DcHLliI/YlqKrGoxhisD5XbX44KyoeRzoNlGr94eTsMVHFAzPOw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2715,18 +2686,18 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.0.tgz", - "integrity": "sha512-NiboZnrsrZY+Cy5hQNbYi+nVNssXVi2I+yL4CIKNIanOhH8kpC5PKQ2jx/MQpwVr21a3XcVoQBArlpRF36OeEQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.2.tgz", + "integrity": "sha512-0yApeHWBqocelHGK22UivZyShNxFbDNrgREBllGh5Ws0D0rg/yId/CJfeoKKpjbfY2ju8j6WgDUGZHYQmINZ5w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.1.0", - "@smithy/middleware-endpoint": "^4.0.1", + "@smithy/core": "^3.1.1", + "@smithy/middleware-endpoint": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.0.1", + "@smithy/util-stream": "^4.0.2", "tslib": "^2.6.2" }, "engines": { @@ -2829,14 +2800,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.1.tgz", - "integrity": "sha512-nkQifWzWUHw/D0aLPgyKut+QnJ5X+5E8wBvGfvrYLLZ86xPfVO6MoqfQo/9s4bF3Xscefua1M6KLZtobHMWrBg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.3.tgz", + "integrity": "sha512-7c5SF1fVK0EOs+2EOf72/qF199zwJflU1d02AevwKbAUPUZyE9RUZiyJxeUmhVxfKDWdUKaaVojNiaDQgnHL9g==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.0", + "@smithy/smithy-client": "^4.1.2", "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -2846,9 +2817,9 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.1.tgz", - "integrity": "sha512-LeAx2faB83litC9vaOdwFaldtto2gczUHxfFf8yoRwDU3cwL4/pDm7i0hxsuBCRk5mzHsrVGw+3EVCj32UZMdw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.3.tgz", + "integrity": "sha512-CVnD42qYD3JKgDlImZ9+On+MqJHzq9uJgPbMdeBE8c2x8VJ2kf2R3XO/yVFx+30ts5lD/GlL0eFIShY3x9ROgQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2856,7 +2827,7 @@ "@smithy/credential-provider-imds": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.0", + "@smithy/smithy-client": "^4.1.2", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, @@ -2922,14 +2893,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.0.1.tgz", - "integrity": "sha512-Js16gOgU6Qht6qTPfuJgb+1YD4AEO+5Y1UPGWKSp3BNo8ONl/qhXSYDhFKJtwybRJynlCqvP5IeiaBsUmkSPTQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.0.2.tgz", + "integrity": "sha512-0eZ4G5fRzIoewtHtwaYyl8g2C+osYOT4KClXgfdNEDAgkbe2TYPqcnw4GAWabqkZCax2ihRGPe9LZnsPdIUIHA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", @@ -2984,9 +2955,9 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.12.1.tgz", - "integrity": "sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.13.0.tgz", + "integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3039,9 +3010,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", - "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", "dev": true, "license": "MIT", "dependencies": { @@ -3073,17 +3044,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", - "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.19.1", - "@typescript-eslint/type-utils": "8.19.1", - "@typescript-eslint/utils": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3103,16 +3074,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", - "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.19.1", - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/typescript-estree": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -3128,14 +3099,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", - "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3146,14 +3117,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", - "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.19.1", - "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.0" }, @@ -3170,9 +3141,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", - "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -3184,14 +3155,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", - "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3211,16 +3182,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", - "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.19.1", - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/typescript-estree": "8.19.1" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3235,13 +3206,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", - "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/types": "8.20.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3253,31 +3224,31 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", - "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.2.tgz", + "integrity": "sha512-U+hZYb0FtgNDb6B3E9piAHzXXIuxuBw2cd6Lvepc9sYYY4KjgiwCBmo3Sird9ZRu3ggLpLBTfw1ZRr77ipiSfw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", + "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.8", - "vitest": "2.1.8" + "@vitest/browser": "3.0.2", + "vitest": "3.0.2" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3286,9 +3257,9 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.1.24", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.24.tgz", - "integrity": "sha512-7IaENe4NNy33g0iuuy5bHY69JYYRjpv4lMx6H5Wp30W7ez2baLHwxsXF5TM4wa8JDYZt8ut99Ytoj7GiDO01hw==", + "version": "1.1.25", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.25.tgz", + "integrity": "sha512-u8DpDnMbPcqBmJOB4PeEtn6q7vKmLVTLFMpzoxSAo0hjYdl4iYSHRleqwPQo0ywc7UV0S6RKIahYRQ3BnZdMVw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3307,37 +3278,37 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", - "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.2.tgz", + "integrity": "sha512-dKSHLBcoZI+3pmP5hiZ7I5grNru2HRtEW8Z5Zp4IXog8QYcxhlox7JUPyIIFWfN53+3HW3KPLIl6nSzUGgKSuQ==", "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.8", - "@vitest/utils": "2.1.8", + "@vitest/spy": "3.0.2", + "@vitest/utils": "3.0.2", "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", - "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.2.tgz", + "integrity": "sha512-Hr09FoBf0jlwwSyzIF4Xw31OntpO3XtZjkccpcBf8FeVW3tpiyKlkeUzxS/txzHqpUCNIX157NaTySxedyZLvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.8", + "@vitest/spy": "3.0.2", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "msw": { @@ -3349,50 +3320,50 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", - "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.2.tgz", + "integrity": "sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ==", "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", - "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.2.tgz", + "integrity": "sha512-GHEsWoncrGxWuW8s405fVoDfSLk6RF2LCXp6XhevbtDjdDme1WV/eNmUueDfpY1IX3MJaCRelVCEXsT9cArfEg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.8", - "pathe": "^1.1.2" + "@vitest/utils": "3.0.2", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", - "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.2.tgz", + "integrity": "sha512-h9s67yD4+g+JoYG0zPCo/cLTabpDqzqNdzMawmNPzDStTiwxwkyYM1v5lWE8gmGv3SVJ2DcxA2NpQJZJv9ym3g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.8", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.0.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", - "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.2.tgz", + "integrity": "sha512-8mI2iUn+PJFMT44e3ISA1R+K6ALVs47W6eriDTfXe6lFqlflID05MB4+rIFhmDSLBj8iBsZkzBYlgSkinxLzSQ==", "license": "MIT", "dependencies": { "tinyspy": "^3.0.2" @@ -3402,14 +3373,14 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", - "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-Qu01ZYZlgHvDP02JnMBRpX43nRaZtNpIzw3C1clDXmn8eakgX6iQVGzTQ/NjkIr64WD8ioqOjkaYRVvHQI5qiw==", "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.8", + "@vitest/pretty-format": "3.0.2", "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3719,9 +3690,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3729,32 +3700,34 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/escape-string-regexp": { @@ -4818,9 +4791,9 @@ } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", "dev": true, "license": "MIT" }, @@ -4854,9 +4827,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "dev": true, "funding": [ { @@ -4874,7 +4847,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5283,9 +5256,9 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -5370,15 +5343,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.1.tgz", - "integrity": "sha512-LKPUQpdEMVOeKluHi8md7rwLcoXHhwvWp3x+sJkMuq3gGm9yaYJtPo8sRZSblMFJ5pcOGCAak/scKf1mvZDlQw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.20.0.tgz", + "integrity": "sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.19.1", - "@typescript-eslint/parser": "8.19.1", - "@typescript-eslint/utils": "8.19.1" + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", + "@typescript-eslint/utils": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5424,21 +5397,21 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz", + "integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5447,19 +5420,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -5480,74 +5459,80 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-node": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", - "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.2.tgz", + "integrity": "sha512-hsEQerBAHvVAbv40m3TFQe/lTEbOp7yDpyqMJqr2Tnd+W58+DEYOt+fluQgekOePcsNBmR77lpVAnIU2Xu4SvQ==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.1", + "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/vitest": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", - "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.2.tgz", + "integrity": "sha512-5bzaHakQ0hmVVKLhfh/jXf6oETDBtgPo8tQCHYB+wftNgFJ+Hah67IsWc8ivx4vFL025Ow8UiuTf4W57z4izvQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.8", - "@vitest/mocker": "2.1.8", - "@vitest/pretty-format": "^2.1.8", - "@vitest/runner": "2.1.8", - "@vitest/snapshot": "2.1.8", - "@vitest/spy": "2.1.8", - "@vitest/utils": "2.1.8", + "@vitest/expect": "3.0.2", + "@vitest/mocker": "3.0.2", + "@vitest/pretty-format": "^3.0.2", + "@vitest/runner": "3.0.2", + "@vitest/snapshot": "3.0.2", + "@vitest/spy": "3.0.2", + "@vitest/utils": "3.0.2", "chai": "^5.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.1", "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.8", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.2", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.8", - "@vitest/ui": "2.1.8", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.2", + "@vitest/ui": "3.0.2", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 13c4c71..ebb9136 100644 --- a/package.json +++ b/package.json @@ -38,24 +38,24 @@ "aws-sdk-client-mock": ">=2.2.0" }, "dependencies": { - "@vitest/expect": "^2.1.3", - "tslib": "^2.8.0" + "@vitest/expect": "^3.0.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@aws-sdk/client-s3": "^3.726.1", - "@aws-sdk/client-secrets-manager": "^3.726.1", + "@aws-sdk/client-s3": "^3.730.0", + "@aws-sdk/client-secrets-manager": "^3.730.0", "@eslint/js": "^9.18.0", - "@stylistic/eslint-plugin": "^2.12.1", + "@stylistic/eslint-plugin": "^2.13.0", "@types/eslint__js": "^8.42.3", - "@types/node": "^22.10.5", - "@vitest/coverage-v8": "^2.1.8", - "@vitest/eslint-plugin": "^1.1.24", + "@types/node": "^22.10.7", + "@vitest/coverage-v8": "^3.0.2", + "@vitest/eslint-plugin": "^1.1.25", "aws-sdk-client-mock": "^4.1.0", "eslint": "^9.18.0", "eslint-config-flat-gitignore": "^1.0.0", "eslint-plugin-perfectionist": "^4.6.0", "typescript": "^5.7.3", - "typescript-eslint": "^8.19.1", - "vitest": "^2.1.8" + "typescript-eslint": "^8.20.0", + "vitest": "^3.0.2" } } diff --git a/src/index.ts b/src/index.ts index 27eca9a..571f90d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export type { CustomMatcher } from './matcher.js'; export { toHaveReceivedAnyCommand, toHaveReceivedCommand, + toHaveReceivedCommandExactlyOnceWith, toHaveReceivedCommandOnce, toHaveReceivedCommandTimes, toHaveReceivedCommandWith, @@ -9,6 +10,7 @@ export { toHaveReceivedNthCommandWith, toReceiveAnyCommand, toReceiveCommand, + toReceiveCommandExactlyOnceWith, toReceiveCommandOnce, toReceiveCommandTimes, toReceiveCommandWith, diff --git a/src/matcher.ts b/src/matcher.ts index 404cf03..e92dfc7 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -15,6 +15,7 @@ import { notNull, ordinalOf } from './utils.js'; interface AliasMatcher { toReceiveAnyCommand: BaseMatcher['toHaveReceivedAnyCommand']; toReceiveCommand: BaseMatcher['toHaveReceivedCommand']; + toReceiveCommandExactlyOnceWith: BaseMatcher['toHaveReceivedCommandExactlyOnceWith']; toReceiveCommandOnce: BaseMatcher['toHaveReceivedCommandOnce']; toReceiveCommandTimes: BaseMatcher['toHaveReceivedCommandTimes']; toReceiveCommandWith: BaseMatcher['toHaveReceivedCommandWith']; @@ -39,6 +40,14 @@ interface BaseMatcher { command: AwsCommandConstructur ): R; + toHaveReceivedCommandExactlyOnceWith< + Input extends object, + Output extends MetadataBearer, + >( + command: AwsCommandConstructur, + input: Partial + ): R; + toHaveReceivedCommandOnce< Input extends object, Output extends MetadataBearer, @@ -209,9 +218,34 @@ function toHaveReceivedCommandWith( }; }; const toReceiveCommandWith = toHaveReceivedCommandWith; -/* - */ +function toHaveReceivedCommandExactlyOnceWith( + this: MatcherState, + client: AwsStub, + command: AwsCommandConstructur, + input: Record, +): ExpectationResult { + const { isNot, utils } = this; + const calls = client.commandCalls(command); + + const hasCallWithArgs = calls.some(call => + new ObjectContaining(input).asymmetricMatch(call.args[0].input), + ); + + const pass = calls.length === 1 && hasCallWithArgs; + + return { + message: () => { + const message = isNot + ? `expected "${command.name}" to not be called once with arguments: ${utils.printExpected(input)}` + : `expected "${command.name}" to be called once with arguments: ${utils.printExpected(input)}`; + return formatCalls(this, client, command, input, message); + }, + pass, + }; +}; +const toReceiveCommandExactlyOnceWith = toHaveReceivedCommandExactlyOnceWith; + function toHaveReceivedNthCommandWith( this: MatcherState, client: AwsStub, @@ -289,6 +323,7 @@ export type { CustomMatcher }; export { toHaveReceivedAnyCommand, toHaveReceivedCommand, + toHaveReceivedCommandExactlyOnceWith, toHaveReceivedCommandOnce, toHaveReceivedCommandTimes, toHaveReceivedCommandWith, @@ -296,6 +331,7 @@ export { toHaveReceivedNthCommandWith, toReceiveAnyCommand, toReceiveCommand, + toReceiveCommandExactlyOnceWith, toReceiveCommandOnce, toReceiveCommandTimes, toReceiveCommandWith, diff --git a/tests/index.test.ts b/tests/index.test.ts index 0f8223c..55524cc 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest'; import { toHaveReceivedAnyCommand, toHaveReceivedCommand, + toHaveReceivedCommandExactlyOnceWith, toHaveReceivedCommandOnce, toHaveReceivedCommandTimes, toHaveReceivedCommandWith, @@ -12,6 +13,7 @@ import { toHaveReceivedNthCommandWith, toReceiveAnyCommand, toReceiveCommand, + toReceiveCommandExactlyOnceWith, toReceiveCommandOnce, toReceiveCommandTimes, toReceiveCommandWith, @@ -22,6 +24,7 @@ import { expect.extend({ toHaveReceivedAnyCommand, toHaveReceivedCommand, + toHaveReceivedCommandExactlyOnceWith, toHaveReceivedCommandOnce, toHaveReceivedCommandTimes, toHaveReceivedCommandWith, @@ -29,6 +32,7 @@ expect.extend({ toHaveReceivedNthCommandWith, toReceiveAnyCommand, toReceiveCommand, + toReceiveCommandExactlyOnceWith, toReceiveCommandOnce, toReceiveCommandTimes, toReceiveCommandWith, @@ -52,7 +56,9 @@ describe('aws-sdk-client-mock-vitest', () => { 'toReceiveLastCommandWith', 'toReceiveNthCommandWith', 'toReceiveAnyCommand', - ])('should be able to extend with %s', (matcher) => { + 'toReceiveCommandExactlyOnceWith', + 'toHaveReceivedCommandExactlyOnceWith', + ])('extend matcher to extend with %s', (matcher) => { expect(expect('something')).toHaveProperty(matcher); }); diff --git a/tests/matcher.test.ts b/tests/matcher.test.ts index 14ec7bf..0e328a3 100644 --- a/tests/matcher.test.ts +++ b/tests/matcher.test.ts @@ -6,11 +6,13 @@ import { S3Client, } from '@aws-sdk/client-s3'; import { mockClient } from 'aws-sdk-client-mock'; +import { randomUUID } from 'node:crypto'; import { describe, expect, it } from 'vitest'; import { toHaveReceivedAnyCommand, toHaveReceivedCommand, + toHaveReceivedCommandExactlyOnceWith, toHaveReceivedCommandOnce, toHaveReceivedCommandTimes, toHaveReceivedCommandWith, @@ -18,6 +20,7 @@ import { toHaveReceivedNthCommandWith, toReceiveAnyCommand, toReceiveCommand, + toReceiveCommandExactlyOnceWith, toReceiveCommandOnce, toReceiveCommandTimes, toReceiveCommandWith, @@ -28,6 +31,7 @@ import { expect.extend({ toHaveReceivedAnyCommand, toHaveReceivedCommand, + toHaveReceivedCommandExactlyOnceWith, toHaveReceivedCommandOnce, toHaveReceivedCommandTimes, toHaveReceivedCommandWith, @@ -35,6 +39,7 @@ expect.extend({ toHaveReceivedNthCommandWith, toReceiveAnyCommand, toReceiveCommand, + toReceiveCommandExactlyOnceWith, toReceiveCommandOnce, toReceiveCommandTimes, toReceiveCommandWith, @@ -43,12 +48,12 @@ expect.extend({ }); describe('toReceiveCommandTimes', () => { - it('should pass with 0 received commands', () => { + it('passes with 0 received commands', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).toReceiveCommandTimes(PutObjectCommand, 0); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -60,45 +65,49 @@ describe('toReceiveCommandTimes', () => { expect(s3Mock).toReceiveCommandTimes(GetBucketAclCommand, 3); }); - it.fails('should fail when command is not called as specified', async () => { + it('fails when command is not called as specified', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); await s3.send(new GetBucketAclCommand({ Bucket: 'bar' })); await s3.send(new GetBucketAclCommand({ Bucket: 'baz' })); - expect(s3Mock).toReceiveCommandTimes(GetBucketAclCommand, 1); + expect(() => { + expect(s3Mock).toReceiveCommandTimes(GetBucketAclCommand, 1); + }).toThrow(/expected "GetBucketAclCommand" to be called 1 times, but got 3 times/); }); describe('not', () => { - it('should pass when not called', () => { + it('passes when not called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toReceiveCommandTimes(PutObjectCommand, 1); }); - it('should pass when called different than specified', async () => { + it('passes when called different than specified', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); expect(s3Mock).not.toReceiveCommandTimes(PutObjectCommand, 2); }); - it.fails('should fail when command received as specified', async () => { + it('fails when command received as specified', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).not.toReceiveCommandTimes(GetObjectCommand, 1); + expect(() => { + expect(s3Mock).not.toReceiveCommandTimes(GetObjectCommand, 1); + }).toThrow(/expected "GetObjectCommand" to not be called 1 times/); }); }); }); describe('toHaveReceivedCommandTimes', () => { - it('should pass with 0 received commands', () => { + it('passes with 0 received commands', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).toHaveReceivedCommandTimes(PutObjectCommand, 0); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -110,60 +119,68 @@ describe('toHaveReceivedCommandTimes', () => { expect(s3Mock).toHaveReceivedCommandTimes(GetBucketAclCommand, 3); }); - it.fails('should fail when command is not called as specified', async () => { + it('fails when command is not called as specified', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); await s3.send(new GetBucketAclCommand({ Bucket: 'bar' })); await s3.send(new GetBucketAclCommand({ Bucket: 'baz' })); - expect(s3Mock).toHaveReceivedCommandTimes(GetBucketAclCommand, 1); + expect(() => { + expect(s3Mock).toHaveReceivedCommandTimes(GetBucketAclCommand, 1); + }).toThrow(/expected "GetBucketAclCommand" to be called 1 times, but got 3 times/); }); describe('not', () => { - it('should pass when not called', () => { + it('passes when not called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toHaveReceivedCommandTimes(PutObjectCommand, 1); }); - it('should pass when called different than specified', async () => { + it('passes when called different than specified', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); expect(s3Mock).not.toHaveReceivedCommandTimes(PutObjectCommand, 2); }); - it.fails('should fail when command received as specified', async () => { + it('fails when command received as specified', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).not.toReceiveCommandTimes(GetObjectCommand, 1); + expect(() => { + expect(s3Mock).not.toReceiveCommandTimes(GetObjectCommand, 1); + }).toThrow(/expected "GetObjectCommand" to not be called 1 times/); }); }); }); describe('toReceiveCommandOnce', () => { - it('should pass when called exactly once', async () => { + it('passes when called exactly once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); expect(s3Mock).toReceiveCommandOnce(GetObjectCommand); }); - it.fails('should fail when not called', () => { + it('fails when not called', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toReceiveCommandOnce(GetObjectCommand); + expect(() => { + expect(s3Mock).toReceiveCommandOnce(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called once, but got 0 times/); }); - it.fails('should fail when called more than once', async () => { + it('fails when called more than once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); - expect(s3Mock).toReceiveCommandOnce(GetObjectCommand); + expect(() => { + expect(s3Mock).toReceiveCommandOnce(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called once, but got 2 times/); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -173,26 +190,29 @@ describe('toReceiveCommandOnce', () => { }); describe('not', () => { - it('should pass when not called', () => { + it('passes when not called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toReceiveCommandOnce(GetObjectCommand); }); - it.fails('should fail when called exactly once', async () => { + it('fails when called exactly once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).not.toReceiveCommandOnce(GetObjectCommand); + expect(() => { + expect(s3Mock).not.toReceiveCommandOnce(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to not be called once/); }); - it('should pass when called more than once', async () => { + it('passes when called more than once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); expect(s3Mock).not.toReceiveCommandOnce(GetObjectCommand); }); - it('should ignore other commands', async () => { + + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); @@ -202,27 +222,31 @@ describe('toReceiveCommandOnce', () => { }); describe('toHaveReceivedCommandOnce', () => { - it('should pass when called exactly once', async () => { + it('passes when called exactly once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); expect(s3Mock).toHaveReceivedCommandOnce(GetObjectCommand); }); - it.fails('should fail when not called', () => { + it('fails when not called', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toHaveReceivedCommandOnce(GetObjectCommand); + expect(() => { + expect(s3Mock).toHaveReceivedCommandOnce(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called once, but got 0 times/); }); - it.fails('should fail when called more than once', async () => { + it('fails when called more than once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); - expect(s3Mock).toHaveReceivedCommandOnce(GetObjectCommand); + expect(() => { + expect(s3Mock).toHaveReceivedCommandOnce(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called once, but got 2 times/); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -232,19 +256,21 @@ describe('toHaveReceivedCommandOnce', () => { }); describe('not', () => { - it('should pass when not called', () => { + it('passes when not called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toHaveReceivedCommandOnce(GetObjectCommand); }); - it.fails('should fail when called exactly once', async () => { + it('fails when called exactly once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).not.toHaveReceivedCommandOnce(GetObjectCommand); + expect(() => { + expect(s3Mock).not.toHaveReceivedCommandOnce(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to not be called once/); }); - it('should pass when called more than once', async () => { + it('passes when called more than once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -252,7 +278,7 @@ describe('toHaveReceivedCommandOnce', () => { expect(s3Mock).not.toHaveReceivedCommandOnce(GetObjectCommand); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); @@ -262,19 +288,21 @@ describe('toHaveReceivedCommandOnce', () => { }); describe('toReceiveCommand', () => { - it.fails('should fail when no command received', () => { + it('fails when no command received', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toReceiveCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).toReceiveCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called at least once/); }); - it('should pass when received command once', async () => { + it('passes when received command once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); expect(s3Mock).toReceiveCommand(GetObjectCommand); }); - it('should pass when received command more than once', async () => { + it('passes when received command more than once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -282,34 +310,38 @@ describe('toReceiveCommand', () => { expect(s3Mock).toReceiveCommand(GetObjectCommand); }); - it.fails('should fail when only received other commands', async () => { + it('fails when only received other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); - expect(s3Mock).toReceiveCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).toReceiveCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called at least once/); }); describe('not', () => { - it('should pass when no command received', () => { + it('passes when no command received', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toReceiveCommand(GetObjectCommand); }); - it('should pass when other commands received', async () => { + it('passes when other commands received', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); expect(s3Mock).not.toReceiveCommand(GetObjectCommand); }); - it.fails('should fail when command received once', async () => { + it('fails when command received once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).not.toReceiveCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).not.toReceiveCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to not be called at all, but actually been called 1 times/); }); - it.fails('should fail when command received multiple times', async () => { + it.fails('fails when command received multiple times', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -320,19 +352,21 @@ describe('toReceiveCommand', () => { }); describe('toHaveReceivedCommand', () => { - it.fails('should fail when no command received', () => { + it('fails when no command received', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toHaveReceivedCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).toHaveReceivedCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called at least once/); }); - it('should pass when received command once', async () => { + it('passes when received command once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); expect(s3Mock).toHaveReceivedCommand(GetObjectCommand); }); - it('should pass when received command more than once', async () => { + it('passes when received command more than once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -340,45 +374,51 @@ describe('toHaveReceivedCommand', () => { expect(s3Mock).toHaveReceivedCommand(GetObjectCommand); }); - it.fails('should fail when only received other commands', async () => { + it('fails when only received other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); - expect(s3Mock).toHaveReceivedCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).toHaveReceivedCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to be called at least once/); }); describe('not', () => { - it('should pass when no command received', () => { + it('passes when no command received', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toHaveReceivedCommand(GetObjectCommand); }); - it('should pass when other commands received', async () => { + it('passes when other commands received', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetBucketAclCommand({ Bucket: 'foo' })); expect(s3Mock).not.toHaveReceivedCommand(GetObjectCommand); }); - it.fails('should fail when command received once', async () => { + it('fails when command received once', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).not.toHaveReceivedCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).not.toHaveReceivedCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to not be called at all, but actually been called 1 times/); }); - it.fails('should fail when command received multiple times', async () => { + it('fails when command received multiple times', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); - expect(s3Mock).not.toHaveReceivedCommand(GetObjectCommand); + expect(() => { + expect(s3Mock).not.toHaveReceivedCommand(GetObjectCommand); + }).toThrow(/expected "GetObjectCommand" to not be called at all, but actually been called 2 times/); }); }); }); describe('toReceiveCommandWith', () => { - it('should pass when received with command', async () => { + it('passes when received with command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -388,7 +428,7 @@ describe('toReceiveCommandWith', () => { }); }); - it('should pass when received with partial command', async () => { + it('passes when received with partial command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -397,7 +437,22 @@ describe('toReceiveCommandWith', () => { }); }); - it('should pass when received at least once with command', async () => { + it('passes with a correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).toReceiveCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }); + + it('passes when received at least once with command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'bucket1', Key: 'key1' })); @@ -410,7 +465,7 @@ describe('toReceiveCommandWith', () => { }); }); - it('should pass when received at least once with partial command', async () => { + it('passes when received at least once with partial command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'bucket1', Key: 'key1' })); @@ -422,7 +477,7 @@ describe('toReceiveCommandWith', () => { }); }); - it('should pass when received mutliple times with command', async () => { + it('passes when received mutliple times with command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -433,7 +488,7 @@ describe('toReceiveCommandWith', () => { }); }); - it('should pass when received mutliple times with partial command', async () => { + it('passes when received mutliple times with partial command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test1.txt' })); @@ -443,39 +498,62 @@ describe('toReceiveCommandWith', () => { }); }); - it.fails('should fail when received with wrong command', async () => { + it('fails when received with wrong command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).toReceiveCommandWith(PutObjectCommand, { - Bucket: 'foo', - Key: 'test.txt', - }); + expect(() => { + expect(s3Mock).toReceiveCommandWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }).toThrow(/expected "PutObjectCommand" to be called with arguments/); }); - it.fails('should fail when input does not match', async () => { + it('fails when input does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).toReceiveCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'wrongkey.txt', - }); + expect(() => { + expect(s3Mock).toReceiveCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'wrongkey.txt', + }); + }).toThrow(/expected "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when input misses fields', async () => { + it('fails when input misses fields', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).toReceiveCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'test.txt', - VersionId: '10', - }); + expect(() => { + expect(s3Mock).toReceiveCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + VersionId: '10', + }); + }).toThrow(/expected "GetObjectCommand" to be called with arguments/); + }); + + it('fails on failed asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).toReceiveCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to be called with arguments/); }); describe('not', () => { - it('shoud pass when never called', () => { + it('passes when never called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toReceiveCommandWith(PutObjectCommand, { Bucket: 'foo', @@ -483,7 +561,7 @@ describe('toReceiveCommandWith', () => { }); }); - it('should pass when not called with input', async () => { + it('passes when not called with input', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -493,30 +571,51 @@ describe('toReceiveCommandWith', () => { }); }); - it.fails('should fail when one matching input', async () => { + it('fails when one matching input', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'baz', Key: 'test.txt' })); - expect(s3Mock).not.toReceiveCommandWith(GetObjectCommand, { - Bucket: 'bar', - Key: 'test.txt', - }); + expect(() => { + expect(s3Mock).not.toReceiveCommandWith(GetObjectCommand, { + Bucket: 'bar', + Key: 'test.txt', + }); + }).toThrow(/expected "GetObjectCommand" to not be called with arguments/); }); - it.fails('should fail on partial match', async () => { + it('fails on partial match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'baz', Key: 'test.txt' })); - expect(s3Mock).not.toReceiveCommandWith(GetObjectCommand, { - Bucket: 'bar', - }); + expect(() => { + expect(s3Mock).not.toReceiveCommandWith(GetObjectCommand, { + Bucket: 'bar', + }); + }).toThrow(/expected "GetObjectCommand" to not be called with arguments/); + }); + + it('fails on correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).not.toReceiveCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to not be called with arguments/); }); - it('should pass when called with additional arguments', async () => { + it('passes when called with additional arguments', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -526,11 +625,26 @@ describe('toReceiveCommandWith', () => { VersionId: 'abc', }); }); + + it('passes on incorrect asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).not.toReceiveCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }); }); }); describe('toHaveReceivedCommandWith', () => { - it('should pass when received with command', async () => { + it('passes when received with command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -540,7 +654,7 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it('should pass when received with partial command', async () => { + it('passes when received with partial command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -549,7 +663,22 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it('should pass when received at least once with command', async () => { + it('passes with a correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }); + + it('passes when received at least once with command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'bucket1', Key: 'key1' })); @@ -562,7 +691,7 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it('should pass when received at least once with partial command', async () => { + it('passes when received at least once with partial command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'bucket1', Key: 'key1' })); @@ -574,7 +703,7 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it('should pass when received mutliple times with command', async () => { + it('passes when received mutliple times with command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -585,7 +714,7 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it('should pass when received mutliple times with partial command', async () => { + it('passes when received mutliple times with partial command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test1.txt' })); @@ -595,39 +724,62 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it.fails('should fail when received with wrong command', async () => { + it('fails when received with wrong command', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).toHaveReceivedCommandWith(PutObjectCommand, { - Bucket: 'foo', - Key: 'test.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedCommandWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }).toThrow(/expected "PutObjectCommand" to be called with arguments/); }); - it.fails('should fail when input does not match', async () => { + it('fails when input does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).toHaveReceivedCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'wrongkey.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'wrongkey.txt', + }); + }).toThrow(/expected "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when input misses fields', async () => { + it('fails when input misses fields', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); - expect(s3Mock).toHaveReceivedCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'test.txt', - VersionId: '10', - }); + expect(() => { + expect(s3Mock).toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + VersionId: '10', + }); + }).toThrow(/expected "GetObjectCommand" to be called with arguments/); + }); + + it('fails on failed asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to be called with arguments/); }); describe('not', () => { - it('shoud pass when never called', () => { + it('passes when never called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toHaveReceivedCommandWith(PutObjectCommand, { Bucket: 'foo', @@ -635,7 +787,7 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it('should pass when not called with input', async () => { + it('passes when not called with input', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -645,30 +797,51 @@ describe('toHaveReceivedCommandWith', () => { }); }); - it.fails('should fail when one matching input', async () => { + it('fails when one matching input', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'baz', Key: 'test.txt' })); - expect(s3Mock).not.toHaveReceivedCommandWith(GetObjectCommand, { - Bucket: 'bar', - Key: 'test.txt', - }); + expect(() => { + expect(s3Mock).not.toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'bar', + Key: 'test.txt', + }); + }).toThrow(/expected "GetObjectCommand" to not be called with arguments/); }); - it.fails('should fail on partial match', async () => { + it('fails on partial match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'baz', Key: 'test.txt' })); - expect(s3Mock).not.toHaveReceivedCommandWith(GetObjectCommand, { - Bucket: 'bar', - }); + expect(() => { + expect(s3Mock).not.toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'bar', + }); + }).toThrow(/expected "GetObjectCommand" to not be called with arguments/); + }); + + it('fails on correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).not.toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to not be called with arguments/); }); - it('should pass when called with additional arguments', async () => { + it('passes when called with additional arguments', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); @@ -678,11 +851,26 @@ describe('toHaveReceivedCommandWith', () => { VersionId: 'abc', }); }); + + it('passes on incorrect asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).not.toHaveReceivedCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }); }); }); describe('toReceiveNthCommandWith', () => { - it('should pass when command matches', async () => { + it('passes when command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -694,7 +882,7 @@ describe('toReceiveNthCommandWith', () => { }); }); - it('should pass when command partially matches', async () => { + it('passes when command partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -705,7 +893,7 @@ describe('toReceiveNthCommandWith', () => { }); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -717,30 +905,34 @@ describe('toReceiveNthCommandWith', () => { }); }); - it.fails('should fail when number is incorrect', async () => { + it('fails when number is incorrect', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file3.txt' })); - expect(s3Mock).toReceiveNthCommandWith(GetObjectCommand, 3, { - Bucket: 'foo', - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).toReceiveNthCommandWith(GetObjectCommand, 3, { + Bucket: 'foo', + Key: 'file2.txt', + }); + }).toThrow(/expected 3rd "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when call is beyond actual calls', async () => { + it('fails when call is beyond actual calls', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).toReceiveNthCommandWith(GetObjectCommand, 2, { - Bucket: 'foo', - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).toReceiveNthCommandWith(GetObjectCommand, 2, { + Bucket: 'foo', + Key: 'file2.txt', + }); + }).toThrow(/expected 2nd "GetObjectCommand" to be called with arguments/); }); describe('not', () => { - it('should succeed when command does not match', async () => { + it('succeeds when command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -752,7 +944,7 @@ describe('toReceiveNthCommandWith', () => { }); }); - it('should succeed when command has missing inputs', async () => { + it('succeeds when command has missing inputs', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -765,7 +957,7 @@ describe('toReceiveNthCommandWith', () => { }); }); - it('should succeed when call is beyond actual calls', async () => { + it('succeeds when call is beyond actual calls', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -775,29 +967,33 @@ describe('toReceiveNthCommandWith', () => { }); }); - it.fails('should fail when call matches', async () => { + it('fails when call matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).not.toReceiveNthCommandWith(GetObjectCommand, 1, { - Bucket: 'foo', - Key: 'file1.txt', - }); + expect(() => { + expect(s3Mock).not.toReceiveNthCommandWith(GetObjectCommand, 1, { + Bucket: 'foo', + Key: 'file1.txt', + }); + }).toThrow(/expected 1st "GetObjectCommand" to not be called with arguments/); }); - it.fails('should fail when call partially matches', async () => { + it('fails when call partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).not.toReceiveNthCommandWith(GetObjectCommand, 1, { - Bucket: 'foo', - }); + expect(() => { + expect(s3Mock).not.toReceiveNthCommandWith(GetObjectCommand, 1, { + Bucket: 'foo', + }); + }).toThrow(/expected 1st "GetObjectCommand" to not be called with arguments/); }); }); }); describe('toHaveReceivedNthCommandWith', () => { - it('should pass when command matches', async () => { + it('passes when command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -809,7 +1005,7 @@ describe('toHaveReceivedNthCommandWith', () => { }); }); - it('should pass when command partially matches', async () => { + it('passes when command partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -820,7 +1016,7 @@ describe('toHaveReceivedNthCommandWith', () => { }); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -832,30 +1028,34 @@ describe('toHaveReceivedNthCommandWith', () => { }); }); - it.fails('should fail when number is incorrect', async () => { + it('fails when number is incorrect', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file3.txt' })); - expect(s3Mock).toHaveReceivedNthCommandWith(GetObjectCommand, 3, { - Bucket: 'foo', - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedNthCommandWith(GetObjectCommand, 3, { + Bucket: 'foo', + Key: 'file2.txt', + }); + }).toThrow(/expected 3rd "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when call is beyond actual calls', async () => { + it('fails when call is beyond actual calls', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).toHaveReceivedNthCommandWith(GetObjectCommand, 2, { - Bucket: 'foo', - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedNthCommandWith(GetObjectCommand, 2, { + Bucket: 'foo', + Key: 'file2.txt', + }); + }).toThrow(/expected 2nd "GetObjectCommand" to be called with arguments/); }); describe('not', () => { - it('should succeed when command does not match', async () => { + it('succeeds when command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -867,7 +1067,7 @@ describe('toHaveReceivedNthCommandWith', () => { }); }); - it('should succeed when command has missing inputs', async () => { + it('succeeds when command has missing inputs', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -880,7 +1080,7 @@ describe('toHaveReceivedNthCommandWith', () => { }); }); - it('should succeed when call is beyond actual calls', async () => { + it('succeeds when call is beyond actual calls', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -890,29 +1090,33 @@ describe('toHaveReceivedNthCommandWith', () => { }); }); - it.fails('should fail when call matches', async () => { + it('fails when call matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).not.toHaveReceivedNthCommandWith(GetObjectCommand, 1, { - Bucket: 'foo', - Key: 'file1.txt', - }); + expect(() => { + expect(s3Mock).not.toHaveReceivedNthCommandWith(GetObjectCommand, 1, { + Bucket: 'foo', + Key: 'file1.txt', + }); + }).toThrow(/expected 1st "GetObjectCommand" to not be called with arguments/); }); - it.fails('should fail when call partially matches', async () => { + it('fails when call partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).not.toHaveReceivedNthCommandWith(GetObjectCommand, 1, { - Bucket: 'foo', - }); + expect(() => { + expect(s3Mock).not.toHaveReceivedNthCommandWith(GetObjectCommand, 1, { + Bucket: 'foo', + }); + }).toThrow(/expected 1st "GetObjectCommand" to not be called with arguments/); }); }); }); describe('toReceiveLastCommandWith', () => { - it('should pass when only command matches', async () => { + it('passes when only command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -922,7 +1126,7 @@ describe('toReceiveLastCommandWith', () => { }); }); - it('should pass when last command matches', async () => { + it('passes when last command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -934,7 +1138,7 @@ describe('toReceiveLastCommandWith', () => { }); }); - it('should pass when last command partially matches', async () => { + it('passes when last command partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -945,7 +1149,7 @@ describe('toReceiveLastCommandWith', () => { }); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -957,38 +1161,44 @@ describe('toReceiveLastCommandWith', () => { }); }); - it.fails('should fail when only command does not match', async () => { + it('fails when only command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).toReceiveLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file3.txt', - }); + expect(() => { + expect(s3Mock).toReceiveLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file3.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when last command does not match', async () => { + it('fails when last command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file3.txt' })); - expect(s3Mock).toReceiveLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file1.txt', - }); + expect(() => { + expect(s3Mock).toReceiveLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file1.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when not called at all', () => { + it('fails when not called at all', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toReceiveLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file1.txt', - }); + expect(() => { + expect(s3Mock).toReceiveLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file1.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to be called with arguments/); }); describe('not', () => { - it('should pass when not called', () => { + it('passes when not called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toReceiveLastCommandWith(GetObjectCommand, { Bucket: 'foo', @@ -996,7 +1206,7 @@ describe('toReceiveLastCommandWith', () => { }); }); - it('should pass when last command does not match', async () => { + it('passes when last command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1007,7 +1217,7 @@ describe('toReceiveLastCommandWith', () => { }); }); - it('should pass when input misses fields', async () => { + it('passes when input misses fields', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1019,31 +1229,35 @@ describe('toReceiveLastCommandWith', () => { }); }); - it.fails('should fail when last command matches', async () => { + it('fails when last command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); - expect(s3Mock).not.toReceiveLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).not.toReceiveLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file2.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to not be called with arguments/); }); - it.fails('should fail when last command partially matches', async () => { + it('fails when last command partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); - expect(s3Mock).not.toReceiveLastCommandWith(GetObjectCommand, { - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).not.toReceiveLastCommandWith(GetObjectCommand, { + Key: 'file2.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to not be called with arguments/); }); }); }); describe('toHaveReceivedLastCommandWith', () => { - it('should pass when only command matches', async () => { + it('passes when only command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1053,7 +1267,7 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it('should pass when last command matches', async () => { + it('passes when last command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1065,7 +1279,7 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it('should pass when last command partially matches', async () => { + it('passes when last command partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1076,7 +1290,7 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it('should ignore other commands', async () => { + it('ignores other commands', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1088,38 +1302,44 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it.fails('should fail when only command does not match', async () => { + it('fails when only command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).toHaveReceivedLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file3.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file3.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when last command does not match', async () => { + it('fails when last command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file3.txt' })); - expect(s3Mock).toHaveReceivedLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file1.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file1.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to be called with arguments/); }); - it.fails('should fail when not called at all', () => { + it('fails when not called at all', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toHaveReceivedLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file1.txt', - }); + expect(() => { + expect(s3Mock).toHaveReceivedLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file1.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to be called with arguments/); }); describe('not', () => { - it('should pass when not called', () => { + it('passes when not called', () => { const s3Mock = mockClient(S3Client); expect(s3Mock).not.toHaveReceivedLastCommandWith(GetObjectCommand, { Bucket: 'foo', @@ -1127,7 +1347,7 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it('should pass when last command does not match', async () => { + it('passes when last command does not match', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1138,7 +1358,7 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it('should pass when input misses fields', async () => { + it('passes when input misses fields', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); @@ -1150,33 +1370,39 @@ describe('toHaveReceivedLastCommandWith', () => { }); }); - it.fails('should fail when last command matches', async () => { + it('fails when last command matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); - expect(s3Mock).not.toHaveReceivedLastCommandWith(GetObjectCommand, { - Bucket: 'foo', - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).not.toHaveReceivedLastCommandWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'file2.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to not be called with arguments/); }); - it.fails('should fail when last command partially matches', async () => { + it('fails when last command partially matches', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); - expect(s3Mock).not.toHaveReceivedLastCommandWith(GetObjectCommand, { - Key: 'file2.txt', - }); + expect(() => { + expect(s3Mock).not.toHaveReceivedLastCommandWith(GetObjectCommand, { + Key: 'file2.txt', + }); + }).toThrow(/expected last "GetObjectCommand" to not be called with arguments/); }); }); }); describe('toReceiveAnyCommand', () => { - it.fails('fails when no command received', () => { + it('fails when no command received', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toReceiveAnyCommand(); + expect(() => { + expect(s3Mock).toReceiveAnyCommand(); + }).toThrow(/expected client "S3Client" to have been called, but was not called/); }); it('passes when one command was received', async () => { @@ -1192,28 +1418,34 @@ describe('toReceiveAnyCommand', () => { expect(s3Mock).not.toReceiveAnyCommand(); }); - it.fails('fails when one command was received', async () => { + it('fails when one command was received', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).not.toReceiveAnyCommand(); + expect(() => { + expect(s3Mock).not.toReceiveAnyCommand(); + }).toThrow(/expected client "S3Client" to not receive any calls, but was called/); }); - it.fails('fails when multiple commands were received', async () => { + it('fails when multiple commands were received', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); await s3.send(new GetObjectAclCommand({ Bucket: 'foo', Key: 'file3.txt' })); - expect(s3Mock).not.toReceiveAnyCommand(); + expect(() => { + expect(s3Mock).not.toReceiveAnyCommand(); + }).toThrow(/expected client "S3Client" to not receive any calls, but was called/); }); }); }); describe('toHaveReceivedAnyCommand', () => { - it.fails('fails when no command received', () => { + it('fails when no command received', () => { const s3Mock = mockClient(S3Client); - expect(s3Mock).toHaveReceivedAnyCommand(); + expect(() => { + expect(s3Mock).toHaveReceivedAnyCommand(); + }).toThrow(/expected client "S3Client" to have been called, but was not called/); }); it('passes when one command was received', async () => { @@ -1229,20 +1461,422 @@ describe('toHaveReceivedAnyCommand', () => { expect(s3Mock).not.toHaveReceivedAnyCommand(); }); - it.fails('fails when one command was received', async () => { + it('fails when one command was received', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); - expect(s3Mock).not.toHaveReceivedAnyCommand(); + expect(() => { + expect(s3Mock).not.toHaveReceivedAnyCommand(); + }).toThrow(/expected client "S3Client" to not receive any calls, but was called/); }); - it.fails('fails when multiple commands were received', async () => { + it('fails when multiple commands were received', async () => { const s3Mock = mockClient(S3Client); const s3 = new S3Client({}); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file1.txt' })); await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'file2.txt' })); await s3.send(new GetObjectAclCommand({ Bucket: 'foo', Key: 'file3.txt' })); - expect(s3Mock).not.toHaveReceivedAnyCommand(); + expect(() => { + expect(s3Mock).not.toHaveReceivedAnyCommand(); + }).toThrow(/expected client "S3Client" to not receive any calls, but was called/); + }); + }); +}); + +describe('toHaveReceivedCommandExactlyOnceWith', () => { + it('passes if called exactly once with command', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }); + + it('passes if called exactly once with partial command', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + }); + }); + + it('passes with a correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }); + + it('fails when not called', () => { + const s3Mock = mockClient(S3Client); + + expect(() => { + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bucket2', + Key: 'key2', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails when received with wrong command', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }).toThrow(/expected "PutObjectCommand" to be called once with arguments/); + }); + + it('fails when input does not match', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'wrongkey.txt', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails when input misses fields', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + VersionId: '10', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails on failed asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails when received to often', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test2.txt' })); + expect(() => { + expect(s3Mock).toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + describe('not', () => { + it('passes when never called', () => { + const s3Mock = mockClient(S3Client); + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }); + + it('passes when not called with input', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bar', + Key: 'test.txt', + }); + }); + + it('passes when called multiple times', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); + await s3.send(new GetObjectCommand({ Bucket: 'baz', Key: 'test.txt' })); + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bar', + Key: 'test.txt', + }); + }); + + it('fails on partial match', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bar', + }); + }).toThrow(/expected "GetObjectCommand" to not be called once with arguments/); + }); + + it('fails on correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to not be called once with arguments/); + }); + + it('passes when called with additional arguments', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + VersionId: 'abc', + }); + }); + + it('passes on incorrect asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).not.toHaveReceivedCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }); + }); +}); + +describe('toReceiveCommandExactlyOnceWith', () => { + it('passes if called exactly once with command', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }); + + it('passes if called exactly once with partial command', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + }); + }); + + it('passes with a correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }); + + it('fails when not called', () => { + const s3Mock = mockClient(S3Client); + + expect(() => { + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bucket2', + Key: 'key2', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails when received with wrong command', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).toReceiveCommandExactlyOnceWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }).toThrow(/expected "PutObjectCommand" to be called once with arguments/); + }); + + it('fails when input does not match', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'wrongkey.txt', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails when input misses fields', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + VersionId: '10', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails on failed asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + it('fails when received to often', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test2.txt' })); + expect(() => { + expect(s3Mock).toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }).toThrow(/expected "GetObjectCommand" to be called once with arguments/); + }); + + describe('not', () => { + it('passes when never called', () => { + const s3Mock = mockClient(S3Client); + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + }); + }); + + it('passes when not called with input', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bar', + Key: 'test.txt', + }); + }); + + it('passes when called multiple times', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); + await s3.send(new GetObjectCommand({ Bucket: 'baz', Key: 'test.txt' })); + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bar', + Key: 'test.txt', + }); + }); + + it('fails on partial match', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'bar', Key: 'test.txt' })); + expect(() => { + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'bar', + }); + }).toThrow(/expected "GetObjectCommand" to not be called once with arguments/); + }); + + it('fails on correct asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(() => { + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.txt$/) as string, + }); + }).toThrow(/expected "GetObjectCommand" to not be called once with arguments/); + }); + + it('passes when called with additional arguments', async () => { + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: 'test.txt' })); + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: 'test.txt', + VersionId: 'abc', + }); + }); + + it('passes on incorrect asymmetric match', async () => { + // Assume code that uses a random string for the bucket key with a known extension + const name = randomUUID().toString(); + const s3Mock = mockClient(S3Client); + const s3 = new S3Client({}); + await s3.send(new GetObjectCommand({ Bucket: 'foo', Key: `${name}.txt` })); + + // asymmetric matchers like stringMatching are typed as `any` so we cast them + // as we want to compare them to strings + expect(s3Mock).not.toReceiveCommandExactlyOnceWith(GetObjectCommand, { + Bucket: 'foo', + Key: expect.stringMatching(/.jpg/) as string, + }); }); }); }); diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 14cd895..bcff592 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -3,15 +3,15 @@ import { describe, expect, it } from 'vitest'; import { notNull, ordinalOf } from '../src/utils.js'; describe('notNull', () => { - it('should return true when not null', () => { + it('returns true when not null', () => { expect(notNull('Hi')).toBe(true); }); - it('should return false when null', () => { + it('returns false when null', () => { expect(notNull(null)).toBe(false); }); - it('should return true when undefined', () => { + it('returns true when undefined', () => { expect(notNull(undefined)).toBe(true); }); }); @@ -39,7 +39,7 @@ describe('ordinalOf', () => { [1022, '1022nd'], [1023, '1023rd'], ]; - it.each(cases)('should translate %d to %s', (a, b) => { + it.each(cases)('translates %d to %s', (a, b) => { expect(ordinalOf(a)).toStrictEqual(b); }); });