refactor(test): move outside of src folder

This commit is contained in:
Elian Doran 2024-12-11 22:18:41 +02:00
parent 3354d5df7a
commit 3c21050a1f
No known key found for this signature in database
4 changed files with 348 additions and 345 deletions

View File

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

View File

@ -1,58 +1,58 @@
import { parseRangeHeader } from "./parseRangeHeader"; import { parseRangeHeader } from "../src/parseRangeHeader";
import { SinonSandbox, createSandbox } from "sinon"; import { SinonSandbox, createSandbox } from "sinon";
import { Logger } from "./Logger"; import { Logger } from "../src/Logger";
import { expect } from "chai"; import { expect } from "chai";
import { RangeParserError } from "./RangeParserError"; import { RangeParserError } from "../src/RangeParserError";
describe("parseRangeHeader tests", () => { describe("parseRangeHeader tests", () => {
let sandbox: SinonSandbox; let sandbox: SinonSandbox;
let logger: Logger; let logger: Logger;
beforeEach(() => { beforeEach(() => {
sandbox = createSandbox(); sandbox = createSandbox();
logger = { logger = {
debug: sandbox.stub() as (message: string, extra?: any) => void debug: sandbox.stub() as (message: string, extra?: any) => void
}; };
}); });
afterEach(() => { afterEach(() => {
sandbox.restore(); sandbox.restore();
}); });
it("returns null if range is not specified", () => { it("returns null if range is not specified", () => {
let value = parseRangeHeader("", 10, logger); let value = parseRangeHeader("", 10, logger);
expect(value).to.be.equal(null); expect(value).to.be.equal(null);
value = parseRangeHeader(null, 10, logger); value = parseRangeHeader(null, 10, logger);
expect(value).to.be.equal(null); expect(value).to.be.equal(null);
}); });
it("returns null if total size is zero", () => { it("returns null if total size is zero", () => {
let value = parseRangeHeader("bytes=0-5", 0, logger); let value = parseRangeHeader("bytes=0-5", 0, logger);
expect(value).to.be.equal(null); expect(value).to.be.equal(null);
}); });
it("if end is not provided, sets end to the last byte (totalSize - 1).", () => { it("if end is not provided, sets end to the last byte (totalSize - 1).", () => {
let value = parseRangeHeader("bytes=0-", 10, logger); let value = parseRangeHeader("bytes=0-", 10, logger);
expect(value).to.be.deep.equal({ start: 0, end: 9 }); expect(value).to.be.deep.equal({ start: 0, end: 9 });
}); });
it('if start is not provided, set it to the offset of last "end" bytes from the end of the file.', () => { it('if start is not provided, set it to the offset of last "end" bytes from the end of the file.', () => {
let value = parseRangeHeader("bytes=-5", 10, logger); let value = parseRangeHeader("bytes=-5", 10, logger);
expect(value).to.be.deep.equal({ start: 5, end: 9 }); expect(value).to.be.deep.equal({ start: 5, end: 9 });
}); });
it("handles invalid ranges", () => { it("handles invalid ranges", () => {
try { try {
parseRangeHeader("bytes=6-5", 10, logger); parseRangeHeader("bytes=6-5", 10, logger);
} catch (error) { } catch (error) {
expect(error).that.be.instanceOf(RangeParserError); expect(error).that.be.instanceOf(RangeParserError);
} }
try { try {
parseRangeHeader("bytes=6-7", 10, logger); parseRangeHeader("bytes=6-7", 10, logger);
} catch (error) { } catch (error) {
expect(error).that.be.instanceOf(RangeParserError); expect(error).that.be.instanceOf(RangeParserError);
} }
try { try {
parseRangeHeader("bytes=6-11", 10, logger); parseRangeHeader("bytes=6-11", 10, logger);
} catch (error) { } catch (error) {
expect(error).that.be.instanceOf(RangeParserError); expect(error).that.be.instanceOf(RangeParserError);
} }
}); });
it("returns a valid parsed range.", () => { it("returns a valid parsed range.", () => {
let value = parseRangeHeader("bytes=0-5", 10, logger); let value = parseRangeHeader("bytes=0-5", 10, logger);
expect(value).to.be.deep.equal({ start: 0, end: 5 }); expect(value).to.be.deep.equal({ start: 0, end: 5 });
}); });
}); });

View File

