Skip to content

Commit 7cd8fdb

Browse files
committed
test(eventstream-handler-node): add unit tests
1 parent 72faa6d commit 7cd8fdb

File tree

3 files changed

+231
-3
lines changed

3 files changed

+231
-3
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { EventSigningStream } from "./EventSigningStream";
2+
import { EventStreamMarshaller } from "@aws-sdk/eventstream-marshaller";
3+
import { toUtf8, fromUtf8 } from "@aws-sdk/util-utf8-node";
4+
import { Message, MessageHeaders } from "@aws-sdk/types";
5+
6+
describe("EventSigningStream", () => {
7+
let originalDate = Date;
8+
afterEach(() => {
9+
Date = originalDate;
10+
});
11+
it("should sign a eventstream payload properly", done => {
12+
const marshaller = new EventStreamMarshaller(toUtf8, fromUtf8);
13+
const inputChunks: Array<Uint8Array> = ([
14+
{
15+
headers: {},
16+
body: fromUtf8("foo")
17+
},
18+
{
19+
headers: {},
20+
body: fromUtf8("bar")
21+
}
22+
] as Array<Message>).map(event => marshaller.marshall(event));
23+
const expected: Array<MessageHeaders> = [
24+
{
25+
":date": { type: "timestamp", value: new Date(1546045446000) },
26+
":chunk-signature": {
27+
type: "binary",
28+
value: Uint8Array.from([
29+
115,
30+
105,
31+
103,
32+
110,
33+
97,
34+
116,
35+
117,
36+
114,
37+
101,
38+
49
39+
])
40+
}
41+
},
42+
{
43+
":date": { type: "timestamp", value: new Date(1546045447000) },
44+
":chunk-signature": {
45+
type: "binary",
46+
value: Uint8Array.from([
47+
115,
48+
105,
49+
103,
50+
110,
51+
97,
52+
116,
53+
117,
54+
114,
55+
101,
56+
50
57+
])
58+
}
59+
}
60+
];
61+
const mockEventSigner = jest
62+
.fn()
63+
.mockReturnValueOnce("7369676e617475726531") //'signature1'
64+
.mockReturnValueOnce("7369676e617475726532"); //'signature2'
65+
// mock 'new Date()'
66+
let mockDateCount = 0;
67+
const mockDate = jest
68+
.spyOn(global, "Date")
69+
//@ts-ignore: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor/60918716#60918716
70+
.mockImplementation(input => {
71+
if (input) return new originalDate(input);
72+
mockDateCount += 1;
73+
return expected[mockDateCount - 1][":date"].value;
74+
});
75+
const signingStream = new EventSigningStream({
76+
priorSignature: "initial",
77+
eventSigner: { sign: mockEventSigner },
78+
eventMarshaller: marshaller
79+
});
80+
const output: Array<MessageHeaders> = [];
81+
signingStream.on("data", chunk => {
82+
output.push(marshaller.unmarshall(chunk).headers);
83+
});
84+
signingStream.on("end", () => {
85+
expect(output).toEqual(expected);
86+
expect(mockEventSigner.mock.calls[0][1].priorSignature).toBe("initial");
87+
expect(mockEventSigner.mock.calls[0][1].signingDate.getTime()).toBe(
88+
(expected[0][":date"].value as Date).getTime()
89+
);
90+
expect(mockEventSigner.mock.calls[1][1].priorSignature).toBe(
91+
"7369676e617475726531"
92+
);
93+
expect(mockEventSigner.mock.calls[1][1].signingDate.getTime()).toBe(
94+
(expected[1][":date"].value as Date).getTime()
95+
);
96+
done();
97+
});
98+
signingStream.on("error", err => {
99+
throw err;
100+
});
101+
for (const input of inputChunks) {
102+
signingStream.write(input);
103+
}
104+
signingStream.end();
105+
});
106+
});
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { Readable, PassThrough } from "stream";
2+
const mockSingingStream = jest.fn().mockImplementation(() => new PassThrough());
3+
jest.mock("./EventSigningStream", () => ({
4+
EventSigningStream: mockSingingStream
5+
}));
6+
import { EventStreamPayloadHandler } from "./EventStreamPayloadHandler";
7+
import {
8+
EventSigner,
9+
Decoder,
10+
Encoder,
11+
FinalizeHandler,
12+
HttpRequest,
13+
FinalizeHandlerArguments
14+
} from "@aws-sdk/types";
15+
16+
describe("EventStreamPayloadHandler", () => {
17+
const mockSigner: EventSigner = {
18+
sign: jest.fn()
19+
};
20+
const mockUtf8Decoder: Decoder = jest.fn();
21+
const mockUtf8encoder: Encoder = jest.fn();
22+
const mockNextHandler: FinalizeHandler<any, any> = jest.fn();
23+
beforeEach(() => {
24+
jest.clearAllMocks();
25+
});
26+
it("should throw if request payload is not a stream", () => {
27+
const handler = new EventStreamPayloadHandler({
28+
eventSigner: () => Promise.resolve(mockSigner),
29+
utf8Decoder: mockUtf8Decoder,
30+
utf8Encoder: mockUtf8encoder
31+
});
32+
expect(
33+
handler.handle(mockNextHandler, {
34+
request: { body: "body" } as HttpRequest,
35+
input: {}
36+
})
37+
).rejects.toThrow("Eventstream payload must be a Readable stream.");
38+
});
39+
it("should close the request payload if downstream middleware throws", async () => {
40+
(mockNextHandler as any).mockImplementationOnce(() =>
41+
Promise.reject(new Error())
42+
);
43+
const handler = new EventStreamPayloadHandler({
44+
eventSigner: () => Promise.resolve(mockSigner),
45+
utf8Decoder: mockUtf8Decoder,
46+
utf8Encoder: mockUtf8encoder
47+
});
48+
const mockRequest = { body: new Readable() } as HttpRequest;
49+
let error;
50+
try {
51+
await handler.handle(mockNextHandler, {
52+
request: mockRequest,
53+
input: {}
54+
});
55+
} catch (e) {
56+
error = e;
57+
}
58+
expect(error instanceof Error).toBe(true);
59+
expect(mockRequest.body.writableEnded).toBe(true);
60+
});
61+
it("should call event signer with request signature from signing middleware", async () => {
62+
const authorization =
63+
"AWS4-HMAC-SHA256 Credential=AKID/20200510/us-west-2/foo/aws4_request, SignedHeaders=host, Signature=1234567890";
64+
const mockRequest = {
65+
body: new PassThrough(),
66+
headers: { authorization }
67+
} as any;
68+
const handler = new EventStreamPayloadHandler({
69+
eventSigner: () => Promise.resolve(mockSigner),
70+
utf8Decoder: mockUtf8Decoder,
71+
utf8Encoder: mockUtf8encoder
72+
});
73+
await handler.handle(mockNextHandler, {
74+
request: mockRequest,
75+
input: {}
76+
});
77+
expect(mockSingingStream.mock.calls.length).toBe(1);
78+
expect(mockSingingStream.mock.calls[0][0].priorSignature).toBe(
79+
"1234567890"
80+
);
81+
});
82+
it("should start piping to request payload through event signer if downstream middleware returns", async () => {
83+
const authorization =
84+
"AWS4-HMAC-SHA256 Credential=AKID/20200510/us-west-2/foo/aws4_request, SignedHeaders=host, Signature=1234567890";
85+
const originalPayload = new PassThrough();
86+
const mockRequest = {
87+
body: originalPayload,
88+
headers: { authorization }
89+
} as any;
90+
const handler = new EventStreamPayloadHandler({
91+
eventSigner: () => Promise.resolve(mockSigner),
92+
utf8Decoder: mockUtf8Decoder,
93+
utf8Encoder: mockUtf8encoder
94+
});
95+
// Middleware that returns the request from payload handler.
96+
(mockNextHandler as any).mockImplementationOnce(
97+
(args: FinalizeHandlerArguments<any>) => {
98+
const handledRequest = args.request as HttpRequest;
99+
// This middleware returns the output request from eventstream payload handler
100+
return Promise.resolve({ output: { handledRequest } });
101+
}
102+
);
103+
const {
104+
output: { handledRequest }
105+
} = await handler.handle(mockNextHandler, {
106+
request: mockRequest,
107+
input: {}
108+
});
109+
// Expect the output payload stream from handler is not the exact stream supplied to the handler
110+
expect(handledRequest.body).not.toBe(originalPayload);
111+
// Expect the data from the output payload from eventstream payload handler the same as from the
112+
// stream supplied to the handler.
113+
const collectData = (stream: Readable) => {
114+
const chunks: any = [];
115+
return new Promise((resolve, reject) => {
116+
stream.on("data", chunk => chunks.push(chunk));
117+
stream.on("error", reject);
118+
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
119+
});
120+
};
121+
originalPayload.end("Some Data");
122+
const collected = await collectData(handledRequest.body);
123+
expect(collected).toEqual("Some Data");
124+
});
125+
});

packages/eventstream-handler-node/src/EventStreamPayloadHandler.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import {
22
EventStreamPayloadHandler as IEventStreamPayloadHandler,
33
MetadataBearer,
4-
BuildHandler,
5-
BuildHandlerArguments,
6-
BuildHandlerOutput,
74
Provider,
85
EventSigner,
96
Encoder,

0 commit comments

Comments
 (0)