import * as utils from "../src/utils"; import * as ParseRangeHeaderExports from "../src/parseRangeHeader"; import { ContentDoesNotExistError } from "../src/ContentDoesNotExistError"; import { SinonSandbox, createSandbox, SinonStub, SinonSpy } from "sinon"; import { createPartialContentHandler } from "../src/createPartialContentHandler"; import { ContentProvider } from "../src/ContentProvider"; import { Logger } from "../src/Logger"; import { expect } from "chai"; import { Request, Response } from "express"; import { Content } from "../src/Content"; import { Stream } from "stream"; import { Range } from "../src/Range"; describe("createPartialContentHandler tests", () => { let sandbox: SinonSandbox; let logger: Logger; beforeEach(() => { sandbox = createSandbox(); logger = { debug: sandbox.stub() as (message: string, extra?: any) => void }; }); afterEach(() => { sandbox.restore(); }); it("returns a handler", () => { const contentProvider = sandbox.stub().resolves({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); expect(typeof handler === "function"); }); describe("handler tests", () => { let req: Request; let res: Response; let statusSpy: SinonSpy; let sendSpy: SinonSpy; let sendStatusSpy: SinonSpy; beforeEach(() => { req = {} as Request; res = { status: (code: number) => res, send: (message: string) => res, sendStatus: (code: number) => res, setHeader: sandbox.stub() as (name: string, value: string) => void } as Response; statusSpy = sandbox.spy(res, "status"); sendSpy = sandbox.spy(res, "send"); sendStatusSpy = sandbox.spy(res, "sendStatus"); }); it("invokes contentProvider with the specified request", async () => { const contentProvider = sandbox.stub().resolves({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); } catch {} expect((contentProvider as SinonStub).calledOnceWith(req)); }); it("returns 404 if contentProvider throws ContentDoesNotExistError error", async () => { const error = new ContentDoesNotExistError("404-File not found!"); const contentProvider = sandbox.stub().rejects(error) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); expect(statusSpy.calledOnceWith(404)); expect(sendSpy.calledOnceWith(error.message)); } catch { expect(false); } }); it("returns 500 if contentProvider throws any other error", async () => { const error = new Error("Something went wrong!"); const contentProvider = sandbox.stub().rejects(error) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); expect(sendStatusSpy.calledOnceWith(500)); } catch { expect(false); } }); it("returns 416 if parseRangeHeader throws RangeParserError error", async () => { const contentProvider = sandbox.stub().resolves({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); req.headers = { range: "bytes=30-10" }; try { await handler(req, res); expect(statusSpy.calledOnceWith(416)); } catch { expect(false); } }); it("returns 500 if parseRangeHeader throws other errors", async () => { const parseRangeHeaderStub = sandbox .stub(ParseRangeHeaderExports, "parseRangeHeader") .throws(new Error("Something went wrong!")); const contentProvider = sandbox.stub().resolves({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); expect(sendStatusSpy.calledOnceWith(500)); } catch { expect(false); } }); it("returns correct response if range is not specified", async () => { const result = ({ pipe() { return result; } } as any) as Stream; const content: Content = { fileName: "file.txt", totalSize: 10, mimeType: "text/plain", getStream(range?: Range) { return result; } }; const pipeSpy = sandbox.spy(result, "pipe"); const getStreamSpy = sandbox.spy(content, "getStream"); const contentProvider = sandbox.stub().resolves(content) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); const setContentTypeHeaderSpy = sandbox.spy(utils, "setContentTypeHeader"); const setContentDispositionHeaderSpy = sandbox.spy(utils, "setContentDispositionHeader"); const setAcceptRangesHeaderSpy = sandbox.spy(utils, "setAcceptRangesHeader"); const setContentLengthHeaderSpy = sandbox.spy(utils, "setContentLengthHeader"); const setContentRangeHeaderSpy = sandbox.spy(utils, "setContentRangeHeader"); try { await handler(req, res); expect(setContentTypeHeaderSpy.calledOnceWith(content.mimeType, res)); expect(setContentDispositionHeaderSpy.calledOnceWith(content.fileName, res)); expect(setAcceptRangesHeaderSpy.calledOnceWith(res)); expect(setContentLengthHeaderSpy.calledOnceWith(content.totalSize, res)); expect(getStreamSpy.calledOnceWith()); expect(pipeSpy.calledOnceWith(res)); expect(setContentRangeHeaderSpy.notCalled); } catch { expect(false); } }); it("returns correct partial response if range is specified", async () => { req.headers = { range: "bytes=0-5" }; const result = ({ pipe() { return result; } } as any) as Stream; const content: Content = { fileName: "file.txt", totalSize: 10, mimeType: "text/plain", getStream(range?: Range) { return result; } }; const range = { start: 0, end: 5 }; const pipeSpy = sandbox.spy(result, "pipe"); const getStreamSpy = sandbox.spy(content, "getStream"); const contentProvider = sandbox.stub().resolves(content) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); const setContentTypeHeaderSpy = sandbox.spy(utils, "setContentTypeHeader"); const setContentDispositionHeaderSpy = sandbox.spy(utils, "setContentDispositionHeader"); const setAcceptRangesHeaderSpy = sandbox.spy(utils, "setAcceptRangesHeader"); const setContentLengthHeaderSpy = sandbox.spy(utils, "setContentLengthHeader"); const setContentRangeHeaderSpy = sandbox.spy(utils, "setContentRangeHeader"); try { await handler(req, res); expect(setContentTypeHeaderSpy.calledOnceWith(content.mimeType, res)); expect(setContentDispositionHeaderSpy.calledOnceWith(content.fileName, res)); expect(setAcceptRangesHeaderSpy.calledOnceWith(res)); expect(setContentRangeHeaderSpy.calledOnceWith(range, content.totalSize, res)); expect(setContentLengthHeaderSpy.calledOnceWith(6, res)); expect(getStreamSpy.calledOnceWith(range)); expect(pipeSpy.calledOnceWith(res)); } catch { expect(false); } }); }); });