ecaedbaf6a
Build and Push Docker Image / build (push) Successful in 50s
Docs Check / Markdown lint (push) Successful in 1m11s
CI / Security audit (push) Successful in 2m1s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m23s
CI / Tests & coverage (push) Successful in 2m30s
Docs Check / Mermaid diagram parse check (push) Successful in 2m9s
CI / Swagger Validation & Coverage (push) Successful in 4m2s
The buildUserDownloads function was calling async matcher functions without awaiting them, causing Promise objects to be returned instead of resolved arrays. This resulted in empty download lists and 17 failing tests. - Made buildUserDownloads async - Added await to matchSabSlots, matchSabHistory, and matchTorrents calls - Updated unit tests to await buildUserDownloads calls All 759 tests now pass.
929 lines
25 KiB
JavaScript
929 lines
25 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
/**
|
|
* Guard tests for server/services/DownloadBuilder.js
|
|
*
|
|
* This test file serves as a regression guard for the deduplicated download-assembly
|
|
* logic that will be extracted from dashboard.js into the DownloadBuilder service.
|
|
* The function buildUserDownloads does not exist yet - this test will pass once
|
|
* the implementation is complete in the next prompt.
|
|
*
|
|
* Coverage:
|
|
* - Happy path with matching downloads
|
|
* - Empty data scenarios
|
|
* - Mixed series and movies
|
|
* - Admin vs regular user permissions
|
|
* - showAll=true vs showAll=false filtering
|
|
* - Duplicate prevention (same download matched via multiple sources)
|
|
*/
|
|
|
|
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;
|
|
const showAll = false;
|
|
|
|
const sonarrTagMap = new Map([[1, 'alice'], [2, 'bob']]);
|
|
const radarrTagMap = new Map([[1, 'alice'], [2, 'bob']]);
|
|
const embyUserMap = new Map([['alice', 'Alice'], ['bob', 'Bob']]);
|
|
|
|
const seriesMap = new Map([
|
|
[1, {
|
|
id: 1,
|
|
title: 'Test Series',
|
|
titleSlug: 'test-series',
|
|
path: '/series/test',
|
|
tags: [1],
|
|
images: [{ coverType: 'poster', remoteUrl: 'https://example.com/poster.jpg' }],
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
]);
|
|
|
|
const moviesMap = new Map([
|
|
[1, {
|
|
id: 1,
|
|
title: 'Test Movie',
|
|
titleSlug: 'test-movie',
|
|
path: '/movies/test',
|
|
tags: [1],
|
|
images: [{ coverType: 'poster', remoteUrl: 'https://example.com/movie-poster.jpg' }],
|
|
_instanceUrl: 'https://radarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
]);
|
|
|
|
it('returns empty array when no downloads match user', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: { data: { queue: { slots: [] } } },
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: { data: { records: [] } },
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('returns empty array for null/undefined cache data', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: null,
|
|
sabnzbdHistory: null,
|
|
sonarrQueue: null,
|
|
sonarrHistory: null,
|
|
radarrQueue: null,
|
|
radarrHistory: null,
|
|
qbittorrentTorrents: null
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('matches SABnzbd queue slot to Sonarr series for tagged user', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'test.series.s01e01.720p',
|
|
nzbname: 'test.series.s01e01.720p',
|
|
status: 'Downloading',
|
|
percentage: 50,
|
|
mb: 1000,
|
|
mbmissing: 500,
|
|
size: '1 GB',
|
|
timeleft: '10:00',
|
|
storage: '/downloads/test'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e01.720p',
|
|
sourceTitle: 'test.series.s01e01.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 200,
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
type: 'series',
|
|
title: 'test.series.s01e01.720p',
|
|
status: 'Downloading',
|
|
progress: 50,
|
|
coverArt: 'https://example.com/poster.jpg',
|
|
seriesName: 'Test Series',
|
|
allTags: ['alice'],
|
|
matchedUserTag: 'alice',
|
|
client: 'sabnzbd'
|
|
});
|
|
expect(result[0].episodes).toBeInstanceOf(Array);
|
|
});
|
|
|
|
it('matches SABnzbd queue slot to Radarr movie for tagged user', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'test.movie.2023.1080p',
|
|
nzbname: 'test.movie.2023.1080p',
|
|
status: 'Downloading',
|
|
percentage: 75,
|
|
mb: 2000,
|
|
mbmissing: 500,
|
|
size: '2 GB',
|
|
timeleft: '5:00',
|
|
storage: '/downloads/testmovie'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: { data: { records: [] } },
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'Test Movie 2023',
|
|
sourceTitle: 'test.movie.2023.1080p',
|
|
movieId: 1,
|
|
movie: moviesMap.get(1),
|
|
_instanceUrl: 'https://radarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
type: 'movie',
|
|
title: 'test.movie.2023.1080p',
|
|
status: 'Downloading',
|
|
progress: 75,
|
|
coverArt: 'https://example.com/movie-poster.jpg',
|
|
movieName: 'Test Movie',
|
|
allTags: ['alice'],
|
|
matchedUserTag: 'alice',
|
|
client: 'sabnzbd'
|
|
});
|
|
});
|
|
|
|
it('matches qBittorrent torrent to Sonarr series for tagged user', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: { data: { queue: { slots: [] } } },
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e02.720p',
|
|
sourceTitle: 'test.series.s01e02.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 201,
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: [{
|
|
hash: 'abc123',
|
|
name: 'test.series.s01e02.720p',
|
|
progress: 60,
|
|
dlspeed: 5242880,
|
|
eta: 600,
|
|
size: 1073741824,
|
|
savePath: '/downloads/test',
|
|
addedOn: new Date(Date.now() - 7200000).toISOString(),
|
|
availability: '50'
|
|
}]
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
type: 'series',
|
|
title: 'test.series.s01e02.720p',
|
|
seriesName: 'Test Series',
|
|
allTags: ['alice'],
|
|
matchedUserTag: 'alice',
|
|
client: 'qbittorrent'
|
|
});
|
|
expect(result[0]).toHaveProperty('id');
|
|
expect(result[0]).toHaveProperty('progress');
|
|
expect(result[0]).toHaveProperty('speed');
|
|
expect(result[0]).toHaveProperty('eta');
|
|
});
|
|
|
|
it('includes admin-specific fields when isAdmin is true', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'test.series.s01e03.720p',
|
|
nzbname: 'test.series.s01e03.720p',
|
|
status: 'Downloading',
|
|
percentage: 30,
|
|
mb: 1500,
|
|
mbmissing: 1050,
|
|
size: '1.5 GB',
|
|
timeleft: '15:00',
|
|
storage: '/downloads/test'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e03.720p',
|
|
sourceTitle: 'test.series.s01e03.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 202,
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin: true,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
downloadPath: '/downloads/test',
|
|
targetPath: '/series/test',
|
|
arrLink: 'https://sonarr.test/series/test-series',
|
|
arrQueueId: 100,
|
|
arrType: 'sonarr',
|
|
arrInstanceUrl: 'https://sonarr.test',
|
|
arrInstanceKey: 'test-key',
|
|
arrContentId: 202,
|
|
arrContentType: 'episode',
|
|
canBlocklist: true
|
|
});
|
|
});
|
|
|
|
it('filters by user tag when showAll is false', async () => {
|
|
const bobSeriesMap = new Map([
|
|
[2, {
|
|
id: 2,
|
|
title: 'Bob Series',
|
|
titleSlug: 'bob-series',
|
|
path: '/series/bob',
|
|
tags: [2], // Bob's tag
|
|
images: [{ coverType: 'poster', remoteUrl: 'https://example.com/bob-poster.jpg' }]
|
|
}]
|
|
]);
|
|
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'bob.series.s01e01.720p',
|
|
nzbname: 'bob.series.s01e01.720p',
|
|
status: 'Downloading',
|
|
percentage: 50,
|
|
mb: 1000,
|
|
mbmissing: 500,
|
|
size: '1 GB',
|
|
timeleft: '10:00',
|
|
storage: '/downloads/bob'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 200,
|
|
title: 'bob.series.s01e01.720p',
|
|
sourceTitle: 'bob.series.s01e01.720p',
|
|
seriesId: 2,
|
|
series: bobSeriesMap.get(2),
|
|
episodeId: 300
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username: 'alice',
|
|
usernameSanitized: 'alice',
|
|
isAdmin: false,
|
|
showAll: false,
|
|
seriesMap: bobSeriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
// Alice should not see Bob's download when showAll is false
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('shows all tagged downloads when showAll is true (admin mode)', async () => {
|
|
const bobSeriesMap = new Map([
|
|
[2, {
|
|
id: 2,
|
|
title: 'Bob Series',
|
|
titleSlug: 'bob-series',
|
|
path: '/series/bob',
|
|
tags: [2], // Bob's tag
|
|
images: [{ coverType: 'poster', remoteUrl: 'https://example.com/bob-poster.jpg' }]
|
|
}]
|
|
]);
|
|
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'bob.series.s01e01.720p',
|
|
nzbname: 'bob.series.s01e01.720p',
|
|
status: 'Downloading',
|
|
percentage: 50,
|
|
mb: 1000,
|
|
mbmissing: 500,
|
|
size: '1 GB',
|
|
timeleft: '10:00',
|
|
storage: '/downloads/bob'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 200,
|
|
title: 'bob.series.s01e01.720p',
|
|
sourceTitle: 'bob.series.s01e01.720p',
|
|
seriesId: 2,
|
|
series: bobSeriesMap.get(2),
|
|
episodeId: 300
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username: 'alice',
|
|
usernameSanitized: 'alice',
|
|
isAdmin: true,
|
|
showAll: true,
|
|
seriesMap: bobSeriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
// Admin with showAll=true should see all tagged downloads
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
type: 'series',
|
|
title: 'bob.series.s01e01.720p',
|
|
allTags: ['bob'],
|
|
matchedUserTag: null,
|
|
tagBadges: [{ label: 'bob', matchedUser: 'Bob' }]
|
|
});
|
|
});
|
|
|
|
it('includes importIssues when present in queue record', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'test.series.s01e04.720p',
|
|
nzbname: 'test.series.s01e04.720p',
|
|
status: 'Downloading',
|
|
percentage: 90,
|
|
mb: 2000,
|
|
mbmissing: 200,
|
|
size: '2 GB',
|
|
timeleft: '2:00',
|
|
storage: '/downloads/test'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e04.720p',
|
|
sourceTitle: 'test.series.s01e04.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 203,
|
|
trackedDownloadState: 'importPending',
|
|
trackedDownloadStatus: 'warning',
|
|
statusMessages: [{ messages: ['Sample needs repack'] }],
|
|
errorMessage: 'Disk space low',
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].importIssues).toEqual(['Sample needs repack', 'Disk space low']);
|
|
});
|
|
|
|
it('handles mixed series and movie downloads', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '10.0 MB/s',
|
|
kbpersec: 10240,
|
|
slots: [
|
|
{
|
|
filename: 'test.series.s01e05.720p',
|
|
nzbname: 'test.series.s01e05.720p',
|
|
status: 'Downloading',
|
|
percentage: 40,
|
|
mb: 800,
|
|
mbmissing: 480,
|
|
size: '800 MB',
|
|
timeleft: '8:00',
|
|
storage: '/downloads/series'
|
|
},
|
|
{
|
|
filename: 'test.movie.2023.1080p',
|
|
nzbname: 'test.movie.2023.1080p',
|
|
status: 'Downloading',
|
|
percentage: 60,
|
|
mb: 1200,
|
|
mbmissing: 480,
|
|
size: '1.2 GB',
|
|
timeleft: '6:00',
|
|
storage: '/downloads/movie'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e05.720p',
|
|
sourceTitle: 'test.series.s01e05.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 204,
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 101,
|
|
title: 'Test Movie 2023',
|
|
sourceTitle: 'test.movie.2023.1080p',
|
|
movieId: 1,
|
|
movie: moviesMap.get(1),
|
|
_instanceUrl: 'https://radarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].type).toBe('series');
|
|
expect(result[1].type).toBe('movie');
|
|
});
|
|
|
|
it('prevents duplicate downloads when same item matches multiple sources', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [{
|
|
filename: 'test.series.s01e06.720p',
|
|
nzbname: 'test.series.s01e06.720p',
|
|
status: 'Downloading',
|
|
percentage: 50,
|
|
mb: 1000,
|
|
mbmissing: 500,
|
|
size: '1 GB',
|
|
timeleft: '10:00',
|
|
storage: '/downloads/test'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e06.720p',
|
|
sourceTitle: 'test.series.s01e06.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 205,
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e06.720p',
|
|
sourceTitle: 'test.series.s01e06.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 205
|
|
}]
|
|
}
|
|
},
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: [{
|
|
hash: 'def456',
|
|
name: 'test.series.s01e06.720p',
|
|
progress: 50,
|
|
dlspeed: 5242880,
|
|
eta: 600,
|
|
size: 1073741824,
|
|
savePath: '/downloads/test'
|
|
}]
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
// Should only return one download item even though it matches in queue, history, and torrents
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
it('matches SABnzbd history slots to completed downloads', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: { data: { queue: { slots: [] } } },
|
|
sabnzbdHistory: {
|
|
data: {
|
|
history: {
|
|
slots: [{
|
|
name: 'test.series.s01e07.720p',
|
|
nzb_name: 'test.series.s01e07.720p',
|
|
status: 'Completed',
|
|
mb: 1000,
|
|
size: '1 GB',
|
|
completed_time: '2024-01-01T12:00:00Z',
|
|
storage: '/downloads/completed'
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
sonarrQueue: { data: { records: [] } },
|
|
sonarrHistory: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e07.720p',
|
|
sourceTitle: 'test.series.s01e07.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 206
|
|
}]
|
|
}
|
|
},
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
type: 'series',
|
|
title: 'test.series.s01e07.720p',
|
|
status: 'Completed',
|
|
completedAt: '2024-01-01T12:00:00Z'
|
|
});
|
|
});
|
|
|
|
it('does not display unmatched torrents', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: { data: { queue: { slots: [] } } },
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: { data: { records: [] } },
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: { data: { records: [] } },
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: [{
|
|
hash: 'ghi789',
|
|
name: 'test.movie.2023.1080p',
|
|
progress: 30,
|
|
dlspeed: 2097152,
|
|
eta: 1200,
|
|
size: 2147483648,
|
|
savePath: '/downloads/test',
|
|
addedOn: new Date(Date.now() - 7200000).toISOString(),
|
|
availability: '50'
|
|
}]
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin: false,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
// Unmatched torrents (not in Sonarr/Radarr queue/history) should not be displayed
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('includes sonarrLink and radarrLink when available', async () => {
|
|
const cacheSnapshot = {
|
|
sabnzbdQueue: {
|
|
data: {
|
|
queue: {
|
|
status: 'Downloading',
|
|
speed: '5.0 MB/s',
|
|
kbpersec: 5120,
|
|
slots: [
|
|
{
|
|
filename: 'test.series.s01e08.720p',
|
|
nzbname: 'test.series.s01e08.720p',
|
|
status: 'Downloading',
|
|
percentage: 25,
|
|
mb: 500,
|
|
mbmissing: 375,
|
|
size: '500 MB',
|
|
timeleft: '12:00',
|
|
storage: '/downloads/series'
|
|
},
|
|
{
|
|
filename: 'test.movie.2023.1080p',
|
|
nzbname: 'test.movie.2023.1080p',
|
|
status: 'Downloading',
|
|
percentage: 50,
|
|
mb: 1000,
|
|
mbmissing: 500,
|
|
size: '1 GB',
|
|
timeleft: '10:00',
|
|
storage: '/downloads/movie'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
sabnzbdHistory: { data: { history: { slots: [] } } },
|
|
sonarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 100,
|
|
title: 'test.series.s01e08.720p',
|
|
sourceTitle: 'test.series.s01e08.720p',
|
|
seriesId: 1,
|
|
series: seriesMap.get(1),
|
|
episodeId: 207,
|
|
_instanceUrl: 'https://sonarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
sonarrHistory: { data: { records: [] } },
|
|
radarrQueue: {
|
|
data: {
|
|
records: [{
|
|
id: 101,
|
|
title: 'Test Movie 2023',
|
|
sourceTitle: 'test.movie.2023.1080p',
|
|
movieId: 1,
|
|
movie: moviesMap.get(1),
|
|
_instanceUrl: 'https://radarr.test',
|
|
_instanceKey: 'test-key'
|
|
}]
|
|
}
|
|
},
|
|
radarrHistory: { data: { records: [] } },
|
|
qbittorrentTorrents: []
|
|
};
|
|
|
|
const result = await buildUserDownloads(cacheSnapshot, {
|
|
username,
|
|
usernameSanitized,
|
|
isAdmin: true,
|
|
showAll,
|
|
seriesMap,
|
|
moviesMap,
|
|
sonarrTagMap,
|
|
radarrTagMap,
|
|
embyUserMap
|
|
});
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].arrLink).toBe('https://sonarr.test/series/test-series');
|
|
expect(result[1].arrLink).toBe('https://radarr.test/movie/test-movie');
|
|
});
|
|
});
|