feat(swagger): Add Swagger API reference, and fixes #28

Merged
Gandalf merged 28 commits from develop into main 2026-05-21 20:14:36 +01:00
2 changed files with 33 additions and 32 deletions
Showing only changes of commit ecaedbaf6a - Show all commits
+4 -4
View File
@@ -26,7 +26,7 @@ const DownloadMatcher = require('./DownloadMatcher');
* @param {string} options.ombiBaseUrl - Ombi base URL for link generation (optional)
* @returns {Array} Array of download objects for the user
*/
function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmin, showAll, seriesMap, moviesMap, sonarrTagMap, radarrTagMap, embyUserMap, ombiRetriever, ombiBaseUrl }) {
async function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmin, showAll, seriesMap, moviesMap, sonarrTagMap, radarrTagMap, embyUserMap, ombiRetriever, ombiBaseUrl }) {
// Input validation
if (!cacheSnapshot || typeof cacheSnapshot !== 'object') {
console.error('[DownloadBuilder] Invalid cacheSnapshot provided');
@@ -74,7 +74,7 @@ function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmi
const seenDownloadKeys = new Set();
if (sabnzbdQueue.data?.queue?.slots) {
const sabMatches = DownloadMatcher.matchSabSlots(sabnzbdQueue.data.queue.slots, context);
const sabMatches = await DownloadMatcher.matchSabSlots(sabnzbdQueue.data.queue.slots, context);
for (const dl of sabMatches) {
const key = `${dl.type}:${dl.title}`;
if (!seenDownloadKeys.has(key)) {
@@ -85,7 +85,7 @@ function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmi
}
if (sabnzbdHistory.data?.history?.slots) {
const sabHistoryMatches = DownloadMatcher.matchSabHistory(sabnzbdHistory.data.history.slots, context);
const sabHistoryMatches = await DownloadMatcher.matchSabHistory(sabnzbdHistory.data.history.slots, context);
for (const dl of sabHistoryMatches) {
const key = `${dl.type}:${dl.title}`;
if (!seenDownloadKeys.has(key)) {
@@ -95,7 +95,7 @@ function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmi
}
}
const torrentMatches = DownloadMatcher.matchTorrents(qbittorrentTorrents, context);
const torrentMatches = await DownloadMatcher.matchTorrents(qbittorrentTorrents, context);
for (const dl of torrentMatches) {
const key = `${dl.type}:${dl.title}`;
if (!seenDownloadKeys.has(key)) {
+29 -28
View File
@@ -21,6 +21,7 @@ import { describe, it, expect } from 'vitest';
import { buildUserDownloads } from '../../../server/services/DownloadBuilder.js';
describe('buildUserDownloads', () => {
// All tests in this suite are async because buildUserDownloads is async
const username = 'alice';
const usernameSanitized = 'alice';
const isAdmin = false;
@@ -56,7 +57,7 @@ describe('buildUserDownloads', () => {
}]
]);
it('returns empty array when no downloads match user', () => {
it('returns empty array when no downloads match user', async () => {
const cacheSnapshot = {
sabnzbdQueue: { data: { queue: { slots: [] } } },
sabnzbdHistory: { data: { history: { slots: [] } } },
@@ -67,7 +68,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -82,7 +83,7 @@ describe('buildUserDownloads', () => {
expect(result).toEqual([]);
});
it('returns empty array for null/undefined cache data', () => {
it('returns empty array for null/undefined cache data', async () => {
const cacheSnapshot = {
sabnzbdQueue: null,
sabnzbdHistory: null,
@@ -93,7 +94,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: null
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -108,7 +109,7 @@ describe('buildUserDownloads', () => {
expect(result).toEqual([]);
});
it('matches SABnzbd queue slot to Sonarr series for tagged user', () => {
it('matches SABnzbd queue slot to Sonarr series for tagged user', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -151,7 +152,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -178,7 +179,7 @@ describe('buildUserDownloads', () => {
expect(result[0].episodes).toBeInstanceOf(Array);
});
it('matches SABnzbd queue slot to Radarr movie for tagged user', () => {
it('matches SABnzbd queue slot to Radarr movie for tagged user', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -220,7 +221,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -246,7 +247,7 @@ describe('buildUserDownloads', () => {
});
});
it('matches qBittorrent torrent to Sonarr series for tagged user', () => {
it('matches qBittorrent torrent to Sonarr series for tagged user', async () => {
const cacheSnapshot = {
sabnzbdQueue: { data: { queue: { slots: [] } } },
sabnzbdHistory: { data: { history: { slots: [] } } },
@@ -280,7 +281,7 @@ describe('buildUserDownloads', () => {
}]
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -307,7 +308,7 @@ describe('buildUserDownloads', () => {
expect(result[0]).toHaveProperty('eta');
});
it('includes admin-specific fields when isAdmin is true', () => {
it('includes admin-specific fields when isAdmin is true', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -350,7 +351,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin: true,
@@ -377,7 +378,7 @@ describe('buildUserDownloads', () => {
});
});
it('filters by user tag when showAll is false', () => {
it('filters by user tag when showAll is false', async () => {
const bobSeriesMap = new Map([
[2, {
id: 2,
@@ -429,7 +430,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username: 'alice',
usernameSanitized: 'alice',
isAdmin: false,
@@ -445,7 +446,7 @@ describe('buildUserDownloads', () => {
expect(result).toEqual([]);
});
it('shows all tagged downloads when showAll is true (admin mode)', () => {
it('shows all tagged downloads when showAll is true (admin mode)', async () => {
const bobSeriesMap = new Map([
[2, {
id: 2,
@@ -497,7 +498,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username: 'alice',
usernameSanitized: 'alice',
isAdmin: true,
@@ -520,7 +521,7 @@ describe('buildUserDownloads', () => {
});
});
it('includes importIssues when present in queue record', () => {
it('includes importIssues when present in queue record', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -567,7 +568,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -583,7 +584,7 @@ describe('buildUserDownloads', () => {
expect(result[0].importIssues).toEqual(['Sample needs repack', 'Disk space low']);
});
it('handles mixed series and movie downloads', () => {
it('handles mixed series and movie downloads', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -651,7 +652,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -668,7 +669,7 @@ describe('buildUserDownloads', () => {
expect(result[1].type).toBe('movie');
});
it('prevents duplicate downloads when same item matches multiple sources', () => {
it('prevents duplicate downloads when same item matches multiple sources', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -730,7 +731,7 @@ describe('buildUserDownloads', () => {
}]
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -746,7 +747,7 @@ describe('buildUserDownloads', () => {
expect(result).toHaveLength(1);
});
it('matches SABnzbd history slots to completed downloads', () => {
it('matches SABnzbd history slots to completed downloads', async () => {
const cacheSnapshot = {
sabnzbdQueue: { data: { queue: { slots: [] } } },
sabnzbdHistory: {
@@ -782,7 +783,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin,
@@ -803,7 +804,7 @@ describe('buildUserDownloads', () => {
});
});
it('does not display unmatched torrents', () => {
it('does not display unmatched torrents', async () => {
const cacheSnapshot = {
sabnzbdQueue: { data: { queue: { slots: [] } } },
sabnzbdHistory: { data: { history: { slots: [] } } },
@@ -824,7 +825,7 @@ describe('buildUserDownloads', () => {
}]
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin: false,
@@ -840,7 +841,7 @@ describe('buildUserDownloads', () => {
expect(result).toEqual([]);
});
it('includes sonarrLink and radarrLink when available', () => {
it('includes sonarrLink and radarrLink when available', async () => {
const cacheSnapshot = {
sabnzbdQueue: {
data: {
@@ -908,7 +909,7 @@ describe('buildUserDownloads', () => {
qbittorrentTorrents: []
};
const result = buildUserDownloads(cacheSnapshot, {
const result = await buildUserDownloads(cacheSnapshot, {
username,
usernameSanitized,
isAdmin: true,