Add guard test for DownloadBuilder service
This commit is contained in:
@@ -0,0 +1,928 @@
|
||||
// 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', () => {
|
||||
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', () => {
|
||||
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 = buildUserDownloads(cacheSnapshot, {
|
||||
username,
|
||||
usernameSanitized,
|
||||
isAdmin,
|
||||
showAll,
|
||||
seriesMap,
|
||||
moviesMap,
|
||||
sonarrTagMap,
|
||||
radarrTagMap,
|
||||
embyUserMap
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array for null/undefined cache data', () => {
|
||||
const cacheSnapshot = {
|
||||
sabnzbdQueue: null,
|
||||
sabnzbdHistory: null,
|
||||
sonarrQueue: null,
|
||||
sonarrHistory: null,
|
||||
radarrQueue: null,
|
||||
radarrHistory: null,
|
||||
qbittorrentTorrents: null
|
||||
};
|
||||
|
||||
const result = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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)', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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', () => {
|
||||
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 = 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('sets canBlocklist correctly for non-admin users', () => {
|
||||
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(), // 2 hours ago
|
||||
availability: '50' // Low availability
|
||||
}]
|
||||
};
|
||||
|
||||
const result = buildUserDownloads(cacheSnapshot, {
|
||||
username,
|
||||
usernameSanitized,
|
||||
isAdmin: false,
|
||||
showAll,
|
||||
seriesMap,
|
||||
moviesMap,
|
||||
sonarrTagMap,
|
||||
radarrTagMap,
|
||||
embyUserMap
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
// Non-admin with old torrent and low availability should be able to blocklist
|
||||
expect(result[0].canBlocklist).toBe(true);
|
||||
});
|
||||
|
||||
it('includes sonarrLink and radarrLink when available', () => {
|
||||
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 = 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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user