|
| 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 | +}); |
0 commit comments