mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-11-13 15:09:20 +01:00
test: add unit tests for configuration and download controllers and handle electron app initialization and improve devMode logic
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
const path = require("path");
|
||||
|
||||
jest.mock("../../server/helpers/getBrowser.helpers.js", () =>
|
||||
jest.fn(() => "firefox")
|
||||
);
|
||||
jest.mock("../../server/helpers/getBrowser.helpers.js", () => ({
|
||||
getUserBrowser: jest.fn(() => "firefox"),
|
||||
}));
|
||||
|
||||
jest.mock("../../server/helpers/path.helpers.js", () => ({
|
||||
ffmpegPath: "/mock/ffmpeg",
|
||||
|
||||
43
test/unit/config.test.js
Normal file
43
test/unit/config.test.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const {mergeMissingFeatures} = require("../../config");
|
||||
|
||||
describe("configuration migration", () => {
|
||||
test("adds missing defaults and preserves user values", () => {
|
||||
const result = mergeMissingFeatures(
|
||||
{enabled: true, retries: 3},
|
||||
{enabled: false}
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
features: {enabled: false, retries: 3},
|
||||
changed: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("preserves unknown user-defined keys", () => {
|
||||
const result = mergeMissingFeatures(
|
||||
{enabled: true},
|
||||
{enabled: false, customSetting: "keep me"}
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
features: {enabled: false, customSetting: "keep me"},
|
||||
changed: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("reports no migration when all default keys exist", () => {
|
||||
const result = mergeMissingFeatures(
|
||||
{enabled: true, retries: 3},
|
||||
{enabled: false, retries: 0}
|
||||
);
|
||||
|
||||
expect(result.changed).toBe(false);
|
||||
expect(result.features.retries).toBe(0);
|
||||
});
|
||||
|
||||
test("rejects non-object configurations", () => {
|
||||
expect(() => mergeMissingFeatures({}, [])).toThrow(
|
||||
"User configuration must be a JSON object"
|
||||
);
|
||||
});
|
||||
});
|
||||
213
test/unit/download.controller.test.js
Normal file
213
test/unit/download.controller.test.js
Normal file
@@ -0,0 +1,213 @@
|
||||
jest.mock("../../server/services/download.services", () => ({
|
||||
fetchDownload: jest.fn(),
|
||||
cancelDownload: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../server/logger", () => ({
|
||||
logger: {
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock("../../server/helpers/validation.helpers", () => ({
|
||||
isValidUrl: jest.fn(),
|
||||
isSafePath: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../server/helpers/notify.helpers", () => ({
|
||||
notifyDownloadFinished: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../config", () => ({
|
||||
configFeatures: {
|
||||
notifySystem: true,
|
||||
},
|
||||
}));
|
||||
|
||||
const {fetchDownload, cancelDownload} = require("../../server/services/download.services");
|
||||
const {logger} = require("../../server/logger");
|
||||
const {isValidUrl, isSafePath} = require("../../server/helpers/validation.helpers");
|
||||
const {notifyDownloadFinished} = require("../../server/helpers/notify.helpers");
|
||||
const {
|
||||
downloadController,
|
||||
cancelDownloadController,
|
||||
progressController,
|
||||
speedController,
|
||||
stageController,
|
||||
playlistInfoController,
|
||||
} = require("../../server/controller/download.controller");
|
||||
|
||||
describe("download.controller", () => {
|
||||
let req, res;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
req = {body: {}, on: jest.fn()};
|
||||
res = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn().mockReturnThis(),
|
||||
send: jest.fn().mockReturnThis(),
|
||||
set: jest.fn().mockReturnThis(),
|
||||
write: jest.fn().mockReturnThis(),
|
||||
flushHeaders: jest.fn(),
|
||||
end: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("downloadController", () => {
|
||||
test("returns 400 for invalid url", async () => {
|
||||
isValidUrl.mockReturnValue(false);
|
||||
req.body.url = "bad-url";
|
||||
await downloadController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "INVALID_URL", message: "Invalid URL"},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 400 for unsafe path", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
isSafePath.mockReturnValue(false);
|
||||
req.body.url = "https://youtube.com/watch?v=123";
|
||||
req.body.savePath = "C:\\Windows\\System32";
|
||||
await downloadController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "INVALID_PATH", message: "Save path not allowed"},
|
||||
});
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("starts download and returns success", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
isSafePath.mockReturnValue(true);
|
||||
req.body = {
|
||||
url: "https://youtube.com/watch?v=123",
|
||||
audioOnly: "1",
|
||||
quality: "720",
|
||||
savePath: "/home/user/Downloads",
|
||||
playlistTitle: "My Playlist",
|
||||
};
|
||||
fetchDownload.mockResolvedValue("/home/user/Downloads");
|
||||
await downloadController(req, res);
|
||||
expect(fetchDownload).toHaveBeenCalled();
|
||||
const callArgs = fetchDownload.mock.calls[0][0];
|
||||
expect(callArgs.url).toBe("https://youtube.com/watch?v=123");
|
||||
expect(callArgs.audioOnly).toBe(true);
|
||||
expect(callArgs.quality).toBe("720");
|
||||
expect(callArgs.outputFolder).toBe("/home/user/Downloads");
|
||||
expect(callArgs.playlistTitle).toBe("My Playlist");
|
||||
expect(notifyDownloadFinished).toHaveBeenCalledWith(
|
||||
"/home/user/Downloads",
|
||||
true
|
||||
);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
success: true,
|
||||
message: "Download Done !",
|
||||
outputFolder: "/home/user/Downloads",
|
||||
});
|
||||
});
|
||||
|
||||
test("defaults quality to best for invalid quality", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
fetchDownload.mockResolvedValue("/downloads");
|
||||
req.body = {url: "https://youtube.com/watch?v=123", quality: "invalid"};
|
||||
await downloadController(req, res);
|
||||
const callArgs = fetchDownload.mock.calls[0][0];
|
||||
expect(callArgs.quality).toBe("best");
|
||||
});
|
||||
|
||||
test("returns 409 when download already in progress", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
fetchDownload.mockRejectedValue(new Error("Download already in progress"));
|
||||
req.body.url = "https://youtube.com/watch?v=123";
|
||||
await downloadController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(409);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "DOWNLOAD_IN_PROGRESS", message: "A download is already in progress"},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 500 on generic error", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
fetchDownload.mockRejectedValue(new Error("yt-dlp failed"));
|
||||
req.body.url = "https://youtube.com/watch?v=123";
|
||||
await downloadController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "DOWNLOAD_FAILED", message: "Server Error"},
|
||||
});
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelDownloadController", () => {
|
||||
test("returns success when download cancelled", () => {
|
||||
cancelDownload.mockReturnValue(true);
|
||||
cancelDownloadController(req, res);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
success: true,
|
||||
message: "Download stopped! Queue cleared.",
|
||||
});
|
||||
expect(logger.info).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns 400 when no download in progress", () => {
|
||||
cancelDownload.mockReturnValue(false);
|
||||
cancelDownloadController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "NO_DOWNLOAD_IN_PROGRESS", message: "No download in progress !"},
|
||||
});
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSE controllers", () => {
|
||||
test("progressController sets headers and writes data", () => {
|
||||
req.on = jest.fn((event, cb) => {
|
||||
if (event === "close") {
|
||||
// don't call immediately
|
||||
}
|
||||
});
|
||||
progressController(req, res);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
})
|
||||
);
|
||||
expect(res.flushHeaders).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("speedController sets headers", () => {
|
||||
req.on = jest.fn();
|
||||
speedController(req, res);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
"Content-Type": "text/event-stream",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("stageController sets headers", () => {
|
||||
req.on = jest.fn();
|
||||
stageController(req, res);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
"Content-Type": "text/event-stream",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("playlistInfoController sets headers", () => {
|
||||
req.on = jest.fn();
|
||||
playlistInfoController(req, res);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
"Content-Type": "text/event-stream",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
83
test/unit/info.controller.test.js
Normal file
83
test/unit/info.controller.test.js
Normal file
@@ -0,0 +1,83 @@
|
||||
jest.mock("../../server/services/info.services", () => ({
|
||||
fetchNormalizedInfo: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../server/logger", () => ({
|
||||
logger: {
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock("../../server/helpers/validation.helpers", () => ({
|
||||
isValidUrl: jest.fn(),
|
||||
}));
|
||||
|
||||
const {fetchNormalizedInfo} = require("../../server/services/info.services");
|
||||
const {logger} = require("../../server/logger");
|
||||
const {isValidUrl} = require("../../server/helpers/validation.helpers");
|
||||
const {infoController} = require("../../server/controller/info.controller");
|
||||
|
||||
describe("info.controller", () => {
|
||||
let req, res;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
req = {query: {}, body: {}};
|
||||
res = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn().mockReturnThis(),
|
||||
send: jest.fn().mockReturnThis(),
|
||||
};
|
||||
});
|
||||
|
||||
test("returns 400 for missing url", async () => {
|
||||
isValidUrl.mockReturnValue(false);
|
||||
req.query.url = undefined;
|
||||
await infoController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "INVALID_URL", message: "Invalid or missing URL"},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 400 for invalid url", async () => {
|
||||
isValidUrl.mockReturnValue(false);
|
||||
req.query.url = "not-a-url";
|
||||
await infoController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "INVALID_URL", message: "Invalid or missing URL"},
|
||||
});
|
||||
});
|
||||
|
||||
test("accepts url from body", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
req.body.url = "https://youtube.com/watch?v=123";
|
||||
fetchNormalizedInfo.mockResolvedValue({type: "video", title: "Test"});
|
||||
await infoController(req, res);
|
||||
expect(fetchNormalizedInfo).toHaveBeenCalledWith("https://youtube.com/watch?v=123");
|
||||
expect(res.json).toHaveBeenCalledWith({type: "video", title: "Test"});
|
||||
});
|
||||
|
||||
test("returns normalized result for valid url", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
req.query.url = "https://youtube.com/watch?v=123";
|
||||
const mockResult = {type: "video", title: "My Video", duration: 120};
|
||||
fetchNormalizedInfo.mockResolvedValue(mockResult);
|
||||
await infoController(req, res);
|
||||
expect(fetchNormalizedInfo).toHaveBeenCalledWith("https://youtube.com/watch?v=123");
|
||||
expect(res.json).toHaveBeenCalledWith(mockResult);
|
||||
expect(logger.info).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns 500 on service error", async () => {
|
||||
isValidUrl.mockReturnValue(true);
|
||||
req.query.url = "https://youtube.com/watch?v=123";
|
||||
fetchNormalizedInfo.mockRejectedValue(new Error("fetch failed"));
|
||||
await infoController(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {code: "FETCH_FAILED", message: "Unable to fetch info"},
|
||||
});
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
161
test/unit/info.services.test.js
Normal file
161
test/unit/info.services.test.js
Normal file
@@ -0,0 +1,161 @@
|
||||
jest.mock("child_process");
|
||||
jest.mock("../../server/helpers/path.helpers", () => ({
|
||||
userYtDlp: "/mock/yt-dlp",
|
||||
denoPath: "/mock/deno",
|
||||
}));
|
||||
jest.mock("../../server/helpers/getBrowser.helpers", () => ({
|
||||
getUserBrowser: jest.fn(() => "firefox"),
|
||||
}));
|
||||
jest.mock("../../server/logger", () => ({
|
||||
logger: {
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock("../../server/helpers/parseInfo.helpers", () => ({
|
||||
parseVideo: jest.fn((data) => ({type: "video", ...data})),
|
||||
parsePlaylist: jest.fn((data) => ({
|
||||
type: "playlist",
|
||||
title: data.title,
|
||||
count: data.entries ? data.entries.length : 0,
|
||||
videos: [],
|
||||
})),
|
||||
}));
|
||||
jest.mock("../../server/helpers/playlist.helpers", () => ({
|
||||
isUrlPlaylist: jest.fn(),
|
||||
}));
|
||||
|
||||
const {execFile} = require("child_process");
|
||||
const {fetchInfo, fetchNormalizedInfo} = require("../../server/services/info.services");
|
||||
const {parseVideo, parsePlaylist} = require("../../server/helpers/parseInfo.helpers");
|
||||
const {isUrlPlaylist} = require("../../server/helpers/playlist.helpers");
|
||||
const {logger} = require("../../server/logger");
|
||||
|
||||
describe("info.services", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("fetchInfo", () => {
|
||||
test("resolves parsed json on success", async () => {
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(null, JSON.stringify({_type: "video", title: "Test"}), "");
|
||||
});
|
||||
const result = await fetchInfo("https://youtube.com/watch?v=123");
|
||||
expect(result).toEqual({_type: "video", title: "Test"});
|
||||
expect(execFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("rejects when cookies error in stderr", async () => {
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(null, "", "sign in to confirm you're not a bot");
|
||||
});
|
||||
await expect(fetchInfo("https://youtube.com/watch?v=123")).rejects.toThrow(
|
||||
/Unable to extract cookies/
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects on exec error", async () => {
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(new Error("spawn failed"), "", "");
|
||||
});
|
||||
await expect(fetchInfo("https://youtube.com/watch?v=123")).rejects.toThrow(
|
||||
"Unable to fetch Info."
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects when no stdout", async () => {
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(null, "", "");
|
||||
});
|
||||
await expect(fetchInfo("https://youtube.com/watch?v=123")).rejects.toThrow(
|
||||
"No Data Received."
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects on invalid json", async () => {
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(null, "not json", "");
|
||||
});
|
||||
await expect(fetchInfo("https://youtube.com/watch?v=123")).rejects.toThrow(
|
||||
"JSON unreadable."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchNormalizedInfo", () => {
|
||||
test("normalizes video", async () => {
|
||||
isUrlPlaylist.mockReturnValue(false);
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(null, JSON.stringify({_type: "video", title: "My Video"}), "");
|
||||
});
|
||||
const result = await fetchNormalizedInfo("https://youtube.com/watch?v=123");
|
||||
expect(result.type).toBe("video");
|
||||
expect(parseVideo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("normalizes playlist", async () => {
|
||||
isUrlPlaylist.mockReturnValue(true);
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(
|
||||
null,
|
||||
JSON.stringify({
|
||||
_type: "playlist",
|
||||
title: "Playlist",
|
||||
entries: [{id: "1"}, {id: "2"}],
|
||||
}),
|
||||
""
|
||||
);
|
||||
});
|
||||
const result = await fetchNormalizedInfo("https://youtube.com/playlist?list=ABC");
|
||||
expect(result.type).toBe("playlist");
|
||||
expect(parsePlaylist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("converts playlist with 1 entry to video", async () => {
|
||||
isUrlPlaylist.mockReturnValue(true);
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
cb(
|
||||
null,
|
||||
JSON.stringify({
|
||||
_type: "playlist",
|
||||
title: "Playlist",
|
||||
entries: [{id: "1", title: "Single"}],
|
||||
}),
|
||||
""
|
||||
);
|
||||
});
|
||||
const result = await fetchNormalizedInfo("https://youtube.com/playlist?list=ABC");
|
||||
expect(result.type).toBe("video");
|
||||
expect(parseVideo).toHaveBeenCalled();
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Playlist with 1 item converted to video")
|
||||
);
|
||||
});
|
||||
|
||||
test("falls back to single video when playlist fetch fails", async () => {
|
||||
isUrlPlaylist.mockReturnValue(true);
|
||||
let callCount = 0;
|
||||
execFile.mockImplementation((cmd, args, opts, cb) => {
|
||||
callCount++;
|
||||
if (callCount === 1) {
|
||||
cb(new Error("fail"), "", "");
|
||||
} else {
|
||||
cb(null, JSON.stringify({_type: "video", title: "Fallback"}), "");
|
||||
}
|
||||
});
|
||||
const result = await fetchNormalizedInfo("https://youtube.com/watch?v=123&list=ABC");
|
||||
expect(result.type).toBe("video");
|
||||
expect(callCount).toBe(2);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Retrying as single video")
|
||||
);
|
||||
});
|
||||
|
||||
test("throws when url is invalid type", async () => {
|
||||
await expect(fetchNormalizedInfo(null)).rejects.toThrow("Invalid URL");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
const mockShow = jest.fn();
|
||||
const mockOn = jest.fn();
|
||||
const mockOpenPath = jest.fn();
|
||||
const mockOpenPath = jest.fn().mockResolvedValue("");
|
||||
const mockOpenExternal = jest.fn();
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
@@ -71,13 +71,16 @@ describe("notify.helpers", () => {
|
||||
expect(mockShow).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("opens folder when notification is clicked", () => {
|
||||
test("opens folder when notification is clicked", (done) => {
|
||||
notifyDownloadFinished("/downloads");
|
||||
|
||||
const callback = mockOn.mock.calls[0][1];
|
||||
callback();
|
||||
|
||||
expect(shell.openPath).toHaveBeenCalledWith("/downloads");
|
||||
setTimeout(() => {
|
||||
expect(shell.openPath).toHaveBeenCalledWith("/downloads");
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,7 +104,7 @@ describe("notify.helpers", () => {
|
||||
callback();
|
||||
|
||||
expect(shell.openExternal).toHaveBeenCalledWith(
|
||||
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
|
||||
"https://www.firefox.com/en-US/download/"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -126,7 +129,7 @@ describe("notify.helpers", () => {
|
||||
callback();
|
||||
|
||||
expect(shell.openExternal).toHaveBeenCalledWith(
|
||||
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
|
||||
"https://www.firefox.com/en-US/download/"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
66
test/unit/parseInfo.helpers.test.js
Normal file
66
test/unit/parseInfo.helpers.test.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const {parseVideo, parsePlaylist} = require("../../server/helpers/parseInfo.helpers");
|
||||
|
||||
describe("parseInfo.helpers", () => {
|
||||
describe("parseVideo", () => {
|
||||
test("adds type video and spreads data", () => {
|
||||
const data = {title: "Test Video", duration: 120, id: "abc123"};
|
||||
const result = parseVideo(data);
|
||||
expect(result.type).toBe("video");
|
||||
expect(result.title).toBe("Test Video");
|
||||
expect(result.duration).toBe(120);
|
||||
expect(result.id).toBe("abc123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parsePlaylist", () => {
|
||||
test("normalizes playlist with entries", () => {
|
||||
const data = {
|
||||
title: "My Playlist",
|
||||
uploader: "Test Channel",
|
||||
entries: [
|
||||
{
|
||||
id: "vid1",
|
||||
title: "Video 1",
|
||||
webpage_url: "https://example.com/watch?v=vid1",
|
||||
duration: 60,
|
||||
thumbnail: "thumb1.jpg",
|
||||
uploader: "Uploader1",
|
||||
},
|
||||
{
|
||||
id: "vid2",
|
||||
title: "Video 2",
|
||||
webpage_url: "https://example.com/watch?v=vid2",
|
||||
duration: 90,
|
||||
thumbnail: "thumb2.jpg",
|
||||
uploader: "Uploader2",
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = parsePlaylist(data);
|
||||
expect(result.type).toBe("playlist");
|
||||
expect(result.title).toBe("My Playlist");
|
||||
expect(result.channel).toBe("Test Channel");
|
||||
expect(result.count).toBe(2);
|
||||
expect(result.videos).toHaveLength(2);
|
||||
expect(result.videos[0]).toEqual({
|
||||
id: "vid1",
|
||||
title: "Video 1",
|
||||
url: "https://example.com/watch?v=vid1",
|
||||
duration: 60,
|
||||
thumbnail: "thumb1.jpg",
|
||||
uploader: "Uploader1",
|
||||
});
|
||||
});
|
||||
|
||||
test("falls back to id when title missing", () => {
|
||||
const data = {
|
||||
id: "PL123",
|
||||
entries: [],
|
||||
};
|
||||
const result = parsePlaylist(data);
|
||||
expect(result.title).toBe("PL123");
|
||||
expect(result.count).toBe(0);
|
||||
expect(result.videos).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
40
test/unit/validation.helpers.test.js
Normal file
40
test/unit/validation.helpers.test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const {isValidUrl, isSafePath} = require("../../server/helpers/validation.helpers");
|
||||
|
||||
describe("validation.helpers", () => {
|
||||
describe("isValidUrl", () => {
|
||||
test("returns true for valid http url", () => {
|
||||
expect(isValidUrl("http://example.com")).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true for valid https url", () => {
|
||||
expect(isValidUrl("https://youtube.com/watch?v=123")).toBe(true);
|
||||
});
|
||||
|
||||
test("returns false for invalid url", () => {
|
||||
expect(isValidUrl("not-a-url")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns false for empty string", () => {
|
||||
expect(isValidUrl("")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSafePath", () => {
|
||||
test("returns false for empty or short path", () => {
|
||||
expect(isSafePath("")).toBe(false);
|
||||
expect(isSafePath("ab")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns false for Windows System32 path", () => {
|
||||
expect(isSafePath("C:\\Windows\\System32\\test")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns false for Windows path containing Windows", () => {
|
||||
expect(isSafePath("C:\\Windows\\SomeFolder")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns true for safe user path", () => {
|
||||
expect(isSafePath("/home/user/Downloads")).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user