import * as utils from "./utils.js"; import { ContentDoesNotExistError } from "./ContentDoesNotExistError.js"; import { createPartialContentHandler } from "./createPartialContentHandler.js"; import type { ContentProvider } from "./ContentProvider.js"; import type { Logger } from "./Logger.js"; import type { Request, Response } from "express"; import type { Content } from "./Content.js"; import { Stream } from "stream"; import type { Range } from "./Range.js"; import type { MockInstance } from "vitest"; describe("createPartialContentHandler tests", () => { let logger: Logger; beforeEach(() => { logger = { debug: vi.fn() as (message: string, extra?: any) => void }; }); afterEach(() => { vi.restoreAllMocks(); }); it("returns a handler", () => { const contentProvider = vi.fn().mockResolvedValue({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); expect(typeof handler === "function"); }); describe("handler tests", () => { let req: Request; let res: Response; let statusSpy: MockInstance; let sendSpy: MockInstance; let sendStatusSpy: MockInstance; beforeEach(() => { req = {} as Request; res = { status: (code: number) => res, send: (message: string) => res, sendStatus: (code: number) => res, setHeader: vi.fn() as (name: string, value: string) => void } as Response; statusSpy = vi.spyOn(res, "status"); sendSpy = vi.spyOn(res, "send"); sendStatusSpy = vi.spyOn(res, "sendStatus"); }); it("invokes contentProvider with the specified request", async () => { const contentProvider = vi.fn().mockResolvedValue({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); } catch {} expect(contentProvider).toHaveBeenCalledExactlyOnceWith(req); }); it("returns 404 if contentProvider throws ContentDoesNotExistError error", async () => { const error = new ContentDoesNotExistError("404-File not found!"); const contentProvider = vi.fn().mockRejectedValue(error) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); expect(statusSpy).toHaveBeenCalledExactlyOnceWith(404); expect(sendSpy).toHaveBeenCalledExactlyOnceWith(error.message); } catch { expect(false); } }); it("returns 500 if contentProvider throws any other error", async () => { const error = new Error("Something went wrong!"); const contentProvider = vi.fn().mockRejectedValue(error) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); expect(sendStatusSpy).toHaveBeenCalledExactlyOnceWith(500); } catch { expect(false); } }); it("returns 416 if parseRangeHeader throws RangeParserError error", async () => { const contentProvider = vi.fn().mockResolvedValue({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); req.headers = { range: "bytes=30-10" }; try { await handler(req, res); expect(statusSpy).toHaveBeenCalledExactlyOnceWith(416); } catch { expect(false); } }); it("returns 500 if parseRangeHeader throws other errors", async () => { const contentProvider = vi.fn().mockResolvedValue({}) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); try { await handler(req, res); expect(sendStatusSpy).toHaveBeenCalledExactlyOnceWith(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 = vi.spyOn(result, "pipe"); const getStreamSpy = vi.spyOn(content, "getStream"); const contentProvider = vi.fn().mockResolvedValue(content) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); const setContentTypeHeaderSpy = vi.spyOn(utils, "setContentTypeHeader"); const setContentDispositionHeaderSpy = vi.spyOn(utils, "setContentDispositionHeader"); const setAcceptRangesHeaderSpy = vi.spyOn(utils, "setAcceptRangesHeader"); const setContentLengthHeaderSpy = vi.spyOn(utils, "setContentLengthHeader"); const setContentRangeHeaderSpy = vi.spyOn(utils, "setContentRangeHeader"); try { await handler(req, res); expect(setContentTypeHeaderSpy).toHaveBeenCalledExactlyOnceWith(content.mimeType, res); expect(setContentDispositionHeaderSpy).toHaveBeenCalledExactlyOnceWith(content.fileName, res); expect(setAcceptRangesHeaderSpy).toHaveBeenCalledExactlyOnceWith(res); expect(setContentLengthHeaderSpy).toHaveBeenCalledExactlyOnceWith(content.totalSize, res); expect(getStreamSpy).toHaveBeenCalledExactlyOnceWith(); expect(pipeSpy).toHaveBeenCalledExactlyOnceWith(res); expect(setContentRangeHeaderSpy).not.toHaveBeenCalled(); } 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 = vi.spyOn(result, "pipe"); const getStreamSpy = vi.spyOn(content, "getStream"); const contentProvider = vi.fn().mockResolvedValue(content) as ContentProvider; const handler = createPartialContentHandler(contentProvider, logger); const setContentTypeHeaderSpy = vi.spyOn(utils, "setContentTypeHeader"); const setContentDispositionHeaderSpy = vi.spyOn(utils, "setContentDispositionHeader"); const setAcceptRangesHeaderSpy = vi.spyOn(utils, "setAcceptRangesHeader"); const setContentLengthHeaderSpy = vi.spyOn(utils, "setContentLengthHeader"); const setContentRangeHeaderSpy = vi.spyOn(utils, "setContentRangeHeader"); try { await handler(req, res); expect(setContentTypeHeaderSpy).toHaveBeenCalledExactlyOnceWith(content.mimeType, res); expect(setContentDispositionHeaderSpy).toHaveBeenCalledExactlyOnceWith(content.fileName, res); expect(setAcceptRangesHeaderSpy).toHaveBeenCalledExactlyOnceWith(res); expect(setContentRangeHeaderSpy).toHaveBeenCalledExactlyOnceWith(range, content.totalSize, res); expect(setContentLengthHeaderSpy).toHaveBeenCalledExactlyOnceWith(6, res); expect(getStreamSpy).toHaveBeenCalledExactlyOnceWith(range); expect(pipeSpy).toHaveBeenCalledExactlyOnceWith(res); } catch { expect(false); } }); }); });