From 74bc7011de81e126bc36e7c73ef9bd15c4285490 Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 15 Sep 2023 18:34:01 +0000 Subject: [PATCH 1/3] fix(signature-v4-crt): remove dynamic imports (!) --- README.md | 18 ++++++++-- .../test/EventBridge.spec.ts | 2 ++ packages/signature-v4-crt/README.md | 2 ++ packages/signature-v4-crt/package.json | 1 + packages/signature-v4-crt/src/index.ts | 2 ++ packages/signature-v4-multi-region/README.md | 5 +-- .../signature-v4-multi-region/package.json | 6 ---- .../src/SignatureV4MultiRegion.spec.ts | 5 ++- .../src/SignatureV4MultiRegion.ts | 7 ++-- .../src/load-crt.browser.ts | 4 --- .../signature-v4-multi-region/src/load-crt.ts | 34 ------------------- .../src/crt-availability.ts | 9 +++++ packages/util-user-agent-node/src/index.ts | 2 ++ .../src/is-crt-available.ts | 19 +++-------- 14 files changed, 48 insertions(+), 68 deletions(-) delete mode 100644 packages/signature-v4-multi-region/src/load-crt.browser.ts delete mode 100644 packages/signature-v4-multi-region/src/load-crt.ts create mode 100644 packages/util-user-agent-node/src/crt-availability.ts diff --git a/README.md b/README.md index 16d5767fec03..5e98600efeff 100644 --- a/README.md +++ b/README.md @@ -562,8 +562,11 @@ If the required AWS Common Runtime components are not installed you will receive ```console Cannot find module '@aws-sdk/signature-v4-crt' ... -Please check if you have installed "@aws-sdk/signature-v4-crt" package explicitly. -For more information please go to https://github.com/aws/aws-sdk-js-v3#known-issues +Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. +You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] +or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. +For more information please go to +https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt" ``` indicating that the required dependency is missing to use the associated functionality. To install this dependency follow @@ -584,6 +587,17 @@ If you are using Yarn: yarn add @aws-sdk/signature-v4-crt ``` +Additionally, load the signature-v4-crt package by importing it. + +```js +require("@aws-sdk/signature-v4-crt"); +// or ESM +import "@aws-sdk/signature-v4-crt"; +``` + +Only the import statement is needed. The implementation then registers itself with `@aws-sdk/signature-v4-multi-region` +and becomes available for its use. You do not need to use any imported objects directly. + #### Related issues 1. [S3 Multi-Region Access Point(MRAP) is not available unless with additional dependency](https://github.com/aws/aws-sdk-js-v3/issues/2822) diff --git a/clients/client-eventbridge/test/EventBridge.spec.ts b/clients/client-eventbridge/test/EventBridge.spec.ts index cdec9a4abd2d..b2b15fd8cdf6 100644 --- a/clients/client-eventbridge/test/EventBridge.spec.ts +++ b/clients/client-eventbridge/test/EventBridge.spec.ts @@ -1,4 +1,6 @@ /// +import "@aws-sdk/signature-v4-crt"; + import { FinalizeRequestMiddleware } from "@aws-sdk/types"; import chai from "chai"; import chaiAsPromised from "chai-as-promised"; diff --git a/packages/signature-v4-crt/README.md b/packages/signature-v4-crt/README.md index ff03e215f7f1..b0770d8df5a7 100644 --- a/packages/signature-v4-crt/README.md +++ b/packages/signature-v4-crt/README.md @@ -5,3 +5,5 @@ This package contains native modules that only executable in Node.js runtime. Please refer to [this issue](https://github.com/aws/aws-sdk-js-v3/issues/2822) for more information. + +See also https://github.com/aws/aws-sdk-js-v3/tree/main#functionality-requiring-aws-common-runtime-crt. \ No newline at end of file diff --git a/packages/signature-v4-crt/package.json b/packages/signature-v4-crt/package.json index a1170d3dc94f..5729b50c9b7b 100644 --- a/packages/signature-v4-crt/package.json +++ b/packages/signature-v4-crt/package.json @@ -23,6 +23,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/signature-v4-multi-region": "*", + "@aws-sdk/util-user-agent-node": "*", "@smithy/querystring-parser": "^2.0.0", "@smithy/signature-v4": "^2.0.0", "@smithy/types": "^2.4.0", diff --git a/packages/signature-v4-crt/src/index.ts b/packages/signature-v4-crt/src/index.ts index ea2acf4d28c7..eddd5a2c85a0 100644 --- a/packages/signature-v4-crt/src/index.ts +++ b/packages/signature-v4-crt/src/index.ts @@ -1,7 +1,9 @@ import { signatureV4CrtContainer } from "@aws-sdk/signature-v4-multi-region"; +import { crtAvailability } from "@aws-sdk/util-user-agent-node"; import { CrtSignerV4 } from "./CrtSignerV4"; signatureV4CrtContainer.CrtSignerV4 = CrtSignerV4; +crtAvailability.isCrtAvailable = true; export * from "./CrtSignerV4"; diff --git a/packages/signature-v4-multi-region/README.md b/packages/signature-v4-multi-region/README.md index 342a94a881ea..5136b7428707 100644 --- a/packages/signature-v4-multi-region/README.md +++ b/packages/signature-v4-multi-region/README.md @@ -3,12 +3,9 @@ [![NPM version](https://img.shields.io/npm/v/@aws-sdk/signature-v4-multi-region/latest.svg)](https://www.npmjs.com/package/@aws-sdk/signature-v4-multi-region) [![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/signature-v4-multi-region.svg)](https://www.npmjs.com/package/@aws-sdk/signature-v4-multi-region) -> An internal package - +See also https://github.com/aws/aws-sdk-js-v3/tree/main#functionality-requiring-aws-common-runtime-crt. ## Usage -You probably shouldn't, at least directly. - This package contains optional dependency [`@aws-sdk/signature-v4-crt`](https://www.npmjs.com/package/@aws-sdk/signature-v4). You need to install this package explicitly to sign an un-regional request using SigV4a algorithm. The package contains Node.js native implementation which requires building at installation. The installed package MAY NOT work if the diff --git a/packages/signature-v4-multi-region/package.json b/packages/signature-v4-multi-region/package.json index 29f45fd92597..74d5c3ff1cd7 100644 --- a/packages/signature-v4-multi-region/package.json +++ b/packages/signature-v4-multi-region/package.json @@ -14,12 +14,6 @@ "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", - "browser": { - "./dist-es/load-crt": "./dist-es/load-crt.browser" - }, - "react-native": { - "./dist-es/load-crt": "./dist-es/load-crt.browser" - }, "author": { "name": "AWS SDK for JavaScript Team", "url": "https://aws.amazon.com/javascript/" diff --git a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts index 8c7c5104803d..32ca2575d1a1 100644 --- a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts +++ b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts @@ -80,7 +80,10 @@ describe("SignatureV4MultiRegion", () => { expect.assertions(1); const signer = new SignatureV4MultiRegion({ ...params }); await expect(async () => await signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( - `\nPlease check if you have installed "@aws-sdk/signature-v4-crt" package explicitly. \n` + + "\n" + + `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. \n` + + `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + + `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. \n` + "For more information please go to " + "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt" ); diff --git a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts index 2839a85f2994..2ab259be2591 100644 --- a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts +++ b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts @@ -7,7 +7,6 @@ import { RequestSigningArguments, } from "@smithy/types"; -import { loadCrt } from "./load-crt"; import { OptionalCrtSignerV4, signatureV4CrtContainer } from "./signature-v4-crt-container"; /** @@ -59,12 +58,14 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner { let CrtSignerV4: OptionalCrtSignerV4 | null = null; try { - loadCrt(); CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4; if (typeof CrtSignerV4 !== "function") throw new Error(); } catch (e) { e.message = - `${e.message}\nPlease check if you have installed "@aws-sdk/signature-v4-crt" package explicitly. \n` + + `${e.message}\n` + + `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. \n` + + `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + + `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. \n` + "For more information please go to " + "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"; throw e; diff --git a/packages/signature-v4-multi-region/src/load-crt.browser.ts b/packages/signature-v4-multi-region/src/load-crt.browser.ts deleted file mode 100644 index 87b9c0bc44ce..000000000000 --- a/packages/signature-v4-multi-region/src/load-crt.browser.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @internal - */ -export function loadCrt(): void {} diff --git a/packages/signature-v4-multi-region/src/load-crt.ts b/packages/signature-v4-multi-region/src/load-crt.ts deleted file mode 100644 index a96f46fbf7a5..000000000000 --- a/packages/signature-v4-multi-region/src/load-crt.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { signatureV4CrtContainer } from "./signature-v4-crt-container"; - -/** - * @internal - */ -export function loadCrt(): void { - if (signatureV4CrtContainer.CrtSignerV4) { - return; - } - try { - if (typeof require === "function") { - // add some indirection to avoid static analysis. - const __require = require; - const moduleName = "@aws-sdk/signature-v4-crt"; - __require.call(null, moduleName); - - process.emitWarning( - `The package @aws-sdk/signature-v4-crt has been loaded dynamically. -To avoid this warning, please explicitly import the package in your application with: - -import "@aws-sdk/signature-v4-crt"; // ESM -require("@aws-sdk/signature-v4-crt"); // CJS - -In a future version of the AWS SDK for JavaScript (v3), this warning -will become an error and dynamic loading will not be available. - -See https://github.com/aws/aws-sdk-js-v3/issues/5229. -` - ); - } - } catch (e) { - // ignored. - } -} diff --git a/packages/util-user-agent-node/src/crt-availability.ts b/packages/util-user-agent-node/src/crt-availability.ts new file mode 100644 index 000000000000..5fa6d554dced --- /dev/null +++ b/packages/util-user-agent-node/src/crt-availability.ts @@ -0,0 +1,9 @@ +/** + * @internal + * + * If \@aws-sdk/signature-v4-crt is installed and loaded, it will register + * this value to true. + */ +export const crtAvailability = { + isCrtAvailable: false, +}; diff --git a/packages/util-user-agent-node/src/index.ts b/packages/util-user-agent-node/src/index.ts index c731769d2848..f8c09d51104f 100644 --- a/packages/util-user-agent-node/src/index.ts +++ b/packages/util-user-agent-node/src/index.ts @@ -5,6 +5,8 @@ import { env, versions } from "process"; import { isCrtAvailable } from "./is-crt-available"; +export { crtAvailability } from "./crt-availability"; + /** * @internal */ diff --git a/packages/util-user-agent-node/src/is-crt-available.ts b/packages/util-user-agent-node/src/is-crt-available.ts index 8714ab658a26..a2d30eb0da44 100644 --- a/packages/util-user-agent-node/src/is-crt-available.ts +++ b/packages/util-user-agent-node/src/is-crt-available.ts @@ -1,22 +1,13 @@ import { UserAgentPair } from "@smithy/types"; +import { crtAvailability } from "./crt-availability"; + /** * @internal */ export const isCrtAvailable = (): UserAgentPair | null => { - try { - // Attempt to load ambient package aws-crt to verify if it exists. - // We cannot use dynamic import(https://github.com/tc39/proposal-dynamic-import) here because bundlers - // (WebPack, Rollup) will try to bundle this optional dependency and fail to build if not exist. - // Thus this user agent key will only available in Node.js runtime. - if (typeof require === "function" && typeof module !== "undefined" && require("aws-crt")) { - // Validate `module` to make sure this is not in a `require.js` scope. - // TODO: load package version. - return ["md/crt-avail"]; - } - return null; - } catch (e) { - // No aws-crt package available in the runtime. - return null; + if (crtAvailability.isCrtAvailable) { + return ["md/crt-avail"]; } + return null; }; From 8764372124a14f359942c96397a94e8b2358d087 Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 26 Oct 2023 15:05:32 +0000 Subject: [PATCH 2/3] test: split s3 ispec into node and browser files --- clients/client-s3/jest.config.e2e.js | 5 + clients/client-s3/package.json | 4 +- .../e2e/{S3.ispec.ts => S3.browser.ispec.ts} | 147 ++++------ clients/client-s3/test/e2e/S3.e2e.spec.ts | 265 ++++++++++++++++++ 4 files changed, 320 insertions(+), 101 deletions(-) create mode 100644 clients/client-s3/jest.config.e2e.js rename clients/client-s3/test/e2e/{S3.ispec.ts => S3.browser.ispec.ts} (68%) create mode 100644 clients/client-s3/test/e2e/S3.e2e.spec.ts diff --git a/clients/client-s3/jest.config.e2e.js b/clients/client-s3/jest.config.e2e.js new file mode 100644 index 000000000000..c3aa6055ef75 --- /dev/null +++ b/clients/client-s3/jest.config.e2e.js @@ -0,0 +1,5 @@ +module.exports = { + preset: "ts-jest", + testMatch: ["**/*.e2e.spec.ts"], + bail: true, +}; diff --git a/clients/client-s3/package.json b/clients/client-s3/package.json index 6c74474878a2..064e46390a73 100644 --- a/clients/client-s3/package.json +++ b/clients/client-s3/package.json @@ -14,7 +14,9 @@ "extract:docs": "api-extractor run --local", "generate:client": "node ../../scripts/generate-clients/single-service --solo s3", "test": "yarn test:unit", - "test:e2e": "ts-mocha test/**/*.ispec.ts && karma start karma.conf.js", + "test:e2e": "yarn test:e2e:node && yarn test:e2e:browser", + "test:e2e:browser": "ts-mocha test/**/*.browser.ispec.ts && karma start karma.conf.js", + "test:e2e:node": "jest --c jest.config.e2e.js", "test:unit": "ts-mocha test/**/*.spec.ts" }, "main": "./dist-cjs/index.js", diff --git a/clients/client-s3/test/e2e/S3.ispec.ts b/clients/client-s3/test/e2e/S3.browser.ispec.ts similarity index 68% rename from clients/client-s3/test/e2e/S3.ispec.ts rename to clients/client-s3/test/e2e/S3.browser.ispec.ts index ae7f37c27ae3..9cfd2f4d047a 100644 --- a/clients/client-s3/test/e2e/S3.ispec.ts +++ b/clients/client-s3/test/e2e/S3.browser.ispec.ts @@ -11,8 +11,7 @@ import { S3, SelectObjectContentEventStream } from "../../src/index"; import { createBuffer } from "./helpers"; chai.use(chaiAsPromised); const { expect } = chai; -// There will be default values of defaultRegion, credentials, and isBrowser variable in browser tests. -// Define the values for Node.js tests + const region: string | undefined = (globalThis as any).defaultRegion || process?.env?.AWS_SMOKE_TEST_REGION; const credentials: Credentials | undefined = (globalThis as any).credentials || undefined; const isBrowser: boolean | undefined = (globalThis as any).isBrowser || false; @@ -21,7 +20,7 @@ const mrapArn = (globalThis as any)?.window?.__env__?.AWS_SMOKE_TEST_MRAP_ARN || let Key = `${Date.now()}`; -describe("@aws-sdk/client-s3", () => { +(isBrowser ? describe : xdescribe)("@aws-sdk/client-s3", () => { const client = new S3({ region: region, credentials, @@ -34,81 +33,51 @@ describe("@aws-sdk/client-s3", () => { after(async () => { await client.deleteObject({ Bucket, Key }); }); - if (isBrowser) { - const buf = createBuffer("1KB"); - it("should succeed with blob body", async () => { - const result = await client.putObject({ - Bucket, - Key, - Body: new Blob([buf]), - }); - expect(result.$metadata.httpStatusCode).to.equal(200); + const buf = createBuffer("1KB"); + it("should succeed with blob body", async () => { + const result = await client.putObject({ + Bucket, + Key, + Body: new Blob([buf]), }); + expect(result.$metadata.httpStatusCode).to.equal(200); + }); - it("should succeed with TypedArray body", async () => { - const result = await client.putObject({ - Bucket, - Key, - Body: buf, - }); - expect(result.$metadata.httpStatusCode).to.equal(200); + it("should succeed with TypedArray body", async () => { + const result = await client.putObject({ + Bucket, + Key, + Body: buf, }); + expect(result.$metadata.httpStatusCode).to.equal(200); + }); - // todo: fix needed - // todo: TypeError: Failed to construct 'Request': The `duplex` member must - // todo: be specified for a request with a streaming body - it.skip("should succeed with ReadableStream body", async () => { - const length = 10 * 1000; // 10KB - const chunkSize = 10; - const readableStream = new ReadableStream({ - start(controller) { - let sizeLeft = length; - while (sizeLeft > 0) { - let chunk = ""; - for (let i = 0; i < Math.min(sizeLeft, chunkSize); i++) { - chunk += "x"; - } - controller.enqueue(chunk); - sizeLeft -= chunk.length; - } - }, - }); - const result = await client.putObject({ - Bucket, - Key, - Body: readableStream, - }); - expect(result.$metadata.httpStatusCode).to.equal(200); - }); - } else { - it("should succeed with Node.js readable stream body", async () => { - const length = 10 * 1000; // 10KB - const chunkSize = 10; - const { Readable } = require("stream"); - let sizeLeft = length; - const inputStream = new Readable({ - read() { - if (sizeLeft <= 0) { - this.push(null); //end stream; - return; - } + // todo: fix needed + // todo: TypeError: Failed to construct 'Request': The `duplex` member must + // todo: be specified for a request with a streaming body + it.skip("should succeed with ReadableStream body", async () => { + const length = 10 * 1000; // 10KB + const chunkSize = 10; + const readableStream = new ReadableStream({ + start(controller) { + let sizeLeft = length; + while (sizeLeft > 0) { let chunk = ""; for (let i = 0; i < Math.min(sizeLeft, chunkSize); i++) { chunk += "x"; } - this.push(chunk); + controller.enqueue(chunk); sizeLeft -= chunk.length; - }, - }); - inputStream.size = length; // This is required - const result = await client.putObject({ - Bucket, - Key, - Body: inputStream, - }); - expect(result.$metadata.httpStatusCode).to.equal(200); + } + }, + }); + const result = await client.putObject({ + Bucket, + Key, + Body: readableStream, }); - } + expect(result.$metadata.httpStatusCode).to.equal(200); + }); }); describe("GetObject", function () { @@ -141,12 +110,7 @@ describe("@aws-sdk/client-s3", () => { } expect(result.$metadata.httpStatusCode).to.equal(200); - if (isBrowser) { - expect(result.Body).to.be.instanceOf(ReadableStream); - } else { - const { Readable } = require("stream"); - expect(result.Body).to.be.instanceOf(Readable); - } + expect(result.Body).to.be.instanceOf(ReadableStream); }); }); @@ -310,34 +274,17 @@ esfuture,29`; describe("Multi-region access point", () => { before(async () => { Key = `${Date.now()}`; - if (!isBrowser) { - await client.putObject({ Bucket: mrapArn, Key, Body: "foo" }); - } }); - after(async () => { - if (!isBrowser) { - await client.deleteObject({ Bucket: mrapArn, Key }); - } - }); - if (isBrowser) { - it("should throw for aws-crt no available in browser", async () => { - try { - await client.listObjects({ - Bucket: mrapArn, - }); - expect.fail("MRAP call in browser should throw"); - } catch (e) { - expect(e.message).include("only available in Node.js"); - } - }); - } else { - it("should succeed with valid MRAP ARN", async () => { - const result = await client.listObjects({ + after(async () => {}); + it("should throw for aws-crt no available in browser", async () => { + try { + await client.listObjects({ Bucket: mrapArn, }); - expect(result.$metadata.httpStatusCode).to.equal(200); - expect(result.Contents).to.be.instanceOf(Array); - }); - } + expect.fail("MRAP call in browser should throw"); + } catch (e) { + expect(e.message).include("only available in Node.js"); + } + }); }); }); diff --git a/clients/client-s3/test/e2e/S3.e2e.spec.ts b/clients/client-s3/test/e2e/S3.e2e.spec.ts new file mode 100644 index 000000000000..6d1e01eed08e --- /dev/null +++ b/clients/client-s3/test/e2e/S3.e2e.spec.ts @@ -0,0 +1,265 @@ +import "@aws-sdk/signature-v4-crt"; + +import { Credentials } from "@aws-sdk/types"; + +import { S3, SelectObjectContentEventStream } from "../../src/index"; +import { createBuffer } from "./helpers"; + +const region: string | undefined = (globalThis as any).defaultRegion || process?.env?.AWS_SMOKE_TEST_REGION; +const credentials: Credentials | undefined = (globalThis as any).credentials || undefined; +const Bucket = (globalThis as any)?.window?.__env__?.AWS_SMOKE_TEST_BUCKET || process?.env?.AWS_SMOKE_TEST_BUCKET; +const mrapArn = (globalThis as any)?.window?.__env__?.AWS_SMOKE_TEST_MRAP_ARN || process?.env?.AWS_SMOKE_TEST_MRAP_ARN; + +let Key = `${Date.now()}`; + +describe("@aws-sdk/client-s3", () => { + const client = new S3({ + region: region, + credentials, + }); + + describe("PutObject", () => { + beforeAll(() => { + Key = `${Date.now()}`; + }); + afterAll(async () => { + await client.deleteObject({ Bucket, Key }); + }); + it("should succeed with Node.js readable stream body", async () => { + const length = 10 * 1000; // 10KB + const chunkSize = 10; + const { Readable } = require("stream"); + let sizeLeft = length; + const inputStream = new Readable({ + read() { + if (sizeLeft <= 0) { + this.push(null); //end stream; + return; + } + let chunk = ""; + for (let i = 0; i < Math.min(sizeLeft, chunkSize); i++) { + chunk += "x"; + } + this.push(chunk); + sizeLeft -= chunk.length; + }, + }); + inputStream.size = length; // This is required + const result = await client.putObject({ + Bucket, + Key, + Body: inputStream, + }); + expect(result.$metadata.httpStatusCode).toEqual(200); + }); + }); + + describe("GetObject", function () { + jest.setTimeout(10 * 1000); + beforeAll(async () => { + Key = `${Date.now()}`; + }); + + afterAll(async () => { + await client.deleteObject({ Bucket, Key }); + }); + + it("should succeed with valid body payload", async () => { + // prepare the object. + const body = createBuffer("1MB"); + + try { + await client.putObject({ Bucket, Key, Body: body }); + } catch (e) { + console.error("failed to put"); + throw e; + } + + try { + // eslint-disable-next-line no-var + var result = await client.getObject({ Bucket, Key }); + } catch (e) { + console.error("failed to get"); + throw e; + } + + expect(result.$metadata.httpStatusCode).toEqual(200); + const { Readable } = require("stream"); + expect(result.Body).toBeInstanceOf(Readable); + }); + }); + + describe("ListObjects", () => { + beforeAll(async () => { + Key = `${Date.now()}`; + await client.putObject({ Bucket, Key, Body: "foo" }); + }); + afterAll(async () => { + await client.deleteObject({ Bucket, Key }); + }); + it("should succeed with valid bucket", async () => { + const result = await client.listObjects({ + Bucket, + }); + expect(result.$metadata.httpStatusCode).toEqual(200); + expect(result.Contents).toBeInstanceOf(Array); + }); + + it("should throw with invalid bucket", () => + expect(client.listObjects({ Bucket: "invalid-bucket" })).rejects.toThrow()); + }); + + describe("MultipartUpload", () => { + let UploadId: string; + let Etag: string; + const multipartObjectKey = `${Key}-multipart`; + beforeAll(() => { + Key = `${Date.now()}`; + }); + afterEach(async () => { + if (UploadId) { + await client.abortMultipartUpload({ + Bucket, + Key: multipartObjectKey, + UploadId, + }); + } + await client.deleteObject({ + Bucket, + Key: multipartObjectKey, + }); + }); + + it("should successfully create, upload list and complete", async () => { + //create multipart upload + const createResult = await client.createMultipartUpload({ + Bucket, + Key: multipartObjectKey, + }); + expect(createResult.$metadata.httpStatusCode).toEqual(200); + expect(typeof createResult.UploadId).toEqual("string"); + UploadId = createResult.UploadId as string; + + //upload part + const uploadResult = await client.uploadPart({ + Bucket, + Key: multipartObjectKey, + UploadId, + PartNumber: 1, + Body: createBuffer("1KB"), + }); + expect(uploadResult.$metadata.httpStatusCode).toEqual(200); + expect(typeof uploadResult.ETag).toEqual("string"); + Etag = uploadResult.ETag as string; + + //list parts + const listPartsResult = await client.listParts({ + Bucket, + Key: multipartObjectKey, + UploadId, + }); + expect(listPartsResult.$metadata.httpStatusCode).toEqual(200); + expect(listPartsResult.Parts?.length).toEqual(1); + expect(listPartsResult.Parts?.[0].ETag).toEqual(Etag); + + //complete multipart upload + const completeResult = await client.completeMultipartUpload({ + Bucket, + Key: multipartObjectKey, + UploadId, + MultipartUpload: { Parts: [{ ETag: Etag, PartNumber: 1 }] }, + }); + expect(completeResult.$metadata.httpStatusCode).toEqual(200); + + //validate the object is uploaded + const headResult = await client.headObject({ + Bucket, + Key: multipartObjectKey, + }); + expect(headResult.$metadata.httpStatusCode).toEqual(200); + }); + + it("should successfully create, abort, and list upload", async () => { + //create multipart upload + const createResult = await client.createMultipartUpload({ + Bucket, + Key: multipartObjectKey, + }); + expect(createResult.$metadata.httpStatusCode).toEqual(200); + const toAbort = createResult.UploadId; + expect(typeof toAbort).toEqual("string"); + + //abort multipart upload + const abortResult = await client.abortMultipartUpload({ + Bucket, + Key: multipartObjectKey, + UploadId: toAbort, + }); + expect(abortResult.$metadata.httpStatusCode).toEqual(204); + + //validate multipart upload is aborted + const listUploadsResult = await client.listMultipartUploads({ + Bucket, + }); + expect(listUploadsResult.$metadata.httpStatusCode).toEqual(200); + expect((listUploadsResult.Uploads || []).map((upload) => upload.UploadId)).not.toContain(toAbort); + }); + }); + + describe("selectObjectContent", () => { + const csvFile = `user_name,age +jsrocks,13 +node4life,22 +esfuture,29`; + beforeAll(async () => { + Key = `${Date.now()}`; + await client.putObject({ Bucket, Key, Body: csvFile }); + }); + afterAll(async () => { + await client.deleteObject({ Bucket, Key }); + }); + it("should succeed", async () => { + const { Payload } = await client.selectObjectContent({ + Bucket, + Key, + ExpressionType: "SQL", + Expression: "SELECT user_name FROM S3Object WHERE cast(age as int) > 20", + InputSerialization: { + CSV: { + FileHeaderInfo: "USE", + RecordDelimiter: "\n", + FieldDelimiter: ",", + }, + }, + OutputSerialization: { + CSV: {}, + }, + }); + const events: SelectObjectContentEventStream[] = []; + for await (const event of Payload!) { + events.push(event); + } + expect(events.length).toEqual(3); + expect(new TextDecoder().decode(events[0].Records?.Payload)).toEqual("node4life\nesfuture\n"); + expect(events[1].Stats?.Details).toBeDefined(); + expect(events[2].End).toBeDefined(); + }); + }); + + describe("Multi-region access point", () => { + beforeAll(async () => { + Key = `${Date.now()}`; + await client.putObject({ Bucket: mrapArn, Key, Body: "foo" }); + }); + afterAll(async () => { + await client.deleteObject({ Bucket: mrapArn, Key }); + }); + it("should succeed with valid MRAP ARN", async () => { + const result = await client.listObjects({ + Bucket: mrapArn, + }); + expect(result.$metadata.httpStatusCode).toEqual(200); + expect(result.Contents).toBeInstanceOf(Array); + }); + }); +}); From 9373d6ef122d0426f41c6fb2d407652d14a42317 Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 26 Oct 2023 15:19:20 +0000 Subject: [PATCH 3/3] test(client-s3): move unit tests to own folder --- clients/client-s3/package.json | 2 +- clients/client-s3/test/{ => unit}/S3.spec.ts | 2 +- clients/client-s3/test/{ => unit}/flexibleChecksums.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename clients/client-s3/test/{ => unit}/S3.spec.ts (99%) rename clients/client-s3/test/{ => unit}/flexibleChecksums.spec.ts (99%) diff --git a/clients/client-s3/package.json b/clients/client-s3/package.json index 064e46390a73..3b30d8ac6436 100644 --- a/clients/client-s3/package.json +++ b/clients/client-s3/package.json @@ -17,7 +17,7 @@ "test:e2e": "yarn test:e2e:node && yarn test:e2e:browser", "test:e2e:browser": "ts-mocha test/**/*.browser.ispec.ts && karma start karma.conf.js", "test:e2e:node": "jest --c jest.config.e2e.js", - "test:unit": "ts-mocha test/**/*.spec.ts" + "test:unit": "ts-mocha test/unit/**/*.spec.ts" }, "main": "./dist-cjs/index.js", "types": "./dist-types/index.d.ts", diff --git a/clients/client-s3/test/S3.spec.ts b/clients/client-s3/test/unit/S3.spec.ts similarity index 99% rename from clients/client-s3/test/S3.spec.ts rename to clients/client-s3/test/unit/S3.spec.ts index 1d57da696144..34bc56d2ecc5 100644 --- a/clients/client-s3/test/S3.spec.ts +++ b/clients/client-s3/test/unit/S3.spec.ts @@ -5,7 +5,7 @@ import chai from "chai"; import chaiAsPromised from "chai-as-promised"; import { PassThrough, Readable } from "stream"; -import { S3 } from "../src/S3"; +import { S3 } from "../../src/S3"; chai.use(chaiAsPromised); const { expect } = chai; diff --git a/clients/client-s3/test/flexibleChecksums.spec.ts b/clients/client-s3/test/unit/flexibleChecksums.spec.ts similarity index 99% rename from clients/client-s3/test/flexibleChecksums.spec.ts rename to clients/client-s3/test/unit/flexibleChecksums.spec.ts index eef84b4c7af2..ec0d7f02545d 100644 --- a/clients/client-s3/test/flexibleChecksums.spec.ts +++ b/clients/client-s3/test/unit/flexibleChecksums.spec.ts @@ -6,7 +6,7 @@ import chai from "chai"; import chaiAsPromised from "chai-as-promised"; import { Readable } from "stream"; -import { S3 } from "../src/S3"; +import { S3 } from "../../src/S3"; chai.use(chaiAsPromised); const { expect } = chai;