@ -1,104 +1,104 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { expect } from "chai"; import { expect } from "chai";
import sinon, { SinonStub, SinonSpy } from "sinon"; import sinon, { SinonStub, SinonSpy } from "sinon";
import { import {
getHeader, getHeader,
setHeader, setHeader,
getRangeHeader, getRangeHeader,
setContentTypeHeader, setContentTypeHeader,
setContentLengthHeader, setContentLengthHeader,
setAcceptRangesHeader, setAcceptRangesHeader,
setContentDispositionHeader, setContentDispositionHeader,
setContentRangeHeader, setContentRangeHeader,
setCacheControlHeaderNoCache setCacheControlHeaderNoCache
} from "./utils"; } from "../src/utils";
describe("utils tests", () => { describe("utils tests", () => {
let req: Request; let req: Request;
let res: Response; let res: Response;
beforeEach(() => { beforeEach(() => {
req = { req = {
headers: { headers: {
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
range: "*" range: "*"
} }
} as Request; } as Request;
res = { res = {
setHeader: sinon.stub() as (name: string, value: string) => void setHeader: sinon.stub() as (name: string, value: string) => void
} as Response; } as Response;
}); });
describe("getHeader tests", () => { describe("getHeader tests", () => {
it("gets the specified header value if present", () => { it("gets the specified header value if present", () => {
const value = getHeader("content-type", req); const value = getHeader("content-type", req);
expect(value).to.equal("application/octet-stream"); expect(value).to.equal("application/octet-stream");
}); });
it("returns undefined if the specified header value is absent", () => { it("returns undefined if the specified header value is absent", () => {
const value = getHeader("mime-type", req); const value = getHeader("mime-type", req);
expect(value).to.be.undefined; expect(value).to.be.undefined;
}); });
}); });
describe("setHeader tests", () => { describe("setHeader tests", () => {
it("invokes res.setHeader API with the specified name and value args", () => { it("invokes res.setHeader API with the specified name and value args", () => {
const name = "Content-Type"; const name = "Content-Type";
const value = "application/octet-stream"; const value = "application/octet-stream";
setHeader(name, value, res); setHeader(name, value, res);
expect((res.setHeader as SinonStub).calledOnceWith(name, value)); expect((res.setHeader as SinonStub).calledOnceWith(name, value));
}); });
}); });
describe("getRangeHeader tests", () => { describe("getRangeHeader tests", () => {
it("gets range header value", () => { it("gets range header value", () => {
const value = getRangeHeader(req); const value = getRangeHeader(req);
expect(value).to.equal("*"); expect(value).to.equal("*");
}); });
}); });
describe("setContentTypeHeader tests", () => { describe("setContentTypeHeader tests", () => {
it("sets Content-Type header with specified value", () => { it("sets Content-Type header with specified value", () => {
const value = "application/octet-stream"; const value = "application/octet-stream";
setContentTypeHeader(value, res); setContentTypeHeader(value, res);
expect((res.setHeader as SinonStub).calledOnceWith("Content-Type", value)); expect((res.setHeader as SinonStub).calledOnceWith("Content-Type", value));
}); });
}); });
describe("setContentLengthHeader tests", () => { describe("setContentLengthHeader tests", () => {
it("sets Content-Length header with specified value", () => { it("sets Content-Length header with specified value", () => {
const value = 100; const value = 100;
setContentLengthHeader(value, res); setContentLengthHeader(value, res);
expect((res.setHeader as SinonStub).calledOnceWith("Content-Length", value)); expect((res.setHeader as SinonStub).calledOnceWith("Content-Length", value));
}); });
}); });
describe("setAcceptRangesHeader tests", () => { describe("setAcceptRangesHeader tests", () => {
it("sets Accept-Ranges header with specified value", () => { it("sets Accept-Ranges header with specified value", () => {
const value = "bytes"; const value = "bytes";
setAcceptRangesHeader(res); setAcceptRangesHeader(res);
expect((res.setHeader as SinonStub).calledOnceWith("Accept-Ranges", value)); expect((res.setHeader as SinonStub).calledOnceWith("Accept-Ranges", value));
}); });
}); });
describe("setContentRangeHeader tests", () => { describe("setContentRangeHeader tests", () => {
it("sets Content-Range header with specified value", () => { it("sets Content-Range header with specified value", () => {
let range = { start: 10, end: 100 }; let range = { start: 10, end: 100 };
const size = 1000; const size = 1000;
let value = `bytes ${range.start}-${range.end}/${size}`; let value = `bytes ${range.start}-${range.end}/${size}`;
setContentRangeHeader(range, size, res); setContentRangeHeader(range, size, res);
expect((res.setHeader as SinonStub).calledOnceWith("Content-Range", value)); expect((res.setHeader as SinonStub).calledOnceWith("Content-Range", value));
range = null; range = null;
value = `bytes */${size}`; value = `bytes */${size}`;
setContentRangeHeader(range, size, res); setContentRangeHeader(range, size, res);
expect((res.setHeader as SinonStub).calledOnceWith("Content-Range", value)); expect((res.setHeader as SinonStub).calledOnceWith("Content-Range", value));
}); });
}); });
describe("setContentDispositionHeader tests", () => { describe("setContentDispositionHeader tests", () => {
it("sets Content-Disposition header with specified value", () => { it("sets Content-Disposition header with specified value", () => {
const fileName = "file.txt"; const fileName = "file.txt";
const value = `attachment; filename="${fileName}"`; const value = `attachment; filename="${fileName}"`;
setContentDispositionHeader(fileName, res); setContentDispositionHeader(fileName, res);
expect((res.setHeader as SinonStub).calledOnceWith("Content-Disposition", value)); expect((res.setHeader as SinonStub).calledOnceWith("Content-Disposition", value));
}); });
}); });
describe("setCacheControlHeaderNoCache tests", () => { describe("setCacheControlHeaderNoCache tests", () => {
it("sets Cache-Control header with specified value", () => { it("sets Cache-Control header with specified value", () => {
const value = "no-cache"; const value = "no-cache";
setCacheControlHeaderNoCache(res); setCacheControlHeaderNoCache(res);
expect((res.setHeader as SinonStub).calledOnceWith("Cache-Control", value)); expect((res.setHeader as SinonStub).calledOnceWith("Cache-Control", value));
}); });
}); });
}); });

View File

@ -1,4 +1,7 @@
{ {
"extends": "./tsconfig.base.json", "extends": "./tsconfig.base.json",
"include": ["src/**/*.ts"] "include": [
"src/**/*.ts",
"spec/**/*.ts"
]
} }