Skip to content

Commit 6bec783

Browse files
committed
feat(middleware-bucket-endpoint): refactor middleware implementation and unit tests
1 parent 3aad79f commit 6bec783

File tree

4 files changed

+152
-148
lines changed

4 files changed

+152
-148
lines changed
Lines changed: 136 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import { constructStack } from "@aws-sdk/middleware-stack";
21
import { HttpRequest } from "@aws-sdk/protocol-http";
32

4-
import { bucketEndpointMiddleware, bucketEndpointMiddlewareOptions } from "./bucketEndpointMiddleware";
53
import { resolveBucketEndpointConfig } from "./configurations";
64

5+
const mockBucketHostname = jest.fn();
6+
jest.mock("./bucketHostname", () => ({
7+
bucketHostname: mockBucketHostname,
8+
}));
9+
const mockBucketArn = "an ARN structure";
10+
const mockArnParse = jest.fn().mockReturnValue(mockBucketArn);
11+
const mockArnValidation = jest.fn();
12+
jest.mock("@aws-sdk/util-arn-parser", () => ({
13+
parse: mockArnParse,
14+
validate: mockArnValidation,
15+
}));
16+
17+
import { bucketEndpointMiddleware } from "./bucketEndpointMiddleware";
18+
719
describe("bucketEndpointMiddleware", () => {
820
const input = { Bucket: "bucket" };
921
const requestInput = {
@@ -15,151 +27,141 @@ describe("bucketEndpointMiddleware", () => {
1527
};
1628
const next = jest.fn();
1729
const previouslyResolvedConfig = {
18-
region: () => Promise.resolve("us-foo-1"),
19-
regionInfoProvider: () => Promise.resolve({ hostname: "foo.us-foo-1.amazonaws.com" }),
30+
region: jest.fn().mockResolvedValue("us-foo-1"),
31+
regionInfoProvider: jest
32+
.fn()
33+
.mockResolvedValue({ hostname: "foo.us-foo-2.amazonaws.com", partition: "aws-foo", signingRegion: "us-foo-1" }),
34+
useArnRegion: jest.fn().mockResolvedValue(false),
2035
};
2136

22-
beforeEach(() => {
23-
next.mockClear();
37+
afterEach(() => {
38+
mockArnValidation.mockClear();
39+
mockBucketHostname.mockClear();
2440
});
2541

26-
it("should convert the request provided into one directed to a virtual hosted-style endpoint", async () => {
27-
const request = new HttpRequest(requestInput);
28-
const handler = bucketEndpointMiddleware(resolveBucketEndpointConfig({ ...previouslyResolvedConfig }))(
29-
next,
30-
{} as any
31-
);
32-
await handler({ input, request });
33-
34-
const {
35-
input: forwardedInput,
36-
request: { hostname, path },
37-
} = next.mock.calls[0][0];
38-
39-
expect(forwardedInput).toBe(input);
40-
expect(hostname).toBe("bucket.s3.us-west-2.amazonaws.com");
41-
expect(path).toBe("/");
42-
});
43-
44-
it("should not convert the request provided into one directed to a virtual hosted-style endpoint if so configured", async () => {
45-
const request = new HttpRequest(requestInput);
46-
const handler = bucketEndpointMiddleware(
47-
resolveBucketEndpointConfig({
48-
...previouslyResolvedConfig,
49-
forcePathStyle: true,
50-
})
51-
)(next, {} as any);
52-
await handler({ input, request });
53-
54-
const {
55-
input: forwardedInput,
56-
request: { hostname, path },
57-
} = next.mock.calls[0][0];
58-
59-
expect(forwardedInput).toBe(input);
60-
expect(hostname).toBe("s3.us-west-2.amazonaws.com");
61-
expect(path).toBe("/bucket");
62-
});
63-
64-
it("should use the bucket name as a virtual hosted-style endpoint if so configured", async () => {
65-
const request = new HttpRequest(requestInput);
66-
const handler = bucketEndpointMiddleware(
67-
resolveBucketEndpointConfig({
68-
...previouslyResolvedConfig,
42+
describe("with regular bucket name", () => {
43+
beforeEach(() => {
44+
mockBucketHostname.mockReturnValue({
6945
bucketEndpoint: true,
70-
})
71-
)(next, {} as any);
72-
await handler({
73-
input: { Bucket: "files.domain.com" },
74-
request: { ...request, path: "/files.domain.com/path/to/key.ext" },
46+
hostname: "bucket.s3.us-west-2.amazonaws.com",
47+
});
7548
});
7649

77-
const {
78-
request: { hostname, path },
79-
} = next.mock.calls[0][0];
80-
81-
expect(hostname).toBe("files.domain.com");
82-
expect(path).toBe("/path/to/key.ext");
83-
});
84-
85-
it("should use a transfer acceleration endpoint if so configured", async () => {
86-
const request = new HttpRequest(requestInput);
87-
const handler = bucketEndpointMiddleware(
88-
resolveBucketEndpointConfig({
89-
...previouslyResolvedConfig,
90-
useAccelerateEndpoint: true,
91-
})
92-
)(next, {} as any);
93-
await handler({ input, request });
94-
95-
const {
96-
input: forwardedInput,
97-
request: { hostname, path },
98-
} = next.mock.calls[0][0];
99-
100-
expect(forwardedInput).toBe(input);
101-
expect(hostname).toBe("bucket.s3-accelerate.amazonaws.com");
102-
expect(path).toBe("/");
103-
});
104-
105-
it("should use a dualstack endpoint if so configured", async () => {
106-
const request = new HttpRequest(requestInput);
107-
const handler = bucketEndpointMiddleware(
108-
resolveBucketEndpointConfig({
109-
...previouslyResolvedConfig,
110-
useDualstackEndpoint: true,
111-
})
112-
)(next, {} as any);
113-
await handler({ input, request });
114-
115-
const {
116-
input: forwardedInput,
117-
request: { hostname, path },
118-
} = next.mock.calls[0][0];
50+
it("should supply default parameters to bucket hostname constructor", async () => {
51+
const request = new HttpRequest(requestInput);
52+
mockArnValidation.mockReturnValue(false);
53+
const handler = bucketEndpointMiddleware(
54+
resolveBucketEndpointConfig({
55+
...previouslyResolvedConfig,
56+
})
57+
)(next, {} as any);
58+
await handler({ input, request });
59+
expect(mockBucketHostname).toBeCalled();
60+
const param = mockBucketHostname.mock.calls[0][0];
61+
expect(param).toEqual({
62+
bucketName: input.Bucket,
63+
baseHostname: requestInput.hostname,
64+
accelerateEndpoint: false,
65+
dualstackEndpoint: false,
66+
pathStyleEndpoint: false,
67+
tlsCompatible: true,
68+
});
69+
});
11970

120-
expect(forwardedInput).toBe(input);
121-
expect(hostname).toBe("bucket.s3.dualstack.us-west-2.amazonaws.com");
122-
expect(path).toBe("/");
71+
it("should relay parameters to bucket hostname constructor", async () => {
72+
const request = new HttpRequest({ ...requestInput, protocol: "http:" });
73+
mockArnValidation.mockReturnValue(false);
74+
const handler = bucketEndpointMiddleware(
75+
resolveBucketEndpointConfig({
76+
...previouslyResolvedConfig,
77+
useAccelerateEndpoint: true,
78+
useDualstackEndpoint: true,
79+
forcePathStyle: true,
80+
})
81+
)(next, {} as any);
82+
await handler({ input, request });
83+
expect(mockBucketHostname).toBeCalled();
84+
const param = mockBucketHostname.mock.calls[0][0];
85+
expect(param).toEqual({
86+
bucketName: input.Bucket,
87+
baseHostname: requestInput.hostname,
88+
accelerateEndpoint: true,
89+
dualstackEndpoint: true,
90+
pathStyleEndpoint: true,
91+
tlsCompatible: false,
92+
});
93+
});
12394
});
12495

125-
it("should use an accelerate dualstack endpoint if configured", async () => {
126-
const request = new HttpRequest(requestInput);
127-
const handler = bucketEndpointMiddleware(
128-
resolveBucketEndpointConfig({
129-
...previouslyResolvedConfig,
130-
useAccelerateEndpoint: true,
131-
useDualstackEndpoint: true,
132-
})
133-
)(next, {} as any);
134-
await handler({ input, request });
96+
describe("allows bucket name to be an ARN", () => {
97+
beforeEach(() => {
98+
mockArnValidation.mockReturnValue(true);
99+
mockBucketHostname.mockReturnValue({
100+
bucketEndpoint: true,
101+
hostname: "myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com",
102+
});
103+
});
135104

136-
const {
137-
request: { hostname, path },
138-
} = next.mock.calls[0][0];
105+
it("should relay parameters to bucket hostname constructor", async () => {
106+
const request = new HttpRequest(requestInput);
107+
const handler = bucketEndpointMiddleware(
108+
resolveBucketEndpointConfig({
109+
...previouslyResolvedConfig,
110+
})
111+
)(next, {} as any);
112+
await handler({
113+
input: { Bucket: "myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com" },
114+
request,
115+
});
116+
expect(mockBucketHostname).toBeCalled();
117+
const param = mockBucketHostname.mock.calls[0][0];
118+
expect(param).toEqual({
119+
bucketName: mockBucketArn,
120+
baseHostname: requestInput.hostname,
121+
accelerateEndpoint: false,
122+
dualstackEndpoint: false,
123+
pathStyleEndpoint: false,
124+
tlsCompatible: true,
125+
clientPartition: "aws-foo",
126+
clientSigningRegion: "us-foo-1",
127+
useArnRegion: false,
128+
});
129+
expect(previouslyResolvedConfig.region).toBeCalled();
130+
expect(previouslyResolvedConfig.regionInfoProvider).toBeCalled();
131+
expect(previouslyResolvedConfig.useArnRegion).toBeCalled();
132+
});
139133

140-
expect(hostname).toBe("bucket.s3-accelerate.dualstack.amazonaws.com");
141-
expect(path).toBe("/");
142-
});
134+
it("should get client partition and signing region with pseudo region", async () => {
135+
const request = new HttpRequest(requestInput);
136+
const handler = bucketEndpointMiddleware(
137+
resolveBucketEndpointConfig({
138+
...previouslyResolvedConfig,
139+
region: () => Promise.resolve("fips-us-foo-1"),
140+
})
141+
)(next, {} as any);
142+
await handler({
143+
input: { Bucket: "myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com" },
144+
request,
145+
});
146+
expect(previouslyResolvedConfig.regionInfoProvider).toBeCalled();
147+
expect(previouslyResolvedConfig.regionInfoProvider.mock.calls[0][0]).toBe("us-foo-1");
148+
});
143149

144-
it("should be inserted before 'hostheaderMiddleware' if exists", async () => {
145-
const stack = constructStack();
146-
const mockHostheaderMiddleware = (next: any) => (args: any) => {
147-
args.request.arr.push("two");
148-
return next(args);
149-
};
150-
const mockbucketEndpointMiddleware = (next: any) => (args: any) => {
151-
args.request.arr.push("one");
152-
return next(args);
153-
};
154-
stack.add(mockHostheaderMiddleware, {
155-
...bucketEndpointMiddlewareOptions,
156-
name: bucketEndpointMiddlewareOptions.toMiddleware,
150+
it("should supply bucketHostname in ARN object if bucket name string is a valid ARN", async () => {
151+
const request = new HttpRequest(requestInput);
152+
const handler = bucketEndpointMiddleware(
153+
resolveBucketEndpointConfig({
154+
...previouslyResolvedConfig,
155+
})
156+
)(next, {} as any);
157+
await handler({
158+
input: { Bucket: "myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com" },
159+
request,
160+
});
161+
expect(mockBucketHostname).toBeCalled();
162+
expect(mockBucketHostname.mock.calls[0][0].bucketName).toBe(mockBucketArn);
163+
expect(mockArnParse).toBeCalled();
164+
expect(mockArnValidation).toBeCalled();
157165
});
158-
stack.addRelativeTo(mockbucketEndpointMiddleware, bucketEndpointMiddlewareOptions);
159-
const handler = stack.resolve(next, {} as any);
160-
expect.assertions(2);
161-
await handler({ request: { arr: [] }, input: {} } as any);
162-
expect(next.mock.calls.length).toBe(1);
163-
expect(next.mock.calls[0][0].request.arr).toEqual(["one", "two"]);
164166
});
165167
});

packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,15 @@ export function bucketEndpointMiddleware(options: BucketEndpointResolvedConfig):
2525
if (options.bucketEndpoint) {
2626
request.hostname = bucketName;
2727
} else {
28-
const clientRegion = await options.region();
29-
const { partition: clientPartition, signingRegion: clientSigningRegion } =
30-
(await options.regionInfoProvider(getPseudoRegion(clientRegion))) || {};
28+
const isBucketNameArn = validateArn(bucketName);
3129
const { hostname, bucketEndpoint } = bucketHostname({
32-
bucketName: validateArn(bucketName) ? parseArn(bucketName) : bucketName,
30+
bucketName: isBucketNameArn ? parseArn(bucketName) : bucketName,
3331
baseHostname: request.hostname,
3432
accelerateEndpoint: options.useAccelerateEndpoint,
3533
dualstackEndpoint: options.useDualstackEndpoint,
3634
pathStyleEndpoint: options.forcePathStyle,
3735
tlsCompatible: request.protocol === "https:",
38-
useArnRegion: await options.useArnRegion(),
39-
clientPartition,
40-
clientSigningRegion,
36+
...(isBucketNameArn ? await getArnHostnameParameters(options) : {}),
4137
});
4238

4339
request.hostname = hostname;
@@ -56,6 +52,16 @@ export function bucketEndpointMiddleware(options: BucketEndpointResolvedConfig):
5652
};
5753
}
5854

55+
const getArnHostnameParameters = async (options: BucketEndpointResolvedConfig) => {
56+
const clientRegion = await options.region();
57+
const clientRegionInfo = await options.regionInfoProvider(getPseudoRegion(clientRegion));
58+
return {
59+
useArnRegion: await options.useArnRegion(),
60+
clientPartition: clientRegionInfo?.partition,
61+
clientSigningRegion: clientRegionInfo?.signingRegion,
62+
};
63+
};
64+
5965
export const bucketEndpointMiddlewareOptions: RelativeMiddlewareOptions = {
6066
tags: ["BUCKET_ENDPOINT"],
6167
name: "bucketEndpointMiddleware",

packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,7 @@ describe("bucketHostname", () => {
312312
});
313313

314314
describe("should validate Access Point ARN", () => {
315-
type TestData = {
316-
bucketArn: string;
317-
message: string;
318-
};
319-
const testData: TestData[] = [
315+
[
320316
{
321317
bucketArn: "arn:aws:sqs:us-west-2:123456789012:someresource",
322318
message: "Expect 's3' in access point ARN service component",
@@ -353,8 +349,7 @@ describe("bucketHostname", () => {
353349
bucketArn: "arn:aws:s3:us-west-2:123456789012:accesspoint:mybucket:object:foo ",
354350
message: "Access Point ARN should have one resource accesspoint/{accesspointname}",
355351
},
356-
];
357-
testData.forEach(({ bucketArn, message }) => {
352+
].forEach(({ bucketArn, message }) => {
358353
it(`should throw "${message}"`, () => {
359354
expect(() => {
360355
bucketHostname({

packages/middleware-bucket-endpoint/src/configurations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface BucketEndpointInputConfig {
2525
}
2626

2727
interface PreviouslyResolved {
28+
useArnRegion: Provider<boolean>;
2829
region: Provider<string>;
2930
regionInfoProvider: RegionInfoProvider;
3031
}

0 commit comments

Comments
 (0